diff --git a/.gitignore b/.gitignore index 2310dd4..ad8aeb5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /thinkphp/ /vendor/ /runtime/* -/addons/* /public/uploads/* .DS_Store .idea diff --git a/addons/.htaccess b/addons/.htaccess new file mode 100755 index 0000000..3418e55 --- /dev/null +++ b/addons/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/addons/alioss/.addonrc b/addons/alioss/.addonrc new file mode 100644 index 0000000..441ba46 --- /dev/null +++ b/addons/alioss/.addonrc @@ -0,0 +1 @@ +{"files":["public\/assets\/addons\/alioss\/js\/spark.js"],"license":"basic","licenseto":"15976","licensekey":"1vzyE5UVSQa426eu UsBJeMN90uf9sdJoJNiFHg==","domains":["0.1"],"licensecodes":[],"validations":["cbeddc9996d1ba6c230fc0319ab4d521"]} \ No newline at end of file diff --git a/addons/alioss/Alioss.php b/addons/alioss/Alioss.php new file mode 100644 index 0000000..e503c61 --- /dev/null +++ b/addons/alioss/Alioss.php @@ -0,0 +1,124 @@ +getConfig(); + $module = strtolower($request->module()); + // 判断api/common/upload 是否使用云存储上传 + if ($module == 'api' && ($config['apiupload'] ?? 0) && + strtolower($request->controller()) == 'common' && + strtolower($request->action()) == 'upload') { + request()->param('isApi', true); + App::invokeMethod(["\\addons\\alioss\\controller\\Index", "upload"], ['isApi' => true]); + } + } + + /** + * 加载配置 + */ + public function uploadConfigInit(&$upload) + { + $config = $this->getConfig(); + + $data = ['deadline' => time() + $config['expire']]; + $signature = hash_hmac('sha1', json_encode($data), $config['accessKeySecret'], true); + + $token = ''; + if (Auth::isModuleAllow()) { + $token = $config['accessKeyId'] . ':' . base64_encode($signature) . ':' . base64_encode(json_encode($data)); + } + $multipart = [ + 'aliosstoken' => $token + ]; + $config['uploadurl'] = 'https://' . $config['bucket'] . '.' . $config['endpoint']; + $upload = array_merge($upload, [ + 'cdnurl' => $config['cdnurl'], + 'uploadurl' => $config['uploadmode'] == 'client' ? $config['uploadurl'] : addon_url('alioss/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' => (bool)$config['multiple'], + ]); + } + + /** + * 附件删除后 + */ + public function uploadDelete($attachment) + { + $config = $this->getConfig(); + if ($attachment['storage'] == 'alioss' && isset($config['syncdelete']) && $config['syncdelete']) { + // 删除云存储端文件 + try { + $ossClient = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']); + $ossClient->deleteObject($config['bucket'], ltrim($attachment->url, '/')); + } catch (OssException $e) { + return false; + } + + //如果是服务端中转,还需要删除本地文件 + //if ($config['uploadmode'] == 'server') { + // $filePath = ROOT_PATH . 'public' . str_replace('/', DS, $attachment->url); + // if ($filePath) { + // @unlink($filePath); + // } + //} + } + return true; + } + +} diff --git a/addons/alioss/bootstrap.js b/addons/alioss/bootstrap.js new file mode 100755 index 0000000..a0604a6 --- /dev/null +++ b/addons/alioss/bootstrap.js @@ -0,0 +1,241 @@ +if (typeof Config.upload.storage !== 'undefined' && Config.upload.storage === 'alioss') { + require(['upload'], function (Upload) { + //获取文件MD5值 + var getFileMd5 = function (file, cb) { + //如果savekey中未检测到md5,则无需获取文件md5,直接返回upload的uuid + if (!Config.upload.savekey.match(/\{(file)?md5\}/)) { + cb && cb(file.upload.uuid); + return; + } + require(['../addons/alioss/js/spark'], function (SparkMD5) { + var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, + chunkSize = 10 * 1024 * 1024, + chunks = Math.ceil(file.size / chunkSize), + currentChunk = 0, + spark = new SparkMD5.ArrayBuffer(), + fileReader = new FileReader(); + + fileReader.onload = function (e) { + spark.append(e.target.result); + currentChunk++; + if (currentChunk < chunks) { + loadNext(); + } else { + cb && cb(spark.end()); + } + }; + + fileReader.onerror = function () { + console.warn('文件读取错误'); + }; + + function loadNext() { + var start = currentChunk * chunkSize, + end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; + + fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); + } + + loadNext(); + + }); + }; + + var _onInit = Upload.events.onInit; + //初始化中完成判断 + Upload.events.onInit = function () { + _onInit.apply(this, Array.prototype.slice.apply(arguments)); + //如果上传接口不是阿里OSS,则不处理 + if (this.options.url !== Config.upload.uploadurl) { + return; + } + $.extend(this.options, { + //关闭自动处理队列功能 + autoQueue: false, + params: function (files, xhr, chunk) { + var params = Config.upload.multipart; + if (chunk) { + return $.extend({}, params, { + filesize: chunk.file.size, + filename: chunk.file.name, + chunkid: chunk.file.upload.uuid, + chunkindex: chunk.index, + chunkcount: chunk.file.upload.totalChunkCount, + chunksize: this.options.chunkSize, + chunkfilesize: chunk.dataBlock.data.size, + width: chunk.file.width || 0, + height: chunk.file.height || 0, + type: chunk.file.type, + uploadId: chunk.file.uploadId, + key: chunk.file.key, + }); + } else { + params = $.extend({}, params, files[0].params); + params.category = files[0].category || ''; + } + return params; + }, + chunkSuccess: function (chunk, file, response) { + var etag = chunk.xhr.getResponseHeader("ETag").replace(/(^")|("$)/g, ''); + file.etags = file.etags ? file.etags : []; + file.etags[chunk.index] = etag; + }, + chunksUploaded: function (file, done) { + var that = this; + Fast.api.ajax({ + url: "/addons/alioss/index/upload", + data: { + action: 'merge', + filesize: file.size, + filename: file.name, + chunkid: file.upload.uuid, + chunkcount: file.upload.totalChunkCount, + md5: file.md5, + key: file.key, + uploadId: file.uploadId, + etags: file.etags, + category: file.category || '', + aliosstoken: Config.upload.multipart.aliosstoken, + }, + }, function (data, ret) { + done(JSON.stringify(ret)); + return false; + }, function (data, ret) { + file.accepted = false; + that._errorProcessing([file], ret.msg); + return false; + }); + + }, + }); + + var _success = this.options.success; + //先移除已有的事件 + this.off("success", _success).on("success", function (file, response) { + var ret = {code: 0, msg: response}; + try { + if (response) { + ret = typeof response === 'string' ? JSON.parse(response) : response; + } + if (file.xhr.status === 200) { + if (Config.upload.uploadmode === 'client') { + ret = {code: 1, data: {url: '/' + file.key}}; + var url = ret.data.url || ''; + + Fast.api.ajax({ + url: "/addons/alioss/index/notify", + data: {name: file.name, url: url, md5: file.md5, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, category: file.category || '', aliosstoken: Config.upload.multipart.aliosstoken} + }, function () { + return false; + }, function () { + return false; + }); + } + } else { + console.error(file.xhr); + } + } catch (e) { + console.error(e); + } + _success.call(this, file, ret); + }); + + this.on("addedfile", function (file) { + var that = this; + setTimeout(function () { + if (file.status === 'error') { + return; + } + getFileMd5(file, function (md5) { + var chunk = that.options.chunking && file.size > that.options.chunkSize ? 1 : 0; + var params = $(that.element).data("params") || {}; + var category = typeof params.category !== 'undefined' ? params.category : ($(that.element).data("category") || ''); + category = typeof category === 'function' ? category.call(that, file) : category; + Fast.api.ajax({ + url: "/addons/alioss/index/params", + data: {method: 'POST', category: category, md5: md5, name: file.name, type: file.type, size: file.size, chunk: chunk, chunksize: that.options.chunkSize, aliosstoken: Config.upload.multipart.aliosstoken}, + }, function (data) { + file.md5 = md5; + file.id = data.id; + file.key = data.key; + file.date = data.date; + file.uploadId = data.uploadId; + file.policy = data.policy; + file.signature = data.signature; + file.partsAuthorization = data.partsAuthorization; + file.params = data; + file.category = category; + + if (file.status != 'error') { + //开始上传 + that.enqueueFile(file); + } else { + that.removeFile(file); + } + return false; + }, function () { + that.removeFile(file); + }); + }); + }, 0); + }); + + if (Config.upload.uploadmode === 'client') { + var _method = this.options.method; + var _url = this.options.url; + this.options.method = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + if (!chunk) { + return "POST"; + } else { + return "PUT"; + } + } + return _method; + }; + this.options.url = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + var index = chunk.dataBlock.chunkIndex; + // debugger; + this.options.headers = {"Authorization": "OSS " + files[0]['id'] + ":" + files[0]['partsAuthorization'][index], "x-oss-date": files[0]['date']}; + if (!chunk) { + return Config.upload.uploadurl + "/" + files[0].key + "?uploadId=" + files[0].uploadId; + } else { + return Config.upload.uploadurl + "/" + files[0].key + "?partNumber=" + (index + 1) + "&uploadId=" + files[0].uploadId; + } + } + return _url; + }; + this.on("sending", function (file, xhr, formData) { + var that = this; + if (file.upload.chunked) { + var _send = xhr.send; + xhr.send = function () { + var chunk = null; + file.upload.chunks.forEach(function (item) { + if (item.status == 'uploading') { + chunk = item; + } + }); + if (chunk) { + _send.call(xhr, chunk.dataBlock.data); + } + }; + } + }); + } + }; + }); +} diff --git a/addons/alioss/config.php b/addons/alioss/config.php new file mode 100644 index 0000000..0d958c2 --- /dev/null +++ b/addons/alioss/config.php @@ -0,0 +1,233 @@ + 'accessKeyId', + 'title' => 'AccessKey ID', + 'type' => 'string', + 'content' => [], + 'value' => '', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'accessKeySecret', + 'title' => 'AccessKey Secret', + 'type' => 'string', + 'content' => [], + 'value' => '', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'bucket', + 'title' => 'Bucket名称', + 'type' => 'string', + 'content' => [], + 'value' => 'yourbucket', + 'rule' => 'required;bucket', + 'msg' => '', + 'tip' => '阿里云OSS的空间名', + 'ok' => '', + 'extend' => 'data-rule-bucket="[/^[0-9a-z_\-]{3,63}$/, \'请输入正确的Bucket名称\']"', + ], + [ + 'name' => 'endpoint', + 'title' => 'Endpoint', + 'type' => 'string', + 'content' => [], + 'value' => 'oss-cn-shenzhen.aliyuncs.com', + 'rule' => 'required;endpoint', + 'msg' => '', + 'tip' => '请填写从阿里云存储获取的Endpoint', + 'ok' => '', + 'extend' => 'data-rule-endpoint="[/^(?!http(s)?:\/\/).*$/, \'不能以http(s)://开头\']"', + ], + [ + 'name' => 'cdnurl', + 'title' => 'CDN地址', + 'type' => 'string', + 'content' => [], + 'value' => 'https://yourbucket.oss-cn-shenzhen.aliyuncs.com', + 'rule' => 'required;cdnurl', + 'msg' => '', + 'tip' => '请填写CDN地址,必须以http(s)://开头', + 'ok' => '', + 'extend' => 'data-rule-cdnurl="[/^http(s)?:\/\/.*$/, \'必需以http(s)://开头\']"', + ], + [ + 'name' => 'uploadmode', + 'title' => '上传模式', + 'type' => 'select', + 'content' => [ + 'client' => '客户端直传(速度快,无备份)', + 'server' => '服务器中转(占用服务器带宽,可备份)', + ], + 'value' => 'server', + 'rule' => '', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'serverbackup', + 'title' => '服务器中转模式备份', + 'type' => 'radio', + 'content' => [ + 1 => '备份(附件管理将产生2条记录)', + 0 => '不备份', + ], + 'value' => '1', + 'rule' => '', + 'msg' => '', + 'tip' => '服务器中转模式下是否备份文件', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'savekey', + 'title' => '保存文件名', + 'type' => 'string', + 'content' => [], + 'value' => '/uploads/{year}{mon}{day}/{filemd5}{.suffix}', + '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' => 'bool', + 'content' => [], + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'thumbstyle', + 'title' => '缩略图样式', + 'type' => 'string', + 'content' => [], + 'value' => '', + 'rule' => '', + 'msg' => '', + 'tip' => '用于后台列表缩略图样式,可使用:?x-oss-process=image/resize,m_lfit,w_120,h_90', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'chunking', + 'title' => '分片上传', + 'type' => 'radio', + 'content' => [ + 1 => '开启', + 0 => '关闭', + ], + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'chunksize', + 'title' => '分片大小', + 'type' => 'number', + 'content' => [], + 'value' => '4194304', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'syncdelete', + 'title' => '附件删除时是否同步删除云存储文件', + 'type' => 'bool', + 'content' => [], + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'apiupload', + 'title' => 'API接口使用云存储', + 'type' => 'bool', + 'content' => [], + 'value' => '0', + 'rule' => 'required', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], + [ + 'name' => 'noneedlogin', + 'title' => '免登录上传', + 'type' => 'checkbox', + 'content' => [ + 'api' => 'API', + 'index' => '前台', + 'admin' => '后台', + ], + 'value' => '', + 'rule' => '', + 'msg' => '', + 'tip' => '', + 'ok' => '', + 'extend' => '', + ], +]; diff --git a/addons/alioss/controller/Index.php b/addons/alioss/controller/Index.php new file mode 100755 index 0000000..c9139b3 --- /dev/null +++ b/addons/alioss/controller/Index.php @@ -0,0 +1,292 @@ +error("当前插件暂无前台页面"); + } + + /** + * 获取签名 + */ + public function params() + { + $this->check(); + + $config = get_addon_config('alioss'); + $name = $this->request->post('name'); + $md5 = $this->request->post('md5'); + $chunk = $this->request->post('chunk'); + $name = xss_clean($name); + + // 检查文件后缀 + $extension = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + $allowedExtensions = explode(',', strtolower($config['mimetype'])); + if (!in_array($extension, $allowedExtensions) || in_array($extension, ['php', 'html', 'htm', 'phar', 'phtml']) || preg_match("/^php(.*)/i", $extension)) { + $this->error('不允许的文件类型'); + } + + $auth = new \addons\alioss\library\Auth(); + $params = $auth->params($name, $md5); + $params['OSSAccessKeyId'] = $params['id']; + $params['success_action_status'] = 200; + $config = get_addon_config('alioss'); + + if ($chunk) { + $oss = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']); + // 初始化 + $fileSize = $this->request->post('size'); + $chunkSize = $this->request->post('chunksize'); + $uploadId = $oss->initiateMultipartUpload($config['bucket'], $params['key']); + $params['uploadId'] = $uploadId; + $params['parts'] = $oss->generateMultiuploadParts($fileSize, $chunkSize); + $params['partsAuthorization'] = []; + $date = gmdate('D, d M Y H:i:s \G\M\T'); + foreach ($params['parts'] as $index => $part) { + $partNumber = $index + 1; + $signstr = "PUT\n\n\n{$date}\nx-oss-date:{$date}\n/{$config['bucket']}/{$params['key']}?partNumber={$partNumber}&uploadId={$uploadId}"; + $authorization = base64_encode(hash_hmac('sha1', $signstr, $config['accessKeySecret'], true)); + $params['partsAuthorization'][$index] = $authorization; + } + $params['date'] = $date; + } + + $this->success('', null, $params); + } + + /** + * 服务器中转上传文件 + * 上传分片 + * 合并分片 + * @param bool $isApi + */ + public function upload($isApi = false) + { + $config = get_addon_config('alioss'); + if ($isApi === true) { + if (!Auth::isModuleAllow()) { + $this->error("请登录后再进行操作"); + } + } else { + $this->check(); + } + $oss = new OssClient($config['accessKeyId'], $config['accessKeySecret'], $config['endpoint']); + + //检测删除文件或附件 + $checkDeleteFile = function ($attachment, $upload, $force = false) use ($config) { + //如果设定为不备份则删除文件和记录 或 强制删除 + if ((isset($config['serverbackup']) && !$config['serverbackup']) || $force) { + if ($attachment && !empty($attachment['id'])) { + $attachment->delete(); + } + if ($upload) { + //文件绝对路径 + $filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname(); + @unlink($filePath); + } + } + }; + + $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"); + $method = $this->request->method(true); + $key = $this->request->post("key"); + $uploadId = $this->request->post("uploadId"); + + if ($action == 'merge') { + $attachment = null; + $upload = null; + //合并分片 + if ($config['uploadmode'] == 'server') { + //合并分片文件 + try { + $upload = new Upload(); + $attachment = $upload->merge($chunkid, $chunkcount, $filename); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + } + + $etags = $this->request->post("etags/a", []); + if (count($etags) != $chunkcount) { + $checkDeleteFile($attachment, $upload, true); + $this->error("分片数据错误"); + } + $listParts = []; + for ($i = 0; $i < $chunkcount; $i++) { + $listParts[] = array("PartNumber" => $i + 1, "ETag" => $etags[$i]); + } + try { + $ret = $oss->completeMultipartUpload($config['bucket'], $key, $uploadId, $listParts); + } catch (\Exception $e) { + $checkDeleteFile($attachment, $upload, true); + $this->error($e->getMessage()); + } + + $result = json_decode(json_encode(simplexml_load_string($ret['body'], "SimpleXMLElement", LIBXML_NOCDATA)), true); + if (!isset($result['Key'])) { + $checkDeleteFile($attachment, $upload, true); + $this->error("上传失败(1001)"); + } else { + $checkDeleteFile($attachment, $upload); + $this->success("上传成功", '', ['url' => "/" . $key, 'fullurl' => cdnurl("/" . $key, true)]); + } + } else { + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $file = $upload->chunk($chunkid, $chunkindex, $chunkcount); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + try { + //上传分片到OSS + $ret = $oss->uploadPart($config['bucket'], $key, $uploadId, ['fileUpload' => $file->getRealPath(), 'partNumber' => $chunkindex + 1]); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + + $this->success("上传成功", "", [], 3, ['ETag' => $ret]); + } + } else { + $attachment = null; + //默认普通上传文件 + $file = $this->request->file('file'); + try { + $upload = new Upload($file); + $attachment = $upload->upload(); + } catch (UploadException $e) { + $this->error($e->getMessage()); + } + + //文件绝对路径 + $filePath = $upload->getFile()->getRealPath() ?: $upload->getFile()->getPathname(); + + $url = $attachment->url; + + try { + $ret = $oss->uploadFile($config['bucket'], ltrim($attachment->url, "/"), $filePath); + //成功不做任何操作 + } catch (\Exception $e) { + $checkDeleteFile($attachment, $upload, true); + \think\Log::write($e->getMessage()); + $this->error("上传失败(1002)"); + } + $checkDeleteFile($attachment, $upload); + + // 记录云存储记录 + $data = $attachment->toArray(); + unset($data['id']); + $data['storage'] = 'alioss'; + Attachment::create($data, true); + + $this->success("上传成功", '', ['url' => $url, 'fullurl' => cdnurl($url, true)]); + } + return; + } + + /** + * 回调 + */ + public function notify() + { + $this->check(); + $config = get_addon_config('alioss'); + if ($config['uploadmode'] != 'client') { + $this->error("无需执行该操作"); + } + $this->request->filter('trim,strip_tags,htmlspecialchars,xss_clean'); + $size = $this->request->post('size/d'); + $name = $this->request->post('name', ''); + $md5 = $this->request->post('md5', ''); + $type = $this->request->post('type', ''); + $url = $this->request->post('url', ''); + $width = $this->request->post('width/d'); + $height = $this->request->post('height/d'); + $category = $this->request->post('category', ''); + $category = array_key_exists($category, config('site.attachmentcategory') ?? []) ? $category : ''; + $suffix = strtolower(pathinfo($name, PATHINFO_EXTENSION)); + $suffix = $suffix && preg_match("/^[a-zA-Z0-9]+$/", $suffix) ? $suffix : 'file'; + $attachment = Attachment::where('url', $url)->where('storage', 'alioss')->find(); + if (!$attachment) { + $params = array( + 'category' => $category, + 'admin_id' => (int)session('admin.id'), + 'user_id' => (int)$this->auth->id, + 'filesize' => $size, + 'filename' => $name, + 'imagewidth' => $width, + 'imageheight' => $height, + 'imagetype' => $suffix, + 'imageframes' => 0, + 'mimetype' => $type, + 'url' => $url, + 'uploadtime' => time(), + 'storage' => 'alioss', + 'sha1' => $md5, + ); + Attachment::create($params, true); + } + $this->success(); + } + + /** + * 检查签名是否正确或过期 + */ + protected function check() + { + $aliosstoken = $this->request->post('aliosstoken', '', 'trim'); + if (!$aliosstoken) { + $this->error("参数不正确(code:1)"); + } + $config = get_addon_config('alioss'); + list($accessKeyId, $sign, $data) = explode(':', $aliosstoken); + if (!$accessKeyId || !$sign || !$data) { + $this->error("参数不能为空(code:2)"); + } + if ($accessKeyId !== $config['accessKeyId']) { + $this->error("参数不正确(code:3)"); + } + if ($sign !== base64_encode(hash_hmac('sha1', base64_decode($data), $config['accessKeySecret'], true))) { + $this->error("签名不正确"); + } + $json = json_decode(base64_decode($data), true); + if ($json['deadline'] < time()) { + $this->error("请求已经超时"); + } + } + +} diff --git a/addons/alioss/info.ini b/addons/alioss/info.ini new file mode 100644 index 0000000..7b8a67a --- /dev/null +++ b/addons/alioss/info.ini @@ -0,0 +1,10 @@ +name = alioss +title = 阿里云OSS云储存 +intro = 使用阿里云OSS云储存,支持直传、服务器中转、分片上传 +author = FastAdmin +website = https://www.fastadmin.net +version = 1.2.6 +state = 1 +url = /addons/alioss +license = basic +licenseto = 15976 diff --git a/addons/alioss/library/Auth.php b/addons/alioss/library/Auth.php new file mode 100755 index 0000000..2a19aaf --- /dev/null +++ b/addons/alioss/library/Auth.php @@ -0,0 +1,102 @@ + isset($config['notifyurl']) ? $config['notifyurl'] : '', + 'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}', + 'callbackBodyType' => "application/x-www-form-urlencoded" + ); + + $base64_callback_body = base64_encode(json_encode($callback_param)); + + $now = time(); + $end = $now + $config['expire']; //设置该policy超时时间是10s. 即这个policy过了这个有效时间,将不能访问 + $expiration = $this->gmt_iso8601($end); + + preg_match('/(\d+)(\w+)/', $config['maxsize'], $matches); + $type = strtolower($matches[2]); + $typeDict = ['b' => 0, 'k' => 1, 'kb' => 1, 'm' => 2, 'mb' => 2, 'gb' => 3, 'g' => 3]; + $size = (int)$config['maxsize'] * pow(1024, $typeDict[$type] ?? 0); + + //最大文件大小.用户可以自己设置 + $condition = array(0 => 'content-length-range', 1 => 0, 2 => $size); + $conditions[] = $condition; + + //表示用户上传的数据,必须是以$dir开始, 不然上传会失败,这一步不是必须项,只是为了安全起见,防止用户通过policy上传到别人的目录 + //$start = array(0 => 'starts-with', 1 => '$key', 2 => $dir); + //$conditions[] = $start; + + $arr = array('expiration' => $expiration, 'conditions' => $conditions); + + $policy = base64_encode(json_encode($arr)); + $signature = base64_encode(hash_hmac('sha1', $policy, $config['accessKeySecret'], true)); + + $key = (new Upload())->getSavekey($config['savekey'], $name, $md5); + $key = ltrim($key, "/"); + + $response = array(); + $response['id'] = $config['accessKeyId']; + $response['key'] = $key; + $response['policy'] = $policy; + $response['signature'] = $signature; + $response['expire'] = $end; + $response['callback'] = ''; + return $response; + } + + public function check($signature, $policy) + { + $config = get_addon_config('alioss'); + $sign = base64_encode(hash_hmac('sha1', $policy, $config['accessKeySecret'], true)); + return $signature == $sign; + } + + private function gmt_iso8601($time) + { + $dtStr = date("c", $time); + $mydatetime = new \DateTime($dtStr); + $expiration = $mydatetime->format(\DateTime::ISO8601); + $pos = strpos($expiration, '+'); + $expiration = substr($expiration, 0, $pos); + return $expiration . "Z"; + } + + public static function isModuleAllow() + { + $config = get_addon_config('alioss'); + $module = request()->module(); + $module = $module ? strtolower($module) : 'index'; + $noNeedLogin = array_filter(explode(',', $config['noneedlogin'] ?? '')); + $isModuleLogin = false; + $tagName = 'upload_config_checklogin'; + foreach (\think\Hook::get($tagName) as $index => $name) { + if (\think\Hook::exec($name, $tagName)) { + $isModuleLogin = true; + break; + } + } + if (in_array($module, $noNeedLogin) + || ($module == 'admin' && \app\admin\library\Auth::instance()->id) + || ($module != 'admin' && \app\common\library\Auth::instance()->id) + || $isModuleLogin) { + return true; + } else { + return false; + } + } + +} diff --git a/addons/alioss/library/OSS/Core/MimeTypes.php b/addons/alioss/library/OSS/Core/MimeTypes.php new file mode 100755 index 0000000..e9b88ff --- /dev/null +++ b/addons/alioss/library/OSS/Core/MimeTypes.php @@ -0,0 +1,262 @@ + 1) { + $ext = strtolower(end($parts)); + if (isset(self::$mime_types[$ext])) { + return self::$mime_types[$ext]; + } + } + + return null; + } + + private static $mime_types = array( + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'apk' => 'application/vnd.android.package-archive', + 'hqx' => 'application/mac-binhex40', + 'cpt' => 'application/mac-compactpro', + 'doc' => 'application/msword', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'rtf' => 'text/rtf', + 'mif' => 'application/vnd.mif', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'sxw' => 'application/vnd.sun.xml.writer', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sxc' => 'application/vnd.sun.xml.calc', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'sxd' => 'application/vnd.sun.xml.draw', + 'std' => 'application/vnd.sun.xml.draw.template', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxm' => 'application/vnd.sun.xml.math', + 'sis' => 'application/vnd.symbian.install', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'bcpio' => 'application/x-bcpio', + 'torrent' => 'application/x-bittorrent', + 'bz2' => 'application/x-bzip2', + 'vcd' => 'application/x-cdlink', + 'pgn' => 'application/x-chess-pgn', + 'cpio' => 'application/x-cpio', + 'csh' => 'application/x-csh', + 'dvi' => 'application/x-dvi', + 'spl' => 'application/x-futuresplash', + 'gtar' => 'application/x-gtar', + 'hdf' => 'application/x-hdf', + 'jar' => 'application/java-archive', + 'jnlp' => 'application/x-java-jnlp-file', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'ksp' => 'application/x-kspread', + 'chrt' => 'application/x-kchart', + 'kil' => 'application/x-killustrator', + 'latex' => 'application/x-latex', + 'rpm' => 'application/x-rpm', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'swf' => 'application/x-shockwave-flash', + 'sit' => 'application/x-stuffit', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'tar' => 'application/x-tar', + 'tcl' => 'application/x-tcl', + 'tex' => 'application/x-tex', + 'man' => 'application/x-troff-man', + 'me' => 'application/x-troff-me', + 'ms' => 'application/x-troff-ms', + 'ustar' => 'application/x-ustar', + 'src' => 'application/x-wais-source', + 'zip' => 'application/zip', + 'm3u' => 'audio/x-mpegurl', + 'ra' => 'audio/x-pn-realaudio', + 'wav' => 'audio/x-wav', + 'wma' => 'audio/x-ms-wma', + 'wax' => 'audio/x-ms-wax', + 'pdb' => 'chemical/x-pdb', + 'xyz' => 'chemical/x-xyz', + 'bmp' => 'image/bmp', + 'gif' => 'image/gif', + 'ief' => 'image/ief', + 'png' => 'image/png', + 'wbmp' => 'image/vnd.wap.wbmp', + 'ras' => 'image/x-cmu-raster', + 'pnm' => 'image/x-portable-anymap', + 'pbm' => 'image/x-portable-bitmap', + 'pgm' => 'image/x-portable-graymap', + 'ppm' => 'image/x-portable-pixmap', + 'rgb' => 'image/x-rgb', + 'xbm' => 'image/x-xbitmap', + 'xpm' => 'image/x-xpixmap', + 'xwd' => 'image/x-xwindowdump', + 'css' => 'text/css', + 'rtx' => 'text/richtext', + 'tsv' => 'text/tab-separated-values', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'wml' => 'text/vnd.wap.wml', + 'wmls' => 'text/vnd.wap.wmlscript', + 'etx' => 'text/x-setext', + 'mxu' => 'video/vnd.mpegurl', + 'flv' => 'video/x-flv', + 'wm' => 'video/x-ms-wm', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wvx' => 'video/x-ms-wvx', + 'avi' => 'video/x-msvideo', + 'movie' => 'video/x-sgi-movie', + 'ice' => 'x-conference/x-cooltalk', + '3gp' => 'video/3gpp', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'asc' => 'text/plain', + 'atom' => 'application/atom+xml', + 'au' => 'audio/basic', + 'bin' => 'application/octet-stream', + 'cdf' => 'application/x-netcdf', + 'cgm' => 'image/cgm', + 'class' => 'application/octet-stream', + 'dcr' => 'application/x-director', + 'dif' => 'video/x-dv', + 'dir' => 'application/x-director', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/octet-stream', + 'dms' => 'application/octet-stream', + 'dtd' => 'application/xml-dtd', + 'dv' => 'video/x-dv', + 'dxr' => 'application/x-director', + 'eps' => 'application/postscript', + 'exe' => 'application/octet-stream', + 'ez' => 'application/andrew-inset', + 'gram' => 'application/srgs', + 'grxml' => 'application/srgs+xml', + 'gz' => 'application/x-gzip', + 'htm' => 'text/html', + 'html' => 'text/html', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ifb' => 'text/calendar', + 'iges' => 'model/iges', + 'igs' => 'model/iges', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'kar' => 'audio/midi', + 'lha' => 'application/octet-stream', + 'lzh' => 'application/octet-stream', + 'm4a' => 'audio/mp4a-latm', + 'm4p' => 'audio/mp4a-latm', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'mac' => 'image/x-macpaint', + 'mathml' => 'application/mathml+xml', + 'mesh' => 'model/mesh', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp2' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpga' => 'audio/mpeg', + 'msh' => 'model/mesh', + 'nc' => 'application/x-netcdf', + 'oda' => 'application/oda', + 'ogv' => 'video/ogv', + 'pct' => 'image/pict', + 'pic' => 'image/pict', + 'pict' => 'image/pict', + 'pnt' => 'image/x-macpaint', + 'pntg' => 'image/x-macpaint', + 'ps' => 'application/postscript', + 'qt' => 'video/quicktime', + 'qti' => 'image/x-quicktime', + 'qtif' => 'image/x-quicktime', + 'ram' => 'audio/x-pn-realaudio', + 'rdf' => 'application/rdf+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'roff' => 'application/x-troff', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'silo' => 'model/mesh', + 'skd' => 'application/x-koan', + 'skm' => 'application/x-koan', + 'skp' => 'application/x-koan', + 'skt' => 'application/x-koan', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'snd' => 'audio/basic', + 'so' => 'application/octet-stream', + 'svg' => 'image/svg+xml', + 't' => 'application/x-troff', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tr' => 'application/x-troff', + 'txt' => 'text/plain', + 'vrml' => 'model/vrml', + 'vxml' => 'application/voicexml+xml', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wrl' => 'model/vrml', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xml' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + ); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Core/OssException.php b/addons/alioss/library/OSS/Core/OssException.php new file mode 100755 index 0000000..b0e9e8b --- /dev/null +++ b/addons/alioss/library/OSS/Core/OssException.php @@ -0,0 +1,54 @@ +details = $details; + } else { + $message = $details; + parent::__construct($message); + } + } + + public function getHTTPStatus() + { + return isset($this->details['status']) ? $this->details['status'] : ''; + } + + public function getRequestId() + { + return isset($this->details['request-id']) ? $this->details['request-id'] : ''; + } + + public function getErrorCode() + { + return isset($this->details['code']) ? $this->details['code'] : ''; + } + + public function getErrorMessage() + { + return isset($this->details['message']) ? $this->details['message'] : ''; + } + + public function getDetails() + { + return isset($this->details['body']) ? $this->details['body'] : ''; + } +} diff --git a/addons/alioss/library/OSS/Core/OssUtil.php b/addons/alioss/library/OSS/Core/OssUtil.php new file mode 100755 index 0000000..6e5d413 --- /dev/null +++ b/addons/alioss/library/OSS/Core/OssUtil.php @@ -0,0 +1,461 @@ + $value) { + if (is_string($key) && !is_array($value)) { + $temp[] = rawurlencode($key) . '=' . rawurlencode($value); + } + } + return implode('&', $temp); + } + + /** + * 转义字符替换 + * + * @param string $subject + * @return string + */ + public static function sReplace($subject) + { + $search = array('<', '>', '&', '\'', '"'); + $replace = array('<', '>', '&', ''', '"'); + return str_replace($search, $replace, $subject); + } + + /** + * 检查是否是中文编码 + * + * @param $str + * @return int + */ + public static function chkChinese($str) + { + return preg_match('/[\x80-\xff]./', $str); + } + + /** + * 检测是否GB2312编码 + * + * @param string $str + * @return boolean false UTF-8编码 TRUE GB2312编码 + */ + public static function isGb2312($str) + { + for ($i = 0; $i < strlen($str); $i++) { + $v = ord($str[$i]); + if ($v > 127) { + if (($v >= 228) && ($v <= 233)) { + if (($i + 2) >= (strlen($str) - 1)) return true; // not enough characters + $v1 = ord($str[$i + 1]); + $v2 = ord($str[$i + 2]); + if (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) + return false; + else + return true; + } + } + } + return false; + } + + /** + * 检测是否GBK编码 + * + * @param string $str + * @param boolean $gbk + * @return boolean + */ + public static function checkChar($str, $gbk = true) + { + for ($i = 0; $i < strlen($str); $i++) { + $v = ord($str[$i]); + if ($v > 127) { + if (($v >= 228) && ($v <= 233)) { + if (($i + 2) >= (strlen($str) - 1)) return $gbk ? true : FALSE; // not enough characters + $v1 = ord($str[$i + 1]); + $v2 = ord($str[$i + 2]); + if ($gbk) { + return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? FALSE : TRUE;//GBK + } else { + return (($v1 >= 128) && ($v1 <= 191) && ($v2 >= 128) && ($v2 <= 191)) ? TRUE : FALSE; + } + } + } + } + return $gbk ? TRUE : FALSE; + } + + /** + * 检验bucket名称是否合法 + * bucket的命名规范: + * 1. 只能包括小写字母,数字 + * 2. 必须以小写字母或者数字开头 + * 3. 长度必须在3-63字节之间 + * + * @param string $bucket Bucket名称 + * @return boolean + */ + public static function validateBucket($bucket) + { + $pattern = '/^[a-z0-9][a-z0-9-]{2,62}$/'; + if (!preg_match($pattern, $bucket)) { + return false; + } + return true; + } + + /** + * 检验object名称是否合法 + * object命名规范: + * 1. 规则长度必须在1-1023字节之间 + * 2. 使用UTF-8编码 + * 3. 不能以 "/" "\\"开头 + * + * @param string $object Object名称 + * @return boolean + */ + public static function validateObject($object) + { + $pattern = '/^.{1,1023}$/'; + if (empty($object) || !preg_match($pattern, $object) || + self::startsWith($object, '/') || self::startsWith($object, '\\') + ) { + return false; + } + return true; + } + + + /** + * 判断字符串$str是不是以$findMe开始 + * + * @param string $str + * @param string $findMe + * @return bool + */ + public static function startsWith($str, $findMe) + { + if (strpos($str, $findMe) === 0) { + return true; + } else { + return false; + } + } + + /** + * 生成createBucketXmlBody接口的xml消息 + * + * @param string $storageClass + * @return string + */ + public static function createBucketXmlBody($storageClass) + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('StorageClass', $storageClass); + return $xml->asXML(); + } + + /** + * 检验$options + * + * @param array $options + * @throws OssException + * @return boolean + */ + public static function validateOptions($options) + { + //$options + if ($options != NULL && !is_array($options)) { + throw new OssException ($options . ':' . 'option must be array'); + } + } + + /** + * 检查上传文件的内容是否合法 + * + * @param $content string + * @throws OssException + */ + public static function validateContent($content) + { + if (empty($content)) { + throw new OssException("http body content is invalid"); + } + } + + /** + * 校验BUCKET/OBJECT/OBJECT GROUP是否为空 + * + * @param string $name + * @param string $errMsg + * @throws OssException + * @return void + */ + public static function throwOssExceptionWithMessageIfEmpty($name, $errMsg) + { + if (empty($name)) { + throw new OssException($errMsg); + } + } + + /** + * 仅供测试使用的接口,请勿使用 + * + * @param $filename + * @param $size + */ + public static function generateFile($filename, $size) + { + if (file_exists($filename) && $size == filesize($filename)) { + echo $filename . " already exists, no need to create again. "; + return; + } + $part_size = 1 * 1024 * 1024; + $fp = fopen($filename, "w"); + $characters = << 0) { + if ($size < $part_size) { + $write_size = $size; + } else { + $write_size = $part_size; + } + $size -= $write_size; + $a = $characters[rand(0, $charactersLength - 1)]; + $content = str_repeat($a, $write_size); + $flag = fwrite($fp, $content); + if (!$flag) { + echo "write to " . $filename . " failed.
"; + break; + } + } + } else { + echo "open " . $filename . " failed.
"; + } + fclose($fp); + } + + /** + * 得到文件的md5编码 + * + * @param $filename + * @param $from_pos + * @param $to_pos + * @return string + */ + public static function getMd5SumForFile($filename, $from_pos, $to_pos) + { + $content_md5 = ""; + if (($to_pos - $from_pos) > self::OSS_MAX_PART_SIZE) { + return $content_md5; + } + $filesize = filesize($filename); + if ($from_pos >= $filesize || $to_pos >= $filesize || $from_pos < 0 || $to_pos < 0) { + return $content_md5; + } + + $total_length = $to_pos - $from_pos + 1; + $buffer = 8192; + $left_length = $total_length; + if (!file_exists($filename)) { + return $content_md5; + } + + if (false === $fh = fopen($filename, 'rb')) { + return $content_md5; + } + + fseek($fh, $from_pos); + $data = ''; + while (!feof($fh)) { + if ($left_length >= $buffer) { + $read_length = $buffer; + } else { + $read_length = $left_length; + } + if ($read_length <= 0) { + break; + } else { + $data .= fread($fh, $read_length); + $left_length = $left_length - $read_length; + } + } + fclose($fh); + $content_md5 = base64_encode(md5($data, true)); + return $content_md5; + } + + /** + * 检测是否windows系统,因为windows系统默认编码为GBK + * + * @return bool + */ + public static function isWin() + { + return strtoupper(substr(PHP_OS, 0, 3)) == "WIN"; + } + + /** + * 主要是由于windows系统编码是gbk,遇到中文时候,如果不进行转换处理会出现找不到文件的问题 + * + * @param $file_path + * @return string + */ + public static function encodePath($file_path) + { + if (self::chkChinese($file_path) && self::isWin()) { + $file_path = iconv('utf-8', 'gbk', $file_path); + } + return $file_path; + } + + /** + * 判断用户输入的endpoint是否是 xxx.xxx.xxx.xxx:port 或者 xxx.xxx.xxx.xxx的ip格式 + * + * @param string $endpoint 需要做判断的endpoint + * @return boolean + */ + public static function isIPFormat($endpoint) + { + $ip_array = explode(":", $endpoint); + $hostname = $ip_array[0]; + $ret = filter_var($hostname, FILTER_VALIDATE_IP); + if (!$ret) { + return false; + } else { + return true; + } + } + + /** + * 生成DeleteMultiObjects接口的xml消息 + * + * @param string[] $objects + * @param bool $quiet + * @return string + */ + public static function createDeleteObjectsXmlBody($objects, $quiet) + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('Quiet', $quiet); + foreach ($objects as $object) { + $sub_object = $xml->addChild('Object'); + $object = OssUtil::sReplace($object); + $sub_object->addChild('Key', $object); + } + return $xml->asXML(); + } + + /** + * 生成CompleteMultipartUpload接口的xml消息 + * + * @param array[] $listParts + * @return string + */ + public static function createCompleteMultipartUploadXmlBody($listParts) + { + $xml = new \SimpleXMLElement(''); + foreach ($listParts as $node) { + $part = $xml->addChild('Part'); + $part->addChild('PartNumber', $node['PartNumber']); + $part->addChild('ETag', $node['ETag']); + } + return $xml->asXML(); + } + + /** + * 读取目录 + * + * @param string $dir + * @param string $exclude + * @param bool $recursive + * @return string[] + */ + public static function readDir($dir, $exclude = ".|..|.svn|.git", $recursive = false) + { + $file_list_array = array(); + $base_path = $dir; + $exclude_array = explode("|", $exclude); + $exclude_array = array_unique(array_merge($exclude_array, array('.', '..'))); + + if ($recursive) { + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)) as $new_file) { + if ($new_file->isDir()) continue; + $object = str_replace($base_path, '', $new_file); + if (!in_array(strtolower($object), $exclude_array)) { + $object = ltrim($object, '/'); + if (is_file($new_file)) { + $key = md5($new_file . $object, false); + $file_list_array[$key] = array('path' => $new_file, 'file' => $object,); + } + } + } + } else if ($handle = opendir($dir)) { + while (false !== ($file = readdir($handle))) { + if (!in_array(strtolower($file), $exclude_array)) { + $new_file = $dir . '/' . $file; + $object = $file; + $object = ltrim($object, '/'); + if (is_file($new_file)) { + $key = md5($new_file . $object, false); + $file_list_array[$key] = array('path' => $new_file, 'file' => $object,); + } + } + } + closedir($handle); + } + return $file_list_array; + } + + /** + * Decode key based on the encoding type + * + * @param string $key + * @param string $encoding + * @return string + */ + public static function decodeKey($key, $encoding) + { + if ($encoding == "") { + return $key; + } + + if ($encoding == "url") { + return rawurldecode($key); + } else { + throw new OssException("Unrecognized encoding type: " . $encoding); + } + } +} diff --git a/addons/alioss/library/OSS/Http/LICENSE b/addons/alioss/library/OSS/Http/LICENSE new file mode 100755 index 0000000..49b38bd --- /dev/null +++ b/addons/alioss/library/OSS/Http/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2006-2010 Ryan Parman, Foleeo Inc., and contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name of Ryan Parman, Foleeo Inc. nor the names of its contributors may be used to + endorse or promote products derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS +AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/addons/alioss/library/OSS/Http/RequestCore.php b/addons/alioss/library/OSS/Http/RequestCore.php new file mode 100755 index 0000000..0071c07 --- /dev/null +++ b/addons/alioss/library/OSS/Http/RequestCore.php @@ -0,0 +1,892 @@ +). + */ + public $request_class = 'OSS\Http\RequestCore'; + + /** + * The default class to use for HTTP Responses (defaults to ). + */ + public $response_class = 'OSS\Http\ResponseCore'; + + /** + * Default useragent string to use. + */ + public $useragent = 'RequestCore/1.4.3'; + + /** + * File to read from while streaming up. + */ + public $read_file = null; + + /** + * The resource to read from while streaming up. + */ + public $read_stream = null; + + /** + * The size of the stream to read from. + */ + public $read_stream_size = null; + + /** + * The length already read from the stream. + */ + public $read_stream_read = 0; + + /** + * File to write to while streaming down. + */ + public $write_file = null; + + /** + * The resource to write to while streaming down. + */ + public $write_stream = null; + + /** + * Stores the intended starting seek position. + */ + public $seek_position = null; + + /** + * The location of the cacert.pem file to use. + */ + public $cacert_location = false; + + /** + * The state of SSL certificate verification. + */ + public $ssl_verification = true; + + /** + * The user-defined callback function to call when a stream is read from. + */ + public $registered_streaming_read_callback = null; + + /** + * The user-defined callback function to call when a stream is written to. + */ + public $registered_streaming_write_callback = null; + + /** + * 请求超时时间, 默认是5184000秒,6天 + * + * @var int + */ + public $timeout = 5184000; + + /** + * 连接超时时间,默认是10秒 + * + * @var int + */ + public $connect_timeout = 10; + + /*%******************************************************************************************%*/ + // CONSTANTS + + /** + * GET HTTP Method + */ + const HTTP_GET = 'GET'; + + /** + * POST HTTP Method + */ + const HTTP_POST = 'POST'; + + /** + * PUT HTTP Method + */ + const HTTP_PUT = 'PUT'; + + /** + * DELETE HTTP Method + */ + const HTTP_DELETE = 'DELETE'; + + /** + * HEAD HTTP Method + */ + const HTTP_HEAD = 'HEAD'; + + + /*%******************************************************************************************%*/ + // CONSTRUCTOR/DESTRUCTOR + + /** + * Constructs a new instance of this class. + * + * @param string $url (Optional) The URL to request or service endpoint to query. + * @param string $proxy (Optional) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @param array $helpers (Optional) An associative array of classnames to use for request, and response functionality. Gets passed in automatically by the calling class. + * @return $this A reference to the current instance. + */ + public function __construct($url = null, $proxy = null, $helpers = null) + { + // Set some default values. + $this->request_url = $url; + $this->method = self::HTTP_GET; + $this->request_headers = array(); + $this->request_body = ''; + + // Set a new Request class if one was set. + if (isset($helpers['request']) && !empty($helpers['request'])) { + $this->request_class = $helpers['request']; + } + + // Set a new Request class if one was set. + if (isset($helpers['response']) && !empty($helpers['response'])) { + $this->response_class = $helpers['response']; + } + + if ($proxy) { + $this->set_proxy($proxy); + } + + return $this; + } + + /** + * Destructs the instance. Closes opened file handles. + * + * @return $this A reference to the current instance. + */ + public function __destruct() + { + if (isset($this->read_file) && isset($this->read_stream)) { + fclose($this->read_stream); + } + + if (isset($this->write_file) && isset($this->write_stream)) { + fclose($this->write_stream); + } + + return $this; + } + + + /*%******************************************************************************************%*/ + // REQUEST METHODS + + /** + * Sets the credentials to use for authentication. + * + * @param string $user (Required) The username to authenticate with. + * @param string $pass (Required) The password to authenticate with. + * @return $this A reference to the current instance. + */ + public function set_credentials($user, $pass) + { + $this->username = $user; + $this->password = $pass; + return $this; + } + + /** + * Adds a custom HTTP header to the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @param mixed $value (Required) The value to assign to the custom HTTP header. + * @return $this A reference to the current instance. + */ + public function add_header($key, $value) + { + $this->request_headers[$key] = $value; + return $this; + } + + /** + * Removes an HTTP header from the cURL request. + * + * @param string $key (Required) The custom HTTP header to set. + * @return $this A reference to the current instance. + */ + public function remove_header($key) + { + if (isset($this->request_headers[$key])) { + unset($this->request_headers[$key]); + } + return $this; + } + + /** + * Set the method type for the request. + * + * @param string $method (Required) One of the following constants: , , , , . + * @return $this A reference to the current instance. + */ + public function set_method($method) + { + $this->method = strtoupper($method); + return $this; + } + + /** + * Sets a custom useragent string for the class. + * + * @param string $ua (Required) The useragent string to use. + * @return $this A reference to the current instance. + */ + public function set_useragent($ua) + { + $this->useragent = $ua; + return $this; + } + + /** + * Set the body to send in the request. + * + * @param string $body (Required) The textual content to send along in the body of the request. + * @return $this A reference to the current instance. + */ + public function set_body($body) + { + $this->request_body = $body; + return $this; + } + + /** + * Set the URL to make the request to. + * + * @param string $url (Required) The URL to make the request to. + * @return $this A reference to the current instance. + */ + public function set_request_url($url) + { + $this->request_url = $url; + return $this; + } + + /** + * Set additional CURLOPT settings. These will merge with the default settings, and override if + * there is a duplicate. + * + * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings. + * @return $this A reference to the current instance. + */ + public function set_curlopts($curlopts) + { + $this->curlopts = $curlopts; + return $this; + } + + /** + * Sets the length in bytes to read from the stream while streaming up. + * + * @param integer $size (Required) The length in bytes to read from the stream. + * @return $this A reference to the current instance. + */ + public function set_read_stream_size($size) + { + $this->read_stream_size = $size; + + return $this; + } + + /** + * Sets the resource to read from while streaming up. Reads the stream from its current position until + * EOF or `$size` bytes have been read. If `$size` is not given it will be determined by and + * . + * + * @param resource $resource (Required) The readable resource to read from. + * @param integer $size (Optional) The size of the stream to read. + * @return $this A reference to the current instance. + */ + public function set_read_stream($resource, $size = null) + { + if (!isset($size) || $size < 0) { + $stats = fstat($resource); + + if ($stats && $stats['size'] >= 0) { + $position = ftell($resource); + + if ($position !== false && $position >= 0) { + $size = $stats['size'] - $position; + } + } + } + + $this->read_stream = $resource; + + return $this->set_read_stream_size($size); + } + + /** + * Sets the file to read from while streaming up. + * + * @param string $location (Required) The readable location to read from. + * @return $this A reference to the current instance. + */ + public function set_read_file($location) + { + $this->read_file = $location; + $read_file_handle = fopen($location, 'r'); + + return $this->set_read_stream($read_file_handle); + } + + /** + * Sets the resource to write to while streaming down. + * + * @param resource $resource (Required) The writeable resource to write to. + * @return $this A reference to the current instance. + */ + public function set_write_stream($resource) + { + $this->write_stream = $resource; + + return $this; + } + + /** + * Sets the file to write to while streaming down. + * + * @param string $location (Required) The writeable location to write to. + * @return $this A reference to the current instance. + */ + public function set_write_file($location) + { + $this->write_file = $location; + } + + /** + * Set the proxy to use for making requests. + * + * @param string $proxy (Required) The faux-url to use for proxy settings. Takes the following format: `proxy://user:pass@hostname:port` + * @return $this A reference to the current instance. + */ + public function set_proxy($proxy) + { + $proxy = parse_url($proxy); + $proxy['user'] = isset($proxy['user']) ? $proxy['user'] : null; + $proxy['pass'] = isset($proxy['pass']) ? $proxy['pass'] : null; + $proxy['port'] = isset($proxy['port']) ? $proxy['port'] : null; + $this->proxy = $proxy; + return $this; + } + + /** + * Set the intended starting seek position. + * + * @param integer $position (Required) The byte-position of the stream to begin reading from. + * @return $this A reference to the current instance. + */ + public function set_seek_position($position) + { + $this->seek_position = isset($position) ? (integer)$position : null; + + return $this; + } + + /** + * A callback function that is invoked by cURL for streaming up. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param resource $header_content (Required) The header callback result. + * @return headers from a stream. + */ + public function streaming_header_callback($curl_handle, $header_content) + { + $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + + if (isset($this->write_file) && intval($code) / 100 == 2 && !isset($this->write_file_handle)) { + $this->write_file_handle = fopen($this->write_file, 'w'); + $this->set_write_stream($this->write_file_handle); + } + + $this->response_raw_headers .= $header_content; + return strlen($header_content); + } + + + /** + * Register a callback function to execute whenever a data stream is read from using + * . + * + * The user-defined callback function should accept three arguments: + * + *
    + *
  • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
  • + *
  • $file_handle - resource - Required - The file handle resource that represents the file on the local file system.
  • + *
  • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
  • + *
+ * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
    + *
  • The name of a global function to execute, passed as a string.
  • + *
  • A method to execute, passed as array('ClassName', 'MethodName').
  • + *
  • An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance. + */ + public function register_streaming_read_callback($callback) + { + $this->registered_streaming_read_callback = $callback; + + return $this; + } + + /** + * Register a callback function to execute whenever a data stream is written to using + * . + * + * The user-defined callback function should accept two arguments: + * + *
    + *
  • $curl_handle - resource - Required - The cURL handle resource that represents the in-progress transfer.
  • + *
  • $length - integer - Required - The length in kilobytes of the data chunk that was transferred.
  • + *
+ * + * @param string|array|function $callback (Required) The callback function is called by , so you can pass the following values:
    + *
  • The name of a global function to execute, passed as a string.
  • + *
  • A method to execute, passed as array('ClassName', 'MethodName').
  • + *
  • An anonymous function (PHP 5.3+).
+ * @return $this A reference to the current instance. + */ + public function register_streaming_write_callback($callback) + { + $this->registered_streaming_write_callback = $callback; + + return $this; + } + + + /*%******************************************************************************************%*/ + // PREPARE, SEND, AND PROCESS REQUEST + + /** + * A callback function that is invoked by cURL for streaming up. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param resource $file_handle (Required) The open file handle resource. + * @param integer $length (Required) The maximum number of bytes to read. + * @return binary Binary data from a stream. + */ + public function streaming_read_callback($curl_handle, $file_handle, $length) + { + // Once we've sent as much as we're supposed to send... + if ($this->read_stream_read >= $this->read_stream_size) { + // Send EOF + return ''; + } + + // If we're at the beginning of an upload and need to seek... + if ($this->read_stream_read == 0 && isset($this->seek_position) && $this->seek_position !== ftell($this->read_stream)) { + if (fseek($this->read_stream, $this->seek_position) !== 0) { + throw new RequestCore_Exception('The stream does not support seeking and is either not at the requested position or the position is unknown.'); + } + } + + $read = fread($this->read_stream, min($this->read_stream_size - $this->read_stream_read, $length)); // Remaining upload data or cURL's requested chunk size + $this->read_stream_read += strlen($read); + + $out = $read === false ? '' : $read; + + // Execute callback function + if ($this->registered_streaming_read_callback) { + call_user_func($this->registered_streaming_read_callback, $curl_handle, $file_handle, $out); + } + + return $out; + } + + /** + * A callback function that is invoked by cURL for streaming down. + * + * @param resource $curl_handle (Required) The cURL handle for the request. + * @param binary $data (Required) The data to write. + * @return integer The number of bytes written. + */ + public function streaming_write_callback($curl_handle, $data) + { + $code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + + if (intval($code) / 100 != 2) { + $this->response_error_body .= $data; + return strlen($data); + } + + $length = strlen($data); + $written_total = 0; + $written_last = 0; + + while ($written_total < $length) { + $written_last = fwrite($this->write_stream, substr($data, $written_total)); + + if ($written_last === false) { + return $written_total; + } + + $written_total += $written_last; + } + + // Execute callback function + if ($this->registered_streaming_write_callback) { + call_user_func($this->registered_streaming_write_callback, $curl_handle, $written_total); + } + + return $written_total; + } + + /** + * Prepares and adds the details of the cURL request. This can be passed along to a + * function. + * + * @return resource The handle for the cURL object. + * + */ + public function prep_request() + { + $curl_handle = curl_init(); + + // Set default options. + curl_setopt($curl_handle, CURLOPT_URL, $this->request_url); + curl_setopt($curl_handle, CURLOPT_FILETIME, true); + curl_setopt($curl_handle, CURLOPT_FRESH_CONNECT, false); + // curl_setopt($curl_handle, CURLOPT_CLOSEPOLICY, CURLCLOSEPOLICY_LEAST_RECENTLY_USED); + curl_setopt($curl_handle, CURLOPT_MAXREDIRS, 5); + curl_setopt($curl_handle, CURLOPT_HEADER, true); + curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl_handle, CURLOPT_TIMEOUT, $this->timeout); + curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, $this->connect_timeout); + curl_setopt($curl_handle, CURLOPT_NOSIGNAL, true); + curl_setopt($curl_handle, CURLOPT_REFERER, $this->request_url); + curl_setopt($curl_handle, CURLOPT_USERAGENT, $this->useragent); + curl_setopt($curl_handle, CURLOPT_HEADERFUNCTION, array($this, 'streaming_header_callback')); + curl_setopt($curl_handle, CURLOPT_READFUNCTION, array($this, 'streaming_read_callback')); + + // Verification of the SSL cert + if ($this->ssl_verification) { + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, 2); + } else { + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl_handle, CURLOPT_SSL_VERIFYHOST, false); + } + + // chmod the file as 0755 + if ($this->cacert_location === true) { + curl_setopt($curl_handle, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem'); + } elseif (is_string($this->cacert_location)) { + curl_setopt($curl_handle, CURLOPT_CAINFO, $this->cacert_location); + } + + // Debug mode + if ($this->debug_mode) { + curl_setopt($curl_handle, CURLOPT_VERBOSE, true); + } + + // Handle open_basedir & safe mode + if (!ini_get('safe_mode') && !ini_get('open_basedir')) { + curl_setopt($curl_handle, CURLOPT_FOLLOWLOCATION, true); + } + + // Enable a proxy connection if requested. + if ($this->proxy) { + $host = $this->proxy['host']; + $host .= ($this->proxy['port']) ? ':' . $this->proxy['port'] : ''; + curl_setopt($curl_handle, CURLOPT_PROXY, $host); + + if (isset($this->proxy['user']) && isset($this->proxy['pass'])) { + curl_setopt($curl_handle, CURLOPT_PROXYUSERPWD, $this->proxy['user'] . ':' . $this->proxy['pass']); + } + } + + // Set credentials for HTTP Basic/Digest Authentication. + if ($this->username && $this->password) { + curl_setopt($curl_handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_setopt($curl_handle, CURLOPT_USERPWD, $this->username . ':' . $this->password); + } + + // Handle the encoding if we can. + if (extension_loaded('zlib')) { + curl_setopt($curl_handle, CURLOPT_ENCODING, ''); + } + + // Process custom headers + if (isset($this->request_headers) && count($this->request_headers)) { + $temp_headers = array(); + + foreach ($this->request_headers as $k => $v) { + $temp_headers[] = $k . ': ' . $v; + } + + curl_setopt($curl_handle, CURLOPT_HTTPHEADER, $temp_headers); + } + + switch ($this->method) { + case self::HTTP_PUT: + //unset($this->read_stream); + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'PUT'); + if (isset($this->read_stream)) { + if (!isset($this->read_stream_size) || $this->read_stream_size < 0) { + throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.'); + } + curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size); + curl_setopt($curl_handle, CURLOPT_UPLOAD, true); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + + case self::HTTP_POST: + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, 'POST'); + if (isset($this->read_stream)) { + if (!isset($this->read_stream_size) || $this->read_stream_size < 0) { + throw new RequestCore_Exception('The stream size for the streaming upload cannot be determined.'); + } + curl_setopt($curl_handle, CURLOPT_INFILESIZE, $this->read_stream_size); + curl_setopt($curl_handle, CURLOPT_UPLOAD, true); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + + case self::HTTP_HEAD: + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, self::HTTP_HEAD); + curl_setopt($curl_handle, CURLOPT_NOBODY, 1); + break; + + default: // Assumed GET + curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, $this->method); + if (isset($this->write_stream) || isset($this->write_file)) { + curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, array($this, 'streaming_write_callback')); + curl_setopt($curl_handle, CURLOPT_HEADER, false); + } else { + curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $this->request_body); + } + break; + } + + // Merge in the CURLOPTs + if (isset($this->curlopts) && sizeof($this->curlopts) > 0) { + foreach ($this->curlopts as $k => $v) { + curl_setopt($curl_handle, $k, $v); + } + } + + return $curl_handle; + } + + /** + * Take the post-processed cURL data and break it down into useful header/body/info chunks. Uses the + * data stored in the `curl_handle` and `response` properties unless replacement data is passed in via + * parameters. + * + * @param resource $curl_handle (Optional) The reference to the already executed cURL request. + * @param string $response (Optional) The actual response content itself that needs to be parsed. + * @return ResponseCore A object containing a parsed HTTP response. + */ + public function process_response($curl_handle = null, $response = null) + { + // Accept a custom one if it's passed. + if ($curl_handle && $response) { + $this->response = $response; + } + + // As long as this came back as a valid resource... + if (is_resource($curl_handle) || (is_object($curl_handle) && in_array(get_class($curl_handle), array('CurlHandle', 'Swoole\Curl\Handler', 'Swoole\Coroutine\Curl\Handle'), true))) { + // Determine what's what. + $header_size = curl_getinfo($curl_handle, CURLINFO_HEADER_SIZE); + $this->response_headers = substr($this->response, 0, $header_size); + $this->response_body = substr($this->response, $header_size); + $this->response_code = curl_getinfo($curl_handle, CURLINFO_HTTP_CODE); + $this->response_info = curl_getinfo($curl_handle); + + if (intval($this->response_code) / 100 != 2 && isset($this->write_file)) { + $this->response_headers = $this->response_raw_headers; + $this->response_body = $this->response_error_body; + } + + // Parse out the headers + $this->response_headers = explode("\r\n\r\n", trim($this->response_headers)); + $this->response_headers = array_pop($this->response_headers); + $this->response_headers = explode("\r\n", $this->response_headers); + array_shift($this->response_headers); + + // Loop through and split up the headers. + $header_assoc = array(); + foreach ($this->response_headers as $header) { + $kv = explode(': ', $header); + $header_assoc[strtolower($kv[0])] = isset($kv[1]) ? $kv[1] : ''; + } + + // Reset the headers to the appropriate property. + $this->response_headers = $header_assoc; + $this->response_headers['info'] = $this->response_info; + $this->response_headers['info']['method'] = $this->method; + + if ($curl_handle && $response) { + return new ResponseCore($this->response_headers, $this->response_body, $this->response_code); + } + } + + // Return false + return false; + } + + /** + * Sends the request, calling necessary utility functions to update built-in properties. + * + * @param boolean $parse (Optional) Whether to parse the response with ResponseCore or not. + * @return string The resulting unparsed data from the request. + */ + public function send_request($parse = false) + { + set_time_limit(0); + + $curl_handle = $this->prep_request(); + $this->response = curl_exec($curl_handle); + + if ($this->response === false) { + throw new RequestCore_Exception('cURL resource: ' . (string)$curl_handle . '; cURL error: ' . curl_error($curl_handle) . ' (' . curl_errno($curl_handle) . ')'); + } + + $parsed_response = $this->process_response($curl_handle, $this->response); + + curl_close($curl_handle); + + if ($parse) { + return $parsed_response; + } + + return $this->response; + } + + /*%******************************************************************************************%*/ + // RESPONSE METHODS + + /** + * Get the HTTP response headers from the request. + * + * @param string $header (Optional) A specific header value to return. Defaults to all headers. + * @return string|array All or selected header values. + */ + public function get_response_header($header = null) + { + if ($header) { + return $this->response_headers[strtolower($header)]; + } + return $this->response_headers; + } + + /** + * Get the HTTP response body from the request. + * + * @return string The response body. + */ + public function get_response_body() + { + return $this->response_body; + } + + /** + * Get the HTTP response code from the request. + * + * @return string The HTTP response code. + */ + public function get_response_code() + { + return $this->response_code; + } +} diff --git a/addons/alioss/library/OSS/Http/RequestCore_Exception.php b/addons/alioss/library/OSS/Http/RequestCore_Exception.php new file mode 100755 index 0000000..cb4e83c --- /dev/null +++ b/addons/alioss/library/OSS/Http/RequestCore_Exception.php @@ -0,0 +1,8 @@ +). + * @param string $body (Required) XML-formatted response from AWS. + * @param integer $status (Optional) HTTP response status code from the request. + * @return Mixed Contains an `header` property (HTTP headers as an associative array), a or `body` property, and an `status` code. + */ + public function __construct($header, $body, $status = null) + { + $this->header = $header; + $this->body = $body; + $this->status = $status; + + return $this; + } + + /** + * Did we receive the status code we expected? + * + * @param integer|array $codes (Optional) The status code(s) to expect. Pass an for a single acceptable value, or an of integers for multiple acceptable values. + * @return boolean Whether we received the expected status code or not. + */ + public function isOK($codes = array(200, 201, 204, 206)) + { + if (is_array($codes)) { + return in_array($this->status, $codes); + } + + return $this->status === $codes; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/BucketInfo.php b/addons/alioss/library/OSS/Model/BucketInfo.php new file mode 100755 index 0000000..9b89674 --- /dev/null +++ b/addons/alioss/library/OSS/Model/BucketInfo.php @@ -0,0 +1,78 @@ +location = $location; + $this->name = $name; + $this->createDate = $createDate; + } + + /** + * 得到bucket所在的region + * + * @return string + */ + public function getLocation() + { + return $this->location; + } + + /** + * 得到bucket的名称 + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 得到bucket的创建时间 + * + * @return string + */ + public function getCreateDate() + { + return $this->createDate; + } + + /** + * bucket所在的region + * + * @var string + */ + private $location; + /** + * bucket的名称 + * + * @var string + */ + private $name; + + /** + * bucket的创建事件 + * + * @var string + */ + private $createDate; + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/BucketListInfo.php b/addons/alioss/library/OSS/Model/BucketListInfo.php new file mode 100755 index 0000000..910717f --- /dev/null +++ b/addons/alioss/library/OSS/Model/BucketListInfo.php @@ -0,0 +1,39 @@ +bucketList = $bucketList; + } + + /** + * 得到BucketInfo列表 + * + * @return BucketInfo[] + */ + public function getBucketList() + { + return $this->bucketList; + } + + /** + * BucketInfo信息列表 + * + * @var array + */ + private $bucketList = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/CnameConfig.php b/addons/alioss/library/OSS/Model/CnameConfig.php new file mode 100755 index 0000000..f3597d2 --- /dev/null +++ b/addons/alioss/library/OSS/Model/CnameConfig.php @@ -0,0 +1,99 @@ +cnameList = array(); + } + + /** + * @return array + * @example + * array(2) { + * [0]=> + * array(3) { + * ["Domain"]=> + * string(11) "www.foo.com" + * ["Status"]=> + * string(7) "enabled" + * ["LastModified"]=> + * string(8) "20150101" + * } + * [1]=> + * array(3) { + * ["Domain"]=> + * string(7) "bar.com" + * ["Status"]=> + * string(8) "disabled" + * ["LastModified"]=> + * string(8) "20160101" + * } + * } + */ + public function getCnames() + { + return $this->cnameList; + } + + + public function addCname($cname) + { + if (count($this->cnameList) >= self::OSS_MAX_RULES) { + throw new OssException( + "num of cname in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES)); + } + $this->cnameList[] = array('Domain' => $cname); + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->Cname)) return; + foreach ($xml->Cname as $entry) { + $cname = array(); + foreach ($entry as $key => $value) { + $cname[strval($key)] = strval($value); + } + $this->cnameList[] = $cname; + } + } + + public function serializeToXml() + { + $strXml = << + + +EOF; + $xml = new \SimpleXMLElement($strXml); + foreach ($this->cnameList as $cname) { + $node = $xml->addChild('Cname'); + foreach ($cname as $key => $value) { + $node->addChild($key, $value); + } + } + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + const OSS_MAX_RULES = 10; + + private $cnameList = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/CorsConfig.php b/addons/alioss/library/OSS/Model/CorsConfig.php new file mode 100755 index 0000000..c44c10a --- /dev/null +++ b/addons/alioss/library/OSS/Model/CorsConfig.php @@ -0,0 +1,113 @@ +rules = array(); + } + + /** + * 得到CorsRule列表 + * + * @return CorsRule[] + */ + public function getRules() + { + return $this->rules; + } + + + /** + * 添加一条CorsRule + * + * @param CorsRule $rule + * @throws OssException + */ + public function addRule($rule) + { + if (count($this->rules) >= self::OSS_MAX_RULES) { + throw new OssException("num of rules in the config exceeds self::OSS_MAX_RULES: " . strval(self::OSS_MAX_RULES)); + } + $this->rules[] = $rule; + } + + /** + * 从xml数据中解析出CorsConfig + * + * @param string $strXml + * @throws OssException + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->CORSRule)) return; + foreach ($xml->CORSRule as $rule) { + $corsRule = new CorsRule(); + foreach ($rule as $key => $value) { + if ($key === self::OSS_CORS_ALLOWED_HEADER) { + $corsRule->addAllowedHeader(strval($value)); + } elseif ($key === self::OSS_CORS_ALLOWED_METHOD) { + $corsRule->addAllowedMethod(strval($value)); + } elseif ($key === self::OSS_CORS_ALLOWED_ORIGIN) { + $corsRule->addAllowedOrigin(strval($value)); + } elseif ($key === self::OSS_CORS_EXPOSE_HEADER) { + $corsRule->addExposeHeader(strval($value)); + } elseif ($key === self::OSS_CORS_MAX_AGE_SECONDS) { + $corsRule->setMaxAgeSeconds(strval($value)); + } + } + $this->addRule($corsRule); + } + return; + } + + /** + * 生成xml字符串 + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + foreach ($this->rules as $rule) { + $xmlRule = $xml->addChild('CORSRule'); + $rule->appendToXml($xmlRule); + } + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + const OSS_CORS_ALLOWED_ORIGIN = 'AllowedOrigin'; + const OSS_CORS_ALLOWED_METHOD = 'AllowedMethod'; + const OSS_CORS_ALLOWED_HEADER = 'AllowedHeader'; + const OSS_CORS_EXPOSE_HEADER = 'ExposeHeader'; + const OSS_CORS_MAX_AGE_SECONDS = 'MaxAgeSeconds'; + const OSS_MAX_RULES = 10; + + /** + * orsRule列表 + * + * @var CorsRule[] + */ + private $rules = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/CorsRule.php b/addons/alioss/library/OSS/Model/CorsRule.php new file mode 100755 index 0000000..2cbe1c1 --- /dev/null +++ b/addons/alioss/library/OSS/Model/CorsRule.php @@ -0,0 +1,150 @@ +allowedOrigins[] = $allowedOrigin; + } + } + + /** + * Rule中增加一条allowedMethod + * + * @param string $allowedMethod + */ + public function addAllowedMethod($allowedMethod) + { + if (!empty($allowedMethod)) { + $this->allowedMethods[] = $allowedMethod; + } + } + + /** + * Rule中增加一条allowedHeader + * + * @param string $allowedHeader + */ + public function addAllowedHeader($allowedHeader) + { + if (!empty($allowedHeader)) { + $this->allowedHeaders[] = $allowedHeader; + } + } + + /** + * Rule中增加一条exposeHeader + * + * @param string $exposeHeader + */ + public function addExposeHeader($exposeHeader) + { + if (!empty($exposeHeader)) { + $this->exposeHeaders[] = $exposeHeader; + } + } + + /** + * @return int + */ + public function getMaxAgeSeconds() + { + return $this->maxAgeSeconds; + } + + /** + * @param int $maxAgeSeconds + */ + public function setMaxAgeSeconds($maxAgeSeconds) + { + $this->maxAgeSeconds = $maxAgeSeconds; + } + + /** + * 得到AllowedHeaders列表 + * + * @return string[] + */ + public function getAllowedHeaders() + { + return $this->allowedHeaders; + } + + /** + * 得到AllowedOrigins列表 + * + * @return string[] + */ + public function getAllowedOrigins() + { + return $this->allowedOrigins; + } + + /** + * 得到AllowedMethods列表 + * + * @return string[] + */ + public function getAllowedMethods() + { + return $this->allowedMethods; + } + + /** + * 得到ExposeHeaders列表 + * + * @return string[] + */ + public function getExposeHeaders() + { + return $this->exposeHeaders; + } + + /** + * 根据提供的xmlRule, 把this按照一定的规则插入到$xmlRule中 + * + * @param \SimpleXMLElement $xmlRule + * @throws OssException + */ + public function appendToXml(&$xmlRule) + { + if (!isset($this->maxAgeSeconds)) { + throw new OssException("maxAgeSeconds is not set in the Rule"); + } + foreach ($this->allowedOrigins as $allowedOrigin) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_ORIGIN, $allowedOrigin); + } + foreach ($this->allowedMethods as $allowedMethod) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_METHOD, $allowedMethod); + } + foreach ($this->allowedHeaders as $allowedHeader) { + $xmlRule->addChild(CorsConfig::OSS_CORS_ALLOWED_HEADER, $allowedHeader); + } + foreach ($this->exposeHeaders as $exposeHeader) { + $xmlRule->addChild(CorsConfig::OSS_CORS_EXPOSE_HEADER, $exposeHeader); + } + $xmlRule->addChild(CorsConfig::OSS_CORS_MAX_AGE_SECONDS, strval($this->maxAgeSeconds)); + } + + private $allowedHeaders = array(); + private $allowedOrigins = array(); + private $allowedMethods = array(); + private $exposeHeaders = array(); + private $maxAgeSeconds = null; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/GetLiveChannelHistory.php b/addons/alioss/library/OSS/Model/GetLiveChannelHistory.php new file mode 100755 index 0000000..6643444 --- /dev/null +++ b/addons/alioss/library/OSS/Model/GetLiveChannelHistory.php @@ -0,0 +1,34 @@ +liveRecordList; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + if (isset($xml->LiveRecord)) { + foreach ($xml->LiveRecord as $record) { + $liveRecord = new LiveChannelHistory(); + $liveRecord->parseFromXmlNode($record); + $this->liveRecordList[] = $liveRecord; + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $liveRecordList = array(); +} diff --git a/addons/alioss/library/OSS/Model/GetLiveChannelInfo.php b/addons/alioss/library/OSS/Model/GetLiveChannelInfo.php new file mode 100755 index 0000000..0b5edfc --- /dev/null +++ b/addons/alioss/library/OSS/Model/GetLiveChannelInfo.php @@ -0,0 +1,68 @@ +description; + } + + public function getStatus() + { + return $this->status; + } + + public function getType() + { + return $this->type; + } + + public function getFragDuration() + { + return $this->fragDuration; + } + + public function getFragCount() + { + return $this->fragCount; + } + + public function getPlayListName() + { + return $this->playlistName; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + $this->description = strval($xml->Description); + $this->status = strval($xml->Status); + + if (isset($xml->Target)) { + foreach ($xml->Target as $target) { + $this->type = strval($target->Type); + $this->fragDuration = strval($target->FragDuration); + $this->fragCount = strval($target->FragCount); + $this->playlistName = strval($target->PlaylistName); + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $description; + private $status; + private $type; + private $fragDuration; + private $fragCount; + private $playlistName; +} diff --git a/addons/alioss/library/OSS/Model/GetLiveChannelStatus.php b/addons/alioss/library/OSS/Model/GetLiveChannelStatus.php new file mode 100755 index 0000000..2ee7a68 --- /dev/null +++ b/addons/alioss/library/OSS/Model/GetLiveChannelStatus.php @@ -0,0 +1,107 @@ +status; + } + + public function getConnectedTime() + { + return $this->connectedTime; + } + + public function getRemoteAddr() + { + return $this->remoteAddr; + } + + public function getVideoWidth() + { + return $this->videoWidth; + } + public function getVideoHeight() + { + return $this->videoHeight; + } + public function getVideoFrameRate() + { + return $this->videoFrameRate; + } + public function getVideoBandwidth() + { + return $this->videoBandwidth; + } + public function getVideoCodec() + { + return $this->videoCodec; + } + + public function getAudioBandwidth() + { + return $this->audioBandwidth; + } + public function getAudioSampleRate() + { + return $this->audioSampleRate; + } + public function getAudioCodec() + { + return $this->audioCodec; + } + + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->status = strval($xml->Status); + $this->connectedTime = strval($xml->ConnectedTime); + $this->remoteAddr = strval($xml->RemoteAddr); + + if (isset($xml->Video)) { + foreach ($xml->Video as $video) { + $this->videoWidth = intval($video->Width); + $this->videoHeight = intval($video->Height); + $this->videoFrameRate = intval($video->FrameRate); + $this->videoBandwidth = intval($video->Bandwidth); + $this->videoCodec = strval($video->Codec); + } + } + + if (isset($xml->Video)) { + foreach ($xml->Audio as $audio) { + $this->audioBandwidth = intval($audio->Bandwidth); + $this->audioSampleRate = intval($audio->SampleRate); + $this->audioCodec = strval($audio->Codec); + } + } + + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $status; + private $connectedTime; + private $remoteAddr; + + private $videoWidth; + private $videoHeight; + private $videoFrameRate; + private $videoBandwidth; + private $videoCodec; + + private $audioBandwidth; + private $audioSampleRate; + private $audioCodec; + + +} diff --git a/addons/alioss/library/OSS/Model/LifecycleAction.php b/addons/alioss/library/OSS/Model/LifecycleAction.php new file mode 100755 index 0000000..5abd825 --- /dev/null +++ b/addons/alioss/library/OSS/Model/LifecycleAction.php @@ -0,0 +1,88 @@ +action = $action; + $this->timeSpec = $timeSpec; + $this->timeValue = $timeValue; + } + + /** + * @return LifecycleAction + */ + public function getAction() + { + return $this->action; + } + + /** + * @param string $action + */ + public function setAction($action) + { + $this->action = $action; + } + + /** + * @return string + */ + public function getTimeSpec() + { + return $this->timeSpec; + } + + /** + * @param string $timeSpec + */ + public function setTimeSpec($timeSpec) + { + $this->timeSpec = $timeSpec; + } + + /** + * @return string + */ + public function getTimeValue() + { + return $this->timeValue; + } + + /** + * @param string $timeValue + */ + public function setTimeValue($timeValue) + { + $this->timeValue = $timeValue; + } + + /** + * appendToXml 把actions插入到xml中 + * + * @param \SimpleXMLElement $xmlRule + */ + public function appendToXml(&$xmlRule) + { + $xmlAction = $xmlRule->addChild($this->action); + $xmlAction->addChild($this->timeSpec, $this->timeValue); + } + + private $action; + private $timeSpec; + private $timeValue; + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/LifecycleConfig.php b/addons/alioss/library/OSS/Model/LifecycleConfig.php new file mode 100755 index 0000000..fc4f575 --- /dev/null +++ b/addons/alioss/library/OSS/Model/LifecycleConfig.php @@ -0,0 +1,107 @@ +rules = array(); + $xml = simplexml_load_string($strXml); + if (!isset($xml->Rule)) return; + $this->rules = array(); + foreach ($xml->Rule as $rule) { + $id = strval($rule->ID); + $prefix = strval($rule->Prefix); + $status = strval($rule->Status); + $actions = array(); + foreach ($rule as $key => $value) { + if ($key === 'ID' || $key === 'Prefix' || $key === 'Status') continue; + $action = $key; + $timeSpec = null; + $timeValue = null; + foreach ($value as $timeSpecKey => $timeValueValue) { + $timeSpec = $timeSpecKey; + $timeValue = strval($timeValueValue); + } + $actions[] = new LifecycleAction($action, $timeSpec, $timeValue); + } + $this->rules[] = new LifecycleRule($id, $prefix, $status, $actions); + } + return; + } + + + /** + * 生成xml字符串 + * + * @return string + */ + public function serializeToXml() + { + + $xml = new \SimpleXMLElement(''); + foreach ($this->rules as $rule) { + $xmlRule = $xml->addChild('Rule'); + $rule->appendToXml($xmlRule); + } + return $xml->asXML(); + } + + /** + * + * 添加LifecycleRule + * + * @param LifecycleRule $lifecycleRule + * @throws OssException + */ + public function addRule($lifecycleRule) + { + if (!isset($lifecycleRule)) { + throw new OssException("lifecycleRule is null"); + } + $this->rules[] = $lifecycleRule; + } + + /** + * 将配置转换成字符串,便于用户查看 + * + * @return string + */ + public function __toString() + { + return $this->serializeToXml(); + } + + /** + * 得到所有的生命周期规则 + * + * @return LifecycleRule[] + */ + public function getRules() + { + return $this->rules; + } + + /** + * @var LifecycleRule[] + */ + private $rules; +} + + diff --git a/addons/alioss/library/OSS/Model/LifecycleRule.php b/addons/alioss/library/OSS/Model/LifecycleRule.php new file mode 100755 index 0000000..ec615b9 --- /dev/null +++ b/addons/alioss/library/OSS/Model/LifecycleRule.php @@ -0,0 +1,126 @@ +id; + } + + /** + * @param string $id 规则ID + */ + public function setId($id) + { + $this->id = $id; + } + + /** + * 得到文件前缀 + * + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * 设置文件前缀 + * + * @param string $prefix 文件前缀 + */ + public function setPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * Lifecycle规则的状态 + * + * @return string + */ + public function getStatus() + { + return $this->status; + } + + /** + * 设置Lifecycle规则状态 + * + * @param string $status + */ + public function setStatus($status) + { + $this->status = $status; + } + + /** + * + * @return LifecycleAction[] + */ + public function getActions() + { + return $this->actions; + } + + /** + * @param LifecycleAction[] $actions + */ + public function setActions($actions) + { + $this->actions = $actions; + } + + + /** + * LifecycleRule constructor. + * + * @param string $id 规则ID + * @param string $prefix 文件前缀 + * @param string $status 规则状态,可选[self::LIFECYCLE_STATUS_ENABLED, self::LIFECYCLE_STATUS_DISABLED] + * @param LifecycleAction[] $actions + */ + public function __construct($id, $prefix, $status, $actions) + { + $this->id = $id; + $this->prefix = $prefix; + $this->status = $status; + $this->actions = $actions; + } + + /** + * @param \SimpleXMLElement $xmlRule + */ + public function appendToXml(&$xmlRule) + { + $xmlRule->addChild('ID', $this->id); + $xmlRule->addChild('Prefix', $this->prefix); + $xmlRule->addChild('Status', $this->status); + foreach ($this->actions as $action) { + $action->appendToXml($xmlRule); + } + } + + private $id; + private $prefix; + private $status; + private $actions = array(); + + const LIFECYCLE_STATUS_ENABLED = 'Enabled'; + const LIFECYCLE_STATUS_DISABLED = 'Disabled'; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/ListMultipartUploadInfo.php b/addons/alioss/library/OSS/Model/ListMultipartUploadInfo.php new file mode 100755 index 0000000..105d005 --- /dev/null +++ b/addons/alioss/library/OSS/Model/ListMultipartUploadInfo.php @@ -0,0 +1,134 @@ +bucket = $bucket; + $this->keyMarker = $keyMarker; + $this->uploadIdMarker = $uploadIdMarker; + $this->nextKeyMarker = $nextKeyMarker; + $this->nextUploadIdMarker = $nextUploadIdMarker; + $this->delimiter = $delimiter; + $this->prefix = $prefix; + $this->maxUploads = $maxUploads; + $this->isTruncated = $isTruncated; + $this->uploads = $uploads; + } + + /** + * 得到bucket名称 + * + * @return string + */ + public function getBucket() + { + return $this->bucket; + } + + /** + * @return string + */ + public function getKeyMarker() + { + return $this->keyMarker; + } + + /** + * + * @return string + */ + public function getUploadIdMarker() + { + return $this->uploadIdMarker; + } + + /** + * @return string + */ + public function getNextKeyMarker() + { + return $this->nextKeyMarker; + } + + /** + * @return string + */ + public function getNextUploadIdMarker() + { + return $this->nextUploadIdMarker; + } + + /** + * @return string + */ + public function getDelimiter() + { + return $this->delimiter; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return int + */ + public function getMaxUploads() + { + return $this->maxUploads; + } + + /** + * @return string + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return UploadInfo[] + */ + public function getUploads() + { + return $this->uploads; + } + + private $bucket = ""; + private $keyMarker = ""; + private $uploadIdMarker = ""; + private $nextKeyMarker = ""; + private $nextUploadIdMarker = ""; + private $delimiter = ""; + private $prefix = ""; + private $maxUploads = 0; + private $isTruncated = "false"; + private $uploads = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/ListPartsInfo.php b/addons/alioss/library/OSS/Model/ListPartsInfo.php new file mode 100755 index 0000000..f1d10ee --- /dev/null +++ b/addons/alioss/library/OSS/Model/ListPartsInfo.php @@ -0,0 +1,97 @@ +bucket = $bucket; + $this->key = $key; + $this->uploadId = $uploadId; + $this->nextPartNumberMarker = $nextPartNumberMarker; + $this->maxParts = $maxParts; + $this->isTruncated = $isTruncated; + $this->listPart = $listPart; + } + + /** + * @return string + */ + public function getBucket() + { + return $this->bucket; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getUploadId() + { + return $this->uploadId; + } + + /** + * @return int + */ + public function getNextPartNumberMarker() + { + return $this->nextPartNumberMarker; + } + + /** + * @return int + */ + public function getMaxParts() + { + return $this->maxParts; + } + + /** + * @return string + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return array + */ + public function getListPart() + { + return $this->listPart; + } + + private $bucket = ""; + private $key = ""; + private $uploadId = ""; + private $nextPartNumberMarker = 0; + private $maxParts = 0; + private $isTruncated = ""; + private $listPart = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/LiveChannelConfig.php b/addons/alioss/library/OSS/Model/LiveChannelConfig.php new file mode 100755 index 0000000..dadedc9 --- /dev/null +++ b/addons/alioss/library/OSS/Model/LiveChannelConfig.php @@ -0,0 +1,121 @@ +description = $option['description']; + } + if (isset($option['status'])) { + $this->status = $option['status']; + } + if (isset($option['type'])) { + $this->type = $option['type']; + } + if (isset($option['fragDuration'])) { + $this->fragDuration = $option['fragDuration']; + } + if (isset($option['fragCount'])) { + $this->fragCount = $option['fragCount']; + } + if (isset($option['playListName'])) { + $this->playListName = $option['playListName']; + } + } + + public function getDescription() + { + return $this->description; + } + + public function getStatus() + { + return $this->status; + } + + public function getType() + { + return $this->type; + } + + public function getFragDuration() + { + return $this->fragDuration; + } + + public function getFragCount() + { + return $this->fragCount; + } + + public function getPlayListName() + { + return $this->playListName; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->description = strval($xml->Description); + $this->status = strval($xml->Status); + $target = $xml->Target; + $this->type = strval($target->Type); + $this->fragDuration = intval($target->FragDuration); + $this->fragCount = intval($target->FragCount); + $this->playListName = strval($target->PlayListName); + } + + public function serializeToXml() + { + $strXml = << + + +EOF; + $xml = new \SimpleXMLElement($strXml); + if (isset($this->description)) { + $xml->addChild('Description', $this->description); + } + + if (isset($this->status)) { + $xml->addChild('Status', $this->status); + } + + $node = $xml->addChild('Target'); + $node->addChild('Type', $this->type); + + if (isset($this->fragDuration)) { + $node->addChild('FragDuration', $this->fragDuration); + } + + if (isset($this->fragCount)) { + $node->addChild('FragCount', $this->fragCount); + } + + if (isset($this->playListName)) { + $node->addChild('PlayListName', $this->playListName); + } + + return $xml->asXML(); + } + + public function __toString() + { + return $this->serializeToXml(); + } + + private $description; + private $status = "enabled"; + private $type; + private $fragDuration = 5; + private $fragCount = 3; + private $playListName = "playlist.m3u8"; +} diff --git a/addons/alioss/library/OSS/Model/LiveChannelHistory.php b/addons/alioss/library/OSS/Model/LiveChannelHistory.php new file mode 100755 index 0000000..1c1fd4d --- /dev/null +++ b/addons/alioss/library/OSS/Model/LiveChannelHistory.php @@ -0,0 +1,59 @@ +startTime; + } + + public function getEndTime() + { + return $this->endTime; + } + + public function getRemoteAddr() + { + return $this->remoteAddr; + } + + public function parseFromXmlNode($xml) + { + if (isset($xml->StartTime)) { + $this->startTime = strval($xml->StartTime); + } + + if (isset($xml->EndTime)) { + $this->endTime = strval($xml->EndTime); + } + + if (isset($xml->RemoteAddr)) { + $this->remoteAddr = strval($xml->RemoteAddr); + } + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->parseFromXmlNode($xml); + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $startTime; + private $endTime; + private $remoteAddr; +} diff --git a/addons/alioss/library/OSS/Model/LiveChannelInfo.php b/addons/alioss/library/OSS/Model/LiveChannelInfo.php new file mode 100755 index 0000000..c63ec54 --- /dev/null +++ b/addons/alioss/library/OSS/Model/LiveChannelInfo.php @@ -0,0 +1,107 @@ +name = $name; + $this->description = $description; + $this->publishUrls = array(); + $this->playUrls = array(); + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + + public function getPublishUrls() + { + return $this->publishUrls; + } + + public function getPlayUrls() + { + return $this->playUrls; + } + + public function getStatus() + { + return $this->status; + } + + public function getLastModified() + { + return $this->lastModified; + } + + public function getDescription() + { + return $this->description; + } + + public function setDescription($description) + { + $this->description = $description; + } + + public function parseFromXmlNode($xml) + { + if (isset($xml->Name)) { + $this->name = strval($xml->Name); + } + + if (isset($xml->Description)) { + $this->description = strval($xml->Description); + } + + if (isset($xml->Status)) { + $this->status = strval($xml->Status); + } + + if (isset($xml->LastModified)) { + $this->lastModified = strval($xml->LastModified); + } + + if (isset($xml->PublishUrls)) { + foreach ($xml->PublishUrls as $url) { + $this->publishUrls[] = strval($url->Url); + } + } + + if (isset($xml->PlayUrls)) { + foreach ($xml->PlayUrls as $url) { + $this->playUrls[] = strval($url->Url); + } + } + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + $this->parseFromXmlNode($xml); + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $name; + private $description; + private $publishUrls; + private $playUrls; + private $status; + private $lastModified; +} diff --git a/addons/alioss/library/OSS/Model/LiveChannelListInfo.php b/addons/alioss/library/OSS/Model/LiveChannelListInfo.php new file mode 100755 index 0000000..bb5093a --- /dev/null +++ b/addons/alioss/library/OSS/Model/LiveChannelListInfo.php @@ -0,0 +1,107 @@ +bucket; + } + + public function setBucketName($name) + { + $this->bucket = $name; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return string + */ + public function getMarker() + { + return $this->marker; + } + + /** + * @return int + */ + public function getMaxKeys() + { + return $this->maxKeys; + } + + /** + * @return mixed + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * @return LiveChannelInfo[] + */ + public function getChannelList() + { + return $this->channelList; + } + + /** + * @return string + */ + public function getNextMarker() + { + return $this->nextMarker; + } + + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + + $this->prefix = strval($xml->Prefix); + $this->marker = strval($xml->Marker); + $this->maxKeys = intval($xml->MaxKeys); + $this->isTruncated = (strval($xml->IsTruncated) == 'true'); + $this->nextMarker = strval($xml->NextMarker); + + if (isset($xml->LiveChannel)) { + foreach ($xml->LiveChannel as $chan) { + $channel = new LiveChannelInfo(); + $channel->parseFromXmlNode($chan); + $this->channelList[] = $channel; + } + } + } + + public function serializeToXml() + { + throw new OssException("Not implemented."); + } + + private $bucket = ''; + private $prefix = ''; + private $marker = ''; + private $nextMarker = ''; + private $maxKeys = 100; + private $isTruncated = 'false'; + private $channelList = array(); +} diff --git a/addons/alioss/library/OSS/Model/LoggingConfig.php b/addons/alioss/library/OSS/Model/LoggingConfig.php new file mode 100755 index 0000000..978421a --- /dev/null +++ b/addons/alioss/library/OSS/Model/LoggingConfig.php @@ -0,0 +1,86 @@ +targetBucket = $targetBucket; + $this->targetPrefix = $targetPrefix; + } + + /** + * @param $strXml + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (!isset($xml->LoggingEnabled)) return; + foreach ($xml->LoggingEnabled as $status) { + foreach ($status as $key => $value) { + if ($key === 'TargetBucket') { + $this->targetBucket = strval($value); + } elseif ($key === 'TargetPrefix') { + $this->targetPrefix = strval($value); + } + } + break; + } + } + + /** + * 序列化成xml字符串 + * + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + if (isset($this->targetBucket) && isset($this->targetPrefix)) { + $loggingEnabled = $xml->addChild('LoggingEnabled'); + $loggingEnabled->addChild('TargetBucket', $this->targetBucket); + $loggingEnabled->addChild('TargetPrefix', $this->targetPrefix); + } + return $xml->asXML(); + } + + /** + * @return string + */ + public function __toString() + { + return $this->serializeToXml(); + } + + /** + * @return string + */ + public function getTargetBucket() + { + return $this->targetBucket; + } + + /** + * @return string + */ + public function getTargetPrefix() + { + return $this->targetPrefix; + } + + private $targetBucket = ""; + private $targetPrefix = ""; + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/ObjectInfo.php b/addons/alioss/library/OSS/Model/ObjectInfo.php new file mode 100755 index 0000000..2ae6c99 --- /dev/null +++ b/addons/alioss/library/OSS/Model/ObjectInfo.php @@ -0,0 +1,93 @@ +key = $key; + $this->lastModified = $lastModified; + $this->eTag = $eTag; + $this->type = $type; + $this->size = $size; + $this->storageClass = $storageClass; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getLastModified() + { + return $this->lastModified; + } + + /** + * @return string + */ + public function getETag() + { + return $this->eTag; + } + + /** + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + /** + * @return string + */ + public function getStorageClass() + { + return $this->storageClass; + } + + private $key = ""; + private $lastModified = ""; + private $eTag = ""; + private $type = ""; + private $size = 0; + private $storageClass = ""; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/ObjectListInfo.php b/addons/alioss/library/OSS/Model/ObjectListInfo.php new file mode 100755 index 0000000..dbe7c7a --- /dev/null +++ b/addons/alioss/library/OSS/Model/ObjectListInfo.php @@ -0,0 +1,126 @@ +bucketName = $bucketName; + $this->prefix = $prefix; + $this->marker = $marker; + $this->nextMarker = $nextMarker; + $this->maxKeys = $maxKeys; + $this->delimiter = $delimiter; + $this->isTruncated = $isTruncated; + $this->objectList = $objectList; + $this->prefixList = $prefixList; + } + + /** + * @return string + */ + public function getBucketName() + { + return $this->bucketName; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + /** + * @return string + */ + public function getMarker() + { + return $this->marker; + } + + /** + * @return int + */ + public function getMaxKeys() + { + return $this->maxKeys; + } + + /** + * @return string + */ + public function getDelimiter() + { + return $this->delimiter; + } + + /** + * @return mixed + */ + public function getIsTruncated() + { + return $this->isTruncated; + } + + /** + * 返回ListObjects接口返回数据中的ObjectInfo列表 + * + * @return ObjectInfo[] + */ + public function getObjectList() + { + return $this->objectList; + } + + /** + * 返回ListObjects接口返回数据中的PrefixInfo列表 + * + * @return PrefixInfo[] + */ + public function getPrefixList() + { + return $this->prefixList; + } + + /** + * @return string + */ + public function getNextMarker() + { + return $this->nextMarker; + } + + private $bucketName = ""; + private $prefix = ""; + private $marker = ""; + private $nextMarker = ""; + private $maxKeys = 0; + private $delimiter = ""; + private $isTruncated = null; + private $objectList = array(); + private $prefixList = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/PartInfo.php b/addons/alioss/library/OSS/Model/PartInfo.php new file mode 100755 index 0000000..439a84d --- /dev/null +++ b/addons/alioss/library/OSS/Model/PartInfo.php @@ -0,0 +1,63 @@ +partNumber = $partNumber; + $this->lastModified = $lastModified; + $this->eTag = $eTag; + $this->size = $size; + } + + /** + * @return int + */ + public function getPartNumber() + { + return $this->partNumber; + } + + /** + * @return string + */ + public function getLastModified() + { + return $this->lastModified; + } + + /** + * @return string + */ + public function getETag() + { + return $this->eTag; + } + + /** + * @return int + */ + public function getSize() + { + return $this->size; + } + + private $partNumber = 0; + private $lastModified = ""; + private $eTag = ""; + private $size = 0; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/PrefixInfo.php b/addons/alioss/library/OSS/Model/PrefixInfo.php new file mode 100755 index 0000000..e61eac4 --- /dev/null +++ b/addons/alioss/library/OSS/Model/PrefixInfo.php @@ -0,0 +1,36 @@ +prefix = $prefix; + } + + /** + * @return string + */ + public function getPrefix() + { + return $this->prefix; + } + + private $prefix; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/RefererConfig.php b/addons/alioss/library/OSS/Model/RefererConfig.php new file mode 100755 index 0000000..1d7d975 --- /dev/null +++ b/addons/alioss/library/OSS/Model/RefererConfig.php @@ -0,0 +1,93 @@ +AllowEmptyReferer)) return; + if (!isset($xml->RefererList)) return; + $this->allowEmptyReferer = + (strval($xml->AllowEmptyReferer) === 'TRUE' || strval($xml->AllowEmptyReferer) === 'true') ? true : false; + + foreach ($xml->RefererList->Referer as $key => $refer) { + $this->refererList[] = strval($refer); + } + } + + + /** + * 把RefererConfig序列化成xml + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + if ($this->allowEmptyReferer) { + $xml->addChild('AllowEmptyReferer', 'true'); + } else { + $xml->addChild('AllowEmptyReferer', 'false'); + } + $refererList = $xml->addChild('RefererList'); + foreach ($this->refererList as $referer) { + $refererList->addChild('Referer', $referer); + } + return $xml->asXML(); + } + + /** + * @return string + */ + function __toString() + { + return $this->serializeToXml(); + } + + /** + * @param boolean $allowEmptyReferer + */ + public function setAllowEmptyReferer($allowEmptyReferer) + { + $this->allowEmptyReferer = $allowEmptyReferer; + } + + /** + * @param string $referer + */ + public function addReferer($referer) + { + $this->refererList[] = $referer; + } + + /** + * @return boolean + */ + public function isAllowEmptyReferer() + { + return $this->allowEmptyReferer; + } + + /** + * @return array + */ + public function getRefererList() + { + return $this->refererList; + } + + private $allowEmptyReferer = true; + private $refererList = array(); +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/StorageCapacityConfig.php b/addons/alioss/library/OSS/Model/StorageCapacityConfig.php new file mode 100755 index 0000000..05e6332 --- /dev/null +++ b/addons/alioss/library/OSS/Model/StorageCapacityConfig.php @@ -0,0 +1,74 @@ +storageCapacity = $storageCapacity; + } + + /** + * Not implemented + */ + public function parseFromXml($strXml) + { + throw new OssException("Not implemented."); + } + + /** + * 把StorageCapacityConfig序列化成xml + * + * @return string + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + $xml->addChild('StorageCapacity', strval($this->storageCapacity)); + return $xml->asXML(); + } + + /** + * To string + * + * @return string + */ + function __toString() + { + return $this->serializeToXml(); + } + + /** + * Set storage capacity + * + * @param int $storageCapacity + */ + public function setStorageCapacity($storageCapacity) + { + $this->storageCapacity = $storageCapacity; + } + + /** + * Get storage capacity + * + * @return int + */ + public function getStorageCapacity() + { + return $this->storageCapacity; + } + + private $storageCapacity = 0; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/UploadInfo.php b/addons/alioss/library/OSS/Model/UploadInfo.php new file mode 100755 index 0000000..8eaa363 --- /dev/null +++ b/addons/alioss/library/OSS/Model/UploadInfo.php @@ -0,0 +1,55 @@ +key = $key; + $this->uploadId = $uploadId; + $this->initiated = $initiated; + } + + /** + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string + */ + public function getUploadId() + { + return $this->uploadId; + } + + /** + * @return string + */ + public function getInitiated() + { + return $this->initiated; + } + + private $key = ""; + private $uploadId = ""; + private $initiated = ""; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/WebsiteConfig.php b/addons/alioss/library/OSS/Model/WebsiteConfig.php new file mode 100755 index 0000000..8ea08a0 --- /dev/null +++ b/addons/alioss/library/OSS/Model/WebsiteConfig.php @@ -0,0 +1,76 @@ +indexDocument = $indexDocument; + $this->errorDocument = $errorDocument; + } + + /** + * @param string $strXml + * @return null + */ + public function parseFromXml($strXml) + { + $xml = simplexml_load_string($strXml); + if (isset($xml->IndexDocument) && isset($xml->IndexDocument->Suffix)) { + $this->indexDocument = strval($xml->IndexDocument->Suffix); + } + if (isset($xml->ErrorDocument) && isset($xml->ErrorDocument->Key)) { + $this->errorDocument = strval($xml->ErrorDocument->Key); + } + } + + /** + * 把WebsiteConfig序列化成xml + * + * @return string + * @throws OssException + */ + public function serializeToXml() + { + $xml = new \SimpleXMLElement(''); + $index_document_part = $xml->addChild('IndexDocument'); + $error_document_part = $xml->addChild('ErrorDocument'); + $index_document_part->addChild('Suffix', $this->indexDocument); + $error_document_part->addChild('Key', $this->errorDocument); + return $xml->asXML(); + } + + /** + * @return string + */ + public function getIndexDocument() + { + return $this->indexDocument; + } + + /** + * @return string + */ + public function getErrorDocument() + { + return $this->errorDocument; + } + + private $indexDocument = ""; + private $errorDocument = ""; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Model/XmlConfig.php b/addons/alioss/library/OSS/Model/XmlConfig.php new file mode 100755 index 0000000..d353a22 --- /dev/null +++ b/addons/alioss/library/OSS/Model/XmlConfig.php @@ -0,0 +1,27 @@ +hostname = $this->checkEndpoint($endpoint, $isCName); + $this->accessKeyId = $accessKeyId; + $this->accessKeySecret = $accessKeySecret; + $this->securityToken = $securityToken; + $this->requestProxy = $requestProxy; + + self::checkEnv(); + } + + /** + * 列举用户所有的Bucket[GetService], Endpoint类型为cname不能进行此操作 + * + * @param array $options + * @return BucketListInfo + * @throws OssException + */ + public function listBuckets($options = null) + { + if ($this->hostType === self::OSS_HOST_TYPE_CNAME) { + throw new OssException("operation is not permitted with CName host"); + } + $this->precheckOptions($options); + $options[self::OSS_BUCKET] = ''; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new ListBucketsResult($response); + return $result->getData(); + } + + /** + * 创建bucket,默认创建的bucket的ACL是OssClient::OSS_ACL_TYPE_PRIVATE + * + * @param string $bucket + * @param string $acl + * @param array $options + * @param string $storageType + * @return null + */ + public function createBucket($bucket, $acl = self::OSS_ACL_TYPE_PRIVATE, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array(self::OSS_ACL => $acl); + if (isset($options[self::OSS_STORAGE])) { + $this->precheckStorage($options[self::OSS_STORAGE]); + $options[self::OSS_CONTENT] = OssUtil::createBucketXmlBody($options[self::OSS_STORAGE]); + unset($options[self::OSS_STORAGE]); + } + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除bucket + * 如果Bucket不为空(Bucket中有Object,或者有分块上传的碎片),则Bucket无法删除, + * 必须删除Bucket中的所有Object以及碎片后,Bucket才能成功删除。 + * + * @param string $bucket + * @param array $options + * @return null + */ + public function deleteBucket($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 判断bucket是否存在 + * + * @param string $bucket + * @return bool + * @throws OssException + */ + public function doesBucketExist($bucket) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new ExistResult($response); + return $result->getData(); + } + + /** + * 获取bucket所属的数据中心位置信息 + * + * @param string $bucket + * @param array $options + * @return string + * @throws OssException + */ + public function getBucketLocation($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'location'; + $response = $this->auth($options); + $result = new GetLocationResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Meta信息 + * + * @param string $bucket + * @param array $options 具体参考SDK文档 + * @return array + */ + public function getBucketMeta($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = '/'; + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 获取bucket的ACL配置情况 + * + * @param string $bucket + * @param array $options + * @return string + * @throws OssException + */ + public function getBucketAcl($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new AclResult($response); + return $result->getData(); + } + + /** + * 设置bucket的ACL配置情况 + * + * @param string $bucket bucket名称 + * @param string $acl 读写权限,可选值 ['private', 'public-read', 'public-read-write'] + * @param array $options 可以为空 + * @return null + * @throws OssException + */ + public function putBucketAcl($bucket, $acl, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array(self::OSS_ACL => $acl); + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取object的ACL属性 + * + * @param string $bucket + * @param string $object + * @return string + * @throws OssException + */ + public function getObjectAcl($bucket, $object) + { + $options = array(); + $this->precheckCommon($bucket, $object, $options, true); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new AclResult($response); + return $result->getData(); + } + + /** + * 设置object的ACL属性 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $acl 读写权限,可选值 ['default', 'private', 'public-read', 'public-read-write'] + * @return null + * @throws OssException + */ + public function putObjectAcl($bucket, $object, $acl) + { + $this->precheckCommon($bucket, $object, $options, true); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_HEADERS] = array(self::OSS_OBJECT_ACL => $acl); + $options[self::OSS_SUB_RESOURCE] = 'acl'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的访问日志配置情况 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @return LoggingConfig + * @throws OssException + */ + public function getBucketLogging($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $response = $this->auth($options); + $result = new GetLoggingResult($response); + return $result->getData(); + } + + /** + * 开启Bucket访问日志记录功能,只有Bucket的所有者才能更改 + * + * @param string $bucket bucket名称 + * @param string $targetBucket 日志文件存放的bucket + * @param string $targetPrefix 日志的文件前缀 + * @param array $options 可以为空 + * @return null + * @throws OssException + */ + public function putBucketLogging($bucket, $targetBucket, $targetPrefix, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $this->precheckBucket($targetBucket, 'targetbucket is not allowed empty'); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + + $loggingConfig = new LoggingConfig($targetBucket, $targetPrefix); + $options[self::OSS_CONTENT] = $loggingConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 关闭bucket访问日志记录功能 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @return null + * @throws OssException + */ + public function deleteBucketLogging($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'logging'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 将bucket设置成静态网站托管模式 + * + * @param string $bucket bucket名称 + * @param WebsiteConfig $websiteConfig + * @param array $options 可以为空 + * @return null + * @throws OssException + */ + public function putBucketWebsite($bucket, $websiteConfig, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $websiteConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取bucket的静态网站托管状态 + * + * @param string $bucket bucket名称 + * @param array $options + * @return WebsiteConfig + * @throws OssException + */ + public function getBucketWebsite($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $response = $this->auth($options); + $result = new GetWebsiteResult($response); + return $result->getData(); + } + + /** + * 关闭bucket的静态网站托管模式 + * + * @param string $bucket bucket名称 + * @param array $options + * @return null + * @throws OssException + */ + public function deleteBucketWebsite($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'website'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 在指定的bucket上设定一个跨域资源共享(CORS)的规则,如果原规则存在则覆盖原规则 + * + * @param string $bucket bucket名称 + * @param CorsConfig $corsConfig 跨域资源共享配置,具体规则参见SDK文档 + * @param array $options array + * @return null + * @throws OssException + */ + public function putBucketCors($bucket, $corsConfig, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $corsConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的CORS配置情况 + * + * @param string $bucket bucket名称 + * @param array $options 可以为空 + * @return CorsConfig + * @throws OssException + */ + public function getBucketCors($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $response = $this->auth($options); + $result = new GetCorsResult($response, __FUNCTION__); + return $result->getData(); + } + + /** + * 关闭指定Bucket对应的CORS功能并清空所有规则 + * + * @param string $bucket bucket名称 + * @param array $options + * @return null + * @throws OssException + */ + public function deleteBucketCors($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cors'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 为指定Bucket增加CNAME绑定 + * + * @param string $bucket bucket名称 + * @param string $cname + * @param array $options + * @return null + * @throws OssException + */ + public function addBucketCname($bucket, $cname, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $cnameConfig = new CnameConfig(); + $cnameConfig->addCname($cname); + $options[self::OSS_CONTENT] = $cnameConfig->serializeToXml(); + $options[self::OSS_COMP] = 'add'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取指定Bucket已绑定的CNAME列表 + * + * @param string $bucket bucket名称 + * @param array $options + * @return CnameConfig + * @throws OssException + */ + public function getBucketCname($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $response = $this->auth($options); + $result = new GetCnameResult($response); + return $result->getData(); + } + + /** + * 解除指定Bucket的CNAME绑定 + * + * @param string $bucket bucket名称 + * @param CnameConfig $cnameConfig + * @param array $options + * @return null + * @throws OssException + */ + public function deleteBucketCname($bucket, $cname, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'cname'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $cnameConfig = new CnameConfig(); + $cnameConfig->addCname($cname); + $options[self::OSS_CONTENT] = $cnameConfig->serializeToXml(); + $options[self::OSS_COMP] = 'delete'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 为指定Bucket创建LiveChannel + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param LiveChannelConfig $channelConfig + * @param array $options + * @return LiveChannelInfo + * @throws OssException + */ + public function putBucketLiveChannel($bucket, $channelName, $channelConfig, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $channelConfig->serializeToXml(); + + $response = $this->auth($options); + $result = new PutLiveChannelResult($response); + $info = $result->getData(); + $info->setName($channelName); + $info->setDescription($channelConfig->getDescription()); + + return $info; + } + + /** + * 设置LiveChannel的status + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param string channelStatus $channelStatus 为enabled或disabled + * @param array $options + * @return null + * @throws OssException + */ + public function putLiveChannelStatus($bucket, $channelName, $channelStatus, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_LIVE_CHANNEL_STATUS] = $channelStatus; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取LiveChannel信息 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @return GetLiveChannelInfo + * @throws OssException + */ + public function getLiveChannelInfo($bucket, $channelName, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + + $response = $this->auth($options); + $result = new GetLiveChannelInfoResult($response); + return $result->getData(); + } + + /** + * 获取LiveChannel状态信息 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @return GetLiveChannelStatus + * @throws OssException + */ + public function getLiveChannelStatus($bucket, $channelName, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_COMP] = 'stat'; + + $response = $this->auth($options); + $result = new GetLiveChannelStatusResult($response); + return $result->getData(); + } + + /** + *获取LiveChannel推流记录 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @return GetLiveChannelHistory + * @throws OssException + */ + public function getLiveChannelHistory($bucket, $channelName, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_COMP] = 'history'; + + $response = $this->auth($options); + $result = new GetLiveChannelHistoryResult($response); + return $result->getData(); + } + + /** + *获取指定Bucket下的live channel列表 + * + * @param string $bucket bucket名称 + * @param array $options + * @return LiveChannelListInfo + * @throws OssException + */ + public function listBucketLiveChannels($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'live'; + $options[self::OSS_QUERY_STRING] = array( + 'prefix' => isset($options['prefix']) ? $options['prefix'] : '', + 'marker' => isset($options['marker']) ? $options['marker'] : '', + 'max-keys' => isset($options['max-keys']) ? $options['max-keys'] : '', + ); + $response = $this->auth($options); + $result = new ListLiveChannelResult($response); + $list = $result->getData(); + $list->setBucketName($bucket); + + return $list; + } + + /** + * 为指定LiveChannel生成播放列表 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param string $playlistName 指定生成的点播播放列表的名称,必须以“.m3u8”结尾 + * @param array $setTime startTime和EndTime以unix时间戳格式给定,跨度不能超过一天 + * @return null + * @throws OssException + */ + public function postVodPlaylist($bucket, $channelName, $playlistName, $setTime) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $channelName . '/' . $playlistName; + $options[self::OSS_SUB_RESOURCE] = 'vod'; + $options[self::OSS_LIVE_CHANNEL_END_TIME] = $setTime['EndTime']; + $options[self::OSS_LIVE_CHANNEL_START_TIME] = $setTime['StartTime']; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除指定Bucket的LiveChannel + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param array $options + * @return null + * @throws OssException + */ + public function deleteBucketLiveChannel($bucket, $channelName, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = $channelName; + $options[self::OSS_SUB_RESOURCE] = 'live'; + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 生成带签名的推流地址 + * + * @param string $bucket bucket名称 + * @param string channelName $channelName + * @param int timeout 设置超时时间,单位为秒 + * @param array $options + * @return 推流地址 + * @throws OssException + */ + public function signRtmpUrl($bucket, $channelName, $timeout = 60, $options = null) + { + $this->precheckCommon($bucket, $channelName, $options, false); + $expires = time() + $timeout; + $proto = 'rtmp://'; + $hostname = $this->generateHostname($bucket); + $cano_params = ''; + $query_items = array(); + $params = isset($options['params']) ? $options['params'] : array(); + uksort($params, 'strnatcasecmp'); + foreach ($params as $key => $value) { + $cano_params = $cano_params . $key . ':' . $value . "\n"; + $query_items[] = rawurlencode($key) . '=' . rawurlencode($value); + } + $resource = '/' . $bucket . '/' . $channelName; + + $string_to_sign = $expires . "\n" . $cano_params . $resource; + $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->accessKeySecret, true)); + + $query_items[] = 'OSSAccessKeyId=' . rawurlencode($this->accessKeyId); + $query_items[] = 'Expires=' . rawurlencode($expires); + $query_items[] = 'Signature=' . rawurlencode($signature); + + return $proto . $hostname . '/live/' . $channelName . '?' . implode('&', $query_items); + } + + /** + * 检验跨域资源请求, 发送跨域请求之前会发送一个preflight请求(OPTIONS)并带上特定的来源域, + * HTTP方法和header信息等给OSS以决定是否发送真正的请求。 OSS可以通过putBucketCors接口 + * 来开启Bucket的CORS支持,开启CORS功能之后,OSS在收到浏览器preflight请求时会根据设定的 + * 规则评估是否允许本次请求 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $origin 请求来源域 + * @param string $request_method 表明实际请求中会使用的HTTP方法 + * @param string $request_headers 表明实际请求中会使用的除了简单头部之外的headers + * @param array $options + * @return array + * @throws OssException + * @link http://help.aliyun.com/document_detail/oss/api-reference/cors/OptionObject.html + */ + public function optionsObject($bucket, $object, $origin, $request_method, $request_headers, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_OPTIONS; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_HEADERS] = array( + self::OSS_OPTIONS_ORIGIN => $origin, + self::OSS_OPTIONS_REQUEST_HEADERS => $request_headers, + self::OSS_OPTIONS_REQUEST_METHOD => $request_method + ); + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 设置Bucket的Lifecycle配置 + * + * @param string $bucket bucket名称 + * @param LifecycleConfig $lifecycleConfig Lifecycle配置类 + * @param array $options + * @return null + * @throws OssException + */ + public function putBucketLifecycle($bucket, $lifecycleConfig, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $lifecycleConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Lifecycle配置情况 + * + * @param string $bucket bucket名称 + * @param array $options + * @return LifecycleConfig + * @throws OssException + */ + public function getBucketLifecycle($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $response = $this->auth($options); + $result = new GetLifecycleResult($response); + return $result->getData(); + } + + /** + * 删除指定Bucket的生命周期配置 + * + * @param string $bucket bucket名称 + * @param array $options + * @return null + * @throws OssException + */ + public function deleteBucketLifecycle($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'lifecycle'; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 设置一个bucket的referer访问白名单和是否允许referer字段为空的请求访问 + * Bucket Referer防盗链具体见OSS防盗链 + * + * @param string $bucket bucket名称 + * @param RefererConfig $refererConfig + * @param array $options + * @return ResponseCore + * @throws null + */ + public function putBucketReferer($bucket, $refererConfig, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'referer'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $options[self::OSS_CONTENT] = $refererConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取Bucket的Referer配置情况 + * Bucket Referer防盗链具体见OSS防盗链 + * + * @param string $bucket bucket名称 + * @param array $options + * @return RefererConfig + * @throws OssException + */ + public function getBucketReferer($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'referer'; + $response = $this->auth($options); + $result = new GetRefererResult($response); + return $result->getData(); + } + + /** + * 设置bucket的容量大小,单位GB + * 当bucket的容量大于设置的容量时,禁止继续写入 + * + * @param string $bucket bucket名称 + * @param int $storageCapacity + * @param array $options + * @return ResponseCore + * @throws null + */ + public function putBucketStorageCapacity($bucket, $storageCapacity, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'qos'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $storageCapacityConfig = new StorageCapacityConfig($storageCapacity); + $options[self::OSS_CONTENT] = $storageCapacityConfig->serializeToXml(); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取bucket的容量大小,单位GB + * + * @param string $bucket bucket名称 + * @param array $options + * @return int + * @throws OssException + */ + public function getBucketStorageCapacity($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'qos'; + $response = $this->auth($options); + $result = new GetStorageCapacityResult($response); + return $result->getData(); + } + + /** + * 获取bucket下的object列表 + * + * @param string $bucket + * @param array $options + * 其中options中的参数如下 + * $options = array( + * 'max-keys' => max-keys用于限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000。 + * 'prefix' => 限定返回的object key必须以prefix作为前缀。注意使用prefix查询时,返回的key中仍会包含prefix。 + * 'delimiter' => 是一个用于对Object名字进行分组的字符。所有名字包含指定的前缀且第一次出现delimiter字符之间的object作为一组元素 + * 'marker' => 用户设定结果从marker之后按字母排序的第一个开始返回。 + * ) + * 其中 prefix,marker用来实现分页显示效果,参数的长度必须小于256字节。 + * @return ObjectListInfo + * @throws OssException + */ + public function listObjects($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_HEADERS] = array( + self::OSS_DELIMITER => isset($options[self::OSS_DELIMITER]) ? $options[self::OSS_DELIMITER] : '/', + self::OSS_PREFIX => isset($options[self::OSS_PREFIX]) ? $options[self::OSS_PREFIX] : '', + self::OSS_MAX_KEYS => isset($options[self::OSS_MAX_KEYS]) ? $options[self::OSS_MAX_KEYS] : self::OSS_MAX_KEYS_VALUE, + self::OSS_MARKER => isset($options[self::OSS_MARKER]) ? $options[self::OSS_MARKER] : '', + ); + $query = isset($options[self::OSS_QUERY_STRING]) ? $options[self::OSS_QUERY_STRING] : array(); + $options[self::OSS_QUERY_STRING] = array_merge( + $query, + array(self::OSS_ENCODING_TYPE => self::OSS_ENCODING_TYPE_URL) + ); + + $response = $this->auth($options); + $result = new ListObjectsResult($response); + return $result->getData(); + } + + /** + * 创建虚拟目录 (本函数会在object名称后增加'/', 所以创建目录的object名称不需要'/'结尾,否则,目录名称会变成'//') + * + * 暂不开放此接口 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return null + */ + public function createObjectDir($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object . '/'; + $options[self::OSS_CONTENT_LENGTH] = array(self::OSS_CONTENT_LENGTH => 0); + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 上传内存中的内容 + * + * @param string $bucket bucket名称 + * @param string $object objcet名称 + * @param string $content 上传的内容 + * @param array $options + * @return null + */ + public function putObject($bucket, $object, $content, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + + $options[self::OSS_CONTENT] = $content; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $object; + + if (!isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + } else { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5($content, true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + $response = $this->auth($options); + + if (isset($options[self::OSS_CALLBACK]) && !empty($options[self::OSS_CALLBACK])) { + $result = new CallbackResult($response); + } else { + $result = new PutSetDeleteResult($response); + } + + return $result->getData(); + } + + /** + * 创建symlink + * @param string $bucket bucket名称 + * @param string $symlink symlink名称 + * @param string $targetObject 目标object名称 + * @param array $options + * @return null + */ + public function putSymlink($bucket, $symlink, $targetObject, $options = null) + { + $this->precheckCommon($bucket, $symlink, $options); + + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $symlink; + $options[self::OSS_SUB_RESOURCE] = self::OSS_SYMLINK; + $options[self::OSS_HEADERS][self::OSS_SYMLINK_TARGET] = rawurlencode($targetObject); + + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取symlink + * @param string $bucket bucket名称 + * @param string $symlink symlink名称 + * @return null + */ + public function getSymlink($bucket, $symlink) + { + $this->precheckCommon($bucket, $symlink, $options); + + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $symlink; + $options[self::OSS_SUB_RESOURCE] = self::OSS_SYMLINK; + + $response = $this->auth($options); + $result = new SymlinkResult($response); + return $result->getData(); + } + + /** + * 上传本地文件 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 本地文件路径 + * @param array $options + * @return null + * @throws OssException + */ + public function uploadFile($bucket, $object, $file, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + OssUtil::throwOssExceptionWithMessageIfEmpty($file, "file path is invalid"); + $file = OssUtil::encodePath($file); + if (!file_exists($file)) { + throw new OssException($file . " file does not exist"); + } + $options[self::OSS_FILE_UPLOAD] = $file; + $file_size = filesize($options[self::OSS_FILE_UPLOAD]); + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5_file($options[self::OSS_FILE_UPLOAD], true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $file); + } + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_CONTENT_LENGTH] = $file_size; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 追加上传内存中的内容 + * + * @param string $bucket bucket名称 + * @param string $object objcet名称 + * @param string $content 本次追加上传的内容 + * @param array $options + * @return int next append position + * @throws OssException + */ + public function appendObject($bucket, $object, $content, $position, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + + $options[self::OSS_CONTENT] = $content; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'append'; + $options[self::OSS_POSITION] = strval($position); + + if (!isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + } else { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5($content, true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + $response = $this->auth($options); + $result = new AppendResult($response); + return $result->getData(); + } + + /** + * 追加上传本地文件 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 追加上传的本地文件路径 + * @param array $options + * @return int next append position + * @throws OssException + */ + public function appendFile($bucket, $object, $file, $position, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + + OssUtil::throwOssExceptionWithMessageIfEmpty($file, "file path is invalid"); + $file = OssUtil::encodePath($file); + if (!file_exists($file)) { + throw new OssException($file . " file does not exist"); + } + $options[self::OSS_FILE_UPLOAD] = $file; + $file_size = filesize($options[self::OSS_FILE_UPLOAD]); + $is_check_md5 = $this->isCheckMD5($options); + if ($is_check_md5) { + $content_md5 = base64_encode(md5_file($options[self::OSS_FILE_UPLOAD], true)); + $options[self::OSS_CONTENT_MD5] = $content_md5; + } + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $file); + } + + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_CONTENT_LENGTH] = $file_size; + $options[self::OSS_SUB_RESOURCE] = 'append'; + $options[self::OSS_POSITION] = strval($position); + + $response = $this->auth($options); + $result = new AppendResult($response); + return $result->getData(); + } + + /** + * 拷贝一个在OSS上已经存在的object成另外一个object + * + * @param string $fromBucket 源bucket名称 + * @param string $fromObject 源object名称 + * @param string $toBucket 目标bucket名称 + * @param string $toObject 目标object名称 + * @param array $options + * @return null + * @throws OssException + */ + public function copyObject($fromBucket, $fromObject, $toBucket, $toObject, $options = null) + { + $this->precheckCommon($fromBucket, $fromObject, $options); + $this->precheckCommon($toBucket, $toObject, $options); + $options[self::OSS_BUCKET] = $toBucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_OBJECT] = $toObject; + if (isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE] = '/' . $fromBucket . '/' . $fromObject; + } else { + $options[self::OSS_HEADERS] = array(self::OSS_OBJECT_COPY_SOURCE => '/' . $fromBucket . '/' . $fromObject); + } + $response = $this->auth($options); + $result = new CopyObjectResult($response); + return $result->getData(); + } + + /** + * 获取Object的Meta信息 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $options 具体参考SDK文档 + * @return array + */ + public function getObjectMeta($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new HeaderResult($response); + return $result->getData(); + } + + /** + * 删除某个Object + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return null + */ + public function deleteObject($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 删除同一个Bucket中的多个Object + * + * @param string $bucket bucket名称 + * @param array $objects object列表 + * @param array $options + * @return ResponseCore + * @throws null + */ + public function deleteObjects($bucket, $objects, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + if (!is_array($objects) || !$objects) { + throw new OssException('objects must be array'); + } + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'delete'; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + $quiet = 'false'; + if (isset($options['quiet'])) { + if (is_bool($options['quiet'])) { //Boolean + $quiet = $options['quiet'] ? 'true' : 'false'; + } elseif (is_string($options['quiet'])) { // string + $quiet = ($options['quiet'] === 'true') ? 'true' : 'false'; + } + } + $xmlBody = OssUtil::createDeleteObjectsXmlBody($objects, $quiet); + $options[self::OSS_CONTENT] = $xmlBody; + $response = $this->auth($options); + $result = new DeleteObjectsResult($response); + return $result->getData(); + } + + /** + * 获得Object内容 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options 该参数中必须设置ALIOSS::OSS_FILE_DOWNLOAD,ALIOSS::OSS_RANGE可选,可以根据实际情况设置;如果不设置,默认会下载全部内容 + * @return string + */ + public function getObject($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_OBJECT] = $object; + if (isset($options[self::OSS_LAST_MODIFIED])) { + $options[self::OSS_HEADERS][self::OSS_IF_MODIFIED_SINCE] = $options[self::OSS_LAST_MODIFIED]; + unset($options[self::OSS_LAST_MODIFIED]); + } + if (isset($options[self::OSS_ETAG])) { + $options[self::OSS_HEADERS][self::OSS_IF_NONE_MATCH] = $options[self::OSS_ETAG]; + unset($options[self::OSS_ETAG]); + } + if (isset($options[self::OSS_RANGE])) { + $range = $options[self::OSS_RANGE]; + $options[self::OSS_HEADERS][self::OSS_RANGE] = "bytes=$range"; + unset($options[self::OSS_RANGE]); + } + $response = $this->auth($options); + $result = new BodyResult($response); + return $result->getData(); + } + + /** + * 检测Object是否存在 + * 通过获取Object的Meta信息来判断Object是否存在, 用户需要自行解析ResponseCore判断object是否存在 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param array $options + * @return bool + */ + public function doesObjectExist($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_HEAD; + $options[self::OSS_OBJECT] = $object; + $response = $this->auth($options); + $result = new ExistResult($response); + return $result->getData(); + } + + /** + * 针对Archive类型的Object读取 + * 需要使用Restore操作让服务端执行解冻任务 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @return null + * @throws OssException + */ + public function restoreObject($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = self::OSS_RESTORE; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 获取分片大小,根据用户提供的part_size,重新计算一个更合理的partsize + * + * @param int $partSize + * @return int + */ + private function computePartSize($partSize) + { + $partSize = (integer)$partSize; + if ($partSize <= self::OSS_MIN_PART_SIZE) { + $partSize = self::OSS_MIN_PART_SIZE; + } elseif ($partSize > self::OSS_MAX_PART_SIZE) { + $partSize = self::OSS_MAX_PART_SIZE; + } + return $partSize; + } + + /** + * 计算文件可以分成多少个part,以及每个part的长度以及起始位置 + * 方法必须在 中调用 + * + * @param integer $file_size 文件大小 + * @param integer $partSize part大小,默认5M + * @return array An array 包含 key-value 键值对. Key 为 `seekTo` 和 `length`. + */ + public function generateMultiuploadParts($file_size, $partSize = 5242880) + { + $i = 0; + $size_count = $file_size; + $values = array(); + $partSize = $this->computePartSize($partSize); + while ($size_count > 0) { + $size_count -= $partSize; + $values[] = array( + self::OSS_SEEK_TO => ($partSize * $i), + self::OSS_LENGTH => (($size_count > 0) ? $partSize : ($size_count + $partSize)), + ); + $i++; + } + return $values; + } + + /** + * 初始化multi-part upload + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param array $options Key-Value数组 + * @return string 返回uploadid + * @throws OssException + */ + public function initiateMultipartUpload($bucket, $object, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_SUB_RESOURCE] = 'uploads'; + $options[self::OSS_CONTENT] = ''; + + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object); + } + if (!isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS] = array(); + } + $response = $this->auth($options); + $result = new InitiateMultipartUploadResult($response); + return $result->getData(); + } + + /** + * 分片上传的块上传接口 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId + * @param array $options Key-Value数组 + * @return string eTag + * @throws OssException + */ + public function uploadPart($bucket, $object, $uploadId, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $this->precheckParam($options, self::OSS_FILE_UPLOAD, __FUNCTION__); + $this->precheckParam($options, self::OSS_PART_NUM, __FUNCTION__); + + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + + if (isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + } + $response = $this->auth($options); + $result = new UploadPartResult($response); + return $result->getData(); + } + + /** + * 获取已成功上传的part + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $options Key-Value数组 + * @return ListPartsInfo + * @throws OssException + */ + public function listParts($bucket, $object, $uploadId, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $options[self::OSS_QUERY_STRING] = array(); + foreach (array('max-parts', 'part-number-marker') as $param) { + if (isset($options[$param])) { + $options[self::OSS_QUERY_STRING][$param] = $options[$param]; + unset($options[$param]); + } + } + $response = $this->auth($options); + $result = new ListPartsResult($response); + return $result->getData(); + } + + /** + * 中止进行一半的分片上传操作 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function abortMultipartUpload($bucket, $object, $uploadId, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_DELETE; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $response = $this->auth($options); + $result = new PutSetDeleteResult($response); + return $result->getData(); + } + + /** + * 在将所有数据Part都上传完成后,调用此接口完成本次分块上传 + * + * @param string $bucket Bucket名称 + * @param string $object Object名称 + * @param string $uploadId uploadId + * @param array $listParts array( array("PartNumber"=> int, "ETag"=>string)) + * @param array $options Key-Value数组 + * @return PutSetDeleteResult + * @throws OssException + */ + public function completeMultipartUpload($bucket, $object, $uploadId, $listParts, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + $options[self::OSS_METHOD] = self::OSS_HTTP_POST; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_UPLOAD_ID] = $uploadId; + $options[self::OSS_CONTENT_TYPE] = 'application/xml'; + if (!is_array($listParts)) { + throw new OssException("listParts must be array type"); + } + $options[self::OSS_CONTENT] = OssUtil::createCompleteMultipartUploadXmlBody($listParts); + $response = $this->auth($options); + if (isset($options[self::OSS_CALLBACK]) && !empty($options[self::OSS_CALLBACK])) { + $result = new CallbackResult($response); + } else { + $result = new PutSetDeleteResult($response); + } + return $result->getData(); + } + + /** + * 罗列出所有执行中的Multipart Upload事件,即已经被初始化的Multipart Upload但是未被 + * Complete或者Abort的Multipart Upload事件 + * + * @param string $bucket bucket + * @param array $options 关联数组 + * @return ListMultipartUploadInfo + * @throws OssException + */ + public function listMultipartUploads($bucket, $options = null) + { + $this->precheckCommon($bucket, null, $options, false); + $options[self::OSS_METHOD] = self::OSS_HTTP_GET; + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = '/'; + $options[self::OSS_SUB_RESOURCE] = 'uploads'; + + foreach (array('delimiter', 'key-marker', 'max-uploads', 'prefix', 'upload-id-marker') as $param) { + if (isset($options[$param])) { + $options[self::OSS_QUERY_STRING][$param] = $options[$param]; + unset($options[$param]); + } + } + $query = isset($options[self::OSS_QUERY_STRING]) ? $options[self::OSS_QUERY_STRING] : array(); + $options[self::OSS_QUERY_STRING] = array_merge( + $query, + array(self::OSS_ENCODING_TYPE => self::OSS_ENCODING_TYPE_URL) + ); + + $response = $this->auth($options); + $result = new ListMultipartUploadResult($response); + return $result->getData(); + } + + /** + * 从一个已存在的Object中拷贝数据来上传一个Part + * + * @param string $fromBucket 源bucket名称 + * @param string $fromObject 源object名称 + * @param string $toBucket 目标bucket名称 + * @param string $toObject 目标object名称 + * @param int $partNumber 分块上传的块id + * @param string $uploadId 初始化multipart upload返回的uploadid + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function uploadPartCopy($fromBucket, $fromObject, $toBucket, $toObject, $partNumber, $uploadId, $options = null) + { + $this->precheckCommon($fromBucket, $fromObject, $options); + $this->precheckCommon($toBucket, $toObject, $options); + + //如果没有设置$options['isFullCopy'],则需要强制判断copy的起止位置 + $start_range = "0"; + if (isset($options['start'])) { + $start_range = $options['start']; + } + $end_range = ""; + if (isset($options['end'])) { + $end_range = $options['end']; + } + $options[self::OSS_METHOD] = self::OSS_HTTP_PUT; + $options[self::OSS_BUCKET] = $toBucket; + $options[self::OSS_OBJECT] = $toObject; + $options[self::OSS_PART_NUM] = $partNumber; + $options[self::OSS_UPLOAD_ID] = $uploadId; + + if (!isset($options[self::OSS_HEADERS])) { + $options[self::OSS_HEADERS] = array(); + } + + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE] = '/' . $fromBucket . '/' . $fromObject; + $options[self::OSS_HEADERS][self::OSS_OBJECT_COPY_SOURCE_RANGE] = "bytes=" . $start_range . "-" . $end_range; + $response = $this->auth($options); + $result = new UploadPartResult($response); + return $result->getData(); + } + + /** + * multipart上传统一封装,从初始化到完成multipart,以及出错后中止动作 + * + * @param string $bucket bucket名称 + * @param string $object object名称 + * @param string $file 需要上传的本地文件的路径 + * @param array $options Key-Value数组 + * @return null + * @throws OssException + */ + public function multiuploadFile($bucket, $object, $file, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + if (isset($options[self::OSS_LENGTH])) { + $options[self::OSS_CONTENT_LENGTH] = $options[self::OSS_LENGTH]; + unset($options[self::OSS_LENGTH]); + } + if (empty($file)) { + throw new OssException("parameter invalid, file is empty"); + } + $uploadFile = OssUtil::encodePath($file); + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = $this->getMimeType($object, $uploadFile); + } + + $upload_position = isset($options[self::OSS_SEEK_TO]) ? (integer)$options[self::OSS_SEEK_TO] : 0; + + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $upload_file_size = (integer)$options[self::OSS_CONTENT_LENGTH]; + } else { + $upload_file_size = filesize($uploadFile); + if ($upload_file_size !== false) { + $upload_file_size -= $upload_position; + } + } + + if ($upload_position === false || !isset($upload_file_size) || $upload_file_size === false || $upload_file_size < 0) { + throw new OssException('The size of `fileUpload` cannot be determined in ' . __FUNCTION__ . '().'); + } + // 处理partSize + if (isset($options[self::OSS_PART_SIZE])) { + $options[self::OSS_PART_SIZE] = $this->computePartSize($options[self::OSS_PART_SIZE]); + } else { + $options[self::OSS_PART_SIZE] = self::OSS_MID_PART_SIZE; + } + + $is_check_md5 = $this->isCheckMD5($options); + // 如果上传的文件小于partSize,则直接使用普通方式上传 + if ($upload_file_size < $options[self::OSS_PART_SIZE] && !isset($options[self::OSS_UPLOAD_ID])) { + return $this->uploadFile($bucket, $object, $uploadFile, $options); + } + + // 初始化multipart + if (isset($options[self::OSS_UPLOAD_ID])) { + $uploadId = $options[self::OSS_UPLOAD_ID]; + } else { + // 初始化 + $uploadId = $this->initiateMultipartUpload($bucket, $object, $options); + } + + // 获取的分片 + $pieces = $this->generateMultiuploadParts($upload_file_size, (integer)$options[self::OSS_PART_SIZE]); + $response_upload_part = array(); + foreach ($pieces as $i => $piece) { + $from_pos = $upload_position + (integer)$piece[self::OSS_SEEK_TO]; + $to_pos = (integer)$piece[self::OSS_LENGTH] + $from_pos - 1; + $up_options = array( + self::OSS_FILE_UPLOAD => $uploadFile, + self::OSS_PART_NUM => ($i + 1), + self::OSS_SEEK_TO => $from_pos, + self::OSS_LENGTH => $to_pos - $from_pos + 1, + self::OSS_CHECK_MD5 => $is_check_md5, + ); + if ($is_check_md5) { + $content_md5 = OssUtil::getMd5SumForFile($uploadFile, $from_pos, $to_pos); + $up_options[self::OSS_CONTENT_MD5] = $content_md5; + } + $response_upload_part[] = $this->uploadPart($bucket, $object, $uploadId, $up_options); + } + + $uploadParts = array(); + foreach ($response_upload_part as $i => $etag) { + $uploadParts[] = array( + 'PartNumber' => ($i + 1), + 'ETag' => $etag, + ); + } + return $this->completeMultipartUpload($bucket, $object, $uploadId, $uploadParts); + } + + /** + * 上传本地目录内的文件或者目录到指定bucket的指定prefix的object中 + * + * @param string $bucket bucket名称 + * @param string $prefix 需要上传到的object的key前缀,可以理解成bucket中的子目录,结尾不能是'/',接口中会补充'/' + * @param string $localDirectory 需要上传的本地目录 + * @param string $exclude 需要排除的目录 + * @param bool $recursive 是否递归的上传localDirectory下的子目录内容 + * @param bool $checkMd5 + * @return array 返回两个列表 array("succeededList" => array("object"), "failedList" => array("object"=>"errorMessage")) + * @throws OssException + */ + public function uploadDir($bucket, $prefix, $localDirectory, $exclude = '.|..|.svn|.git', $recursive = false, $checkMd5 = true) + { + $retArray = array("succeededList" => array(), "failedList" => array()); + if (empty($bucket)) throw new OssException("parameter error, bucket is empty"); + if (!is_string($prefix)) throw new OssException("parameter error, prefix is not string"); + if (empty($localDirectory)) throw new OssException("parameter error, localDirectory is empty"); + $directory = $localDirectory; + $directory = OssUtil::encodePath($directory); + //判断是否目录 + if (!is_dir($directory)) { + throw new OssException('parameter error: ' . $directory . ' is not a directory, please check it'); + } + //read directory + $file_list_array = OssUtil::readDir($directory, $exclude, $recursive); + if (!$file_list_array) { + throw new OssException($directory . ' is empty...'); + } + foreach ($file_list_array as $k => $item) { + if (is_dir($item['path'])) { + continue; + } + $options = array( + self::OSS_PART_SIZE => self::OSS_MIN_PART_SIZE, + self::OSS_CHECK_MD5 => $checkMd5, + ); + $realObject = (!empty($prefix) ? $prefix . '/' : '') . $item['file']; + + try { + $this->multiuploadFile($bucket, $realObject, $item['path'], $options); + $retArray["succeededList"][] = $realObject; + } catch (OssException $e) { + $retArray["failedList"][$realObject] = $e->getMessage(); + } + } + return $retArray; + } + + /** + * 支持生成get和put签名, 用户可以生成一个具有一定有效期的 + * 签名过的url + * + * @param string $bucket + * @param string $object + * @param int $timeout + * @param string $method + * @param array $options Key-Value数组 + * @return string + * @throws OssException + */ + public function signUrl($bucket, $object, $timeout = 60, $method = self::OSS_HTTP_GET, $options = null) + { + $this->precheckCommon($bucket, $object, $options); + //method + if (self::OSS_HTTP_GET !== $method && self::OSS_HTTP_PUT !== $method) { + throw new OssException("method is invalid"); + } + $options[self::OSS_BUCKET] = $bucket; + $options[self::OSS_OBJECT] = $object; + $options[self::OSS_METHOD] = $method; + if (!isset($options[self::OSS_CONTENT_TYPE])) { + $options[self::OSS_CONTENT_TYPE] = ''; + } + $timeout = time() + $timeout; + $options[self::OSS_PREAUTH] = $timeout; + $options[self::OSS_DATE] = $timeout; + $this->setSignStsInUrl(true); + return $this->auth($options); + } + + /** + * 检测options参数 + * + * @param array $options + * @throws OssException + */ + private function precheckOptions(&$options) + { + OssUtil::validateOptions($options); + if (!$options) { + $options = array(); + } + } + + /** + * 校验bucket参数 + * + * @param string $bucket + * @param string $errMsg + * @throws OssException + */ + private function precheckBucket($bucket, $errMsg = 'bucket is not allowed empty') + { + OssUtil::throwOssExceptionWithMessageIfEmpty($bucket, $errMsg); + } + + /** + * 校验object参数 + * + * @param string $object + * @throws OssException + */ + private function precheckObject($object) + { + OssUtil::throwOssExceptionWithMessageIfEmpty($object, "object name is empty"); + } + + /** + * 校验option restore + * + * @param string $restore + * @throws OssException + */ + private function precheckStorage($storage) + { + if (is_string($storage)) { + switch ($storage) { + case self::OSS_STORAGE_ARCHIVE: + return; + case self::OSS_STORAGE_IA: + return; + case self::OSS_STORAGE_STANDARD: + return; + default: + break; + } + } + throw new OssException('storage name is invalid'); + } + + /** + * 校验bucket,options参数 + * + * @param string $bucket + * @param string $object + * @param array $options + * @param bool $isCheckObject + */ + private function precheckCommon($bucket, $object, &$options, $isCheckObject = true) + { + if ($isCheckObject) { + $this->precheckObject($object); + } + $this->precheckOptions($options); + $this->precheckBucket($bucket); + } + + /** + * 参数校验 + * + * @param array $options + * @param string $param + * @param string $funcName + * @throws OssException + */ + private function precheckParam($options, $param, $funcName) + { + if (!isset($options[$param])) { + throw new OssException('The `' . $param . '` options is required in ' . $funcName . '().'); + } + } + + /** + * 检测md5 + * + * @param array $options + * @return bool|null + */ + private function isCheckMD5($options) + { + return $this->getValue($options, self::OSS_CHECK_MD5, false, true, true); + } + + /** + * 获取value + * + * @param array $options + * @param string $key + * @param string $default + * @param bool $isCheckEmpty + * @param bool $isCheckBool + * @return bool|null + */ + private function getValue($options, $key, $default = null, $isCheckEmpty = false, $isCheckBool = false) + { + $value = $default; + if (isset($options[$key])) { + if ($isCheckEmpty) { + if (!empty($options[$key])) { + $value = $options[$key]; + } + } else { + $value = $options[$key]; + } + unset($options[$key]); + } + if ($isCheckBool) { + if ($value !== true && $value !== false) { + $value = false; + } + } + return $value; + } + + /** + * 获取mimetype类型 + * + * @param string $object + * @return string + */ + private function getMimeType($object, $file = null) + { + if (!is_null($file)) { + $type = MimeTypes::getMimetype($file); + if (!is_null($type)) { + return $type; + } + } + + $type = MimeTypes::getMimetype($object); + if (!is_null($type)) { + return $type; + } + + return self::DEFAULT_CONTENT_TYPE; + } + + /** + * 验证并且执行请求,按照OSS Api协议,执行操作 + * + * @param array $options + * @return ResponseCore + * @throws OssException + * @throws RequestCore_Exception + */ + public function auth($options) + { + OssUtil::validateOptions($options); + //验证bucket,list_bucket时不需要验证 + $this->authPrecheckBucket($options); + //验证object + $this->authPrecheckObject($options); + //Object名称的编码必须是utf8 + $this->authPrecheckObjectEncoding($options); + //验证ACL + $this->authPrecheckAcl($options); + // 获得当次请求使用的协议头,是https还是http + $scheme = $this->useSSL ? 'https://' : 'http://'; + // 获得当次请求使用的hostname,如果是公共域名或者专有域名,bucket拼在前面构成三级域名 + $hostname = $this->generateHostname($options[self::OSS_BUCKET]); + $string_to_sign = ''; + $headers = $this->generateHeaders($options, $hostname); + $signable_query_string_params = $this->generateSignableQueryStringParam($options); + $signable_query_string = OssUtil::toQueryString($signable_query_string_params); + $resource_uri = $this->generateResourceUri($options); + //生成请求URL + $conjunction = '?'; + $non_signable_resource = ''; + if (isset($options[self::OSS_SUB_RESOURCE])) { + $conjunction = '&'; + } + if ($signable_query_string !== '') { + $signable_query_string = $conjunction . $signable_query_string; + $conjunction = '&'; + } + $query_string = $this->generateQueryString($options); + if ($query_string !== '') { + $non_signable_resource .= $conjunction . $query_string; + $conjunction = '&'; + } + $this->requestUrl = $scheme . $hostname . $resource_uri . $signable_query_string . $non_signable_resource; + + //创建请求 + $request = new RequestCore($this->requestUrl, $this->requestProxy); + $request->set_useragent($this->generateUserAgent()); + // Streaming uploads + if (isset($options[self::OSS_FILE_UPLOAD])) { + if (is_resource($options[self::OSS_FILE_UPLOAD])) { + $length = null; + + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $length = $options[self::OSS_CONTENT_LENGTH]; + } elseif (isset($options[self::OSS_SEEK_TO])) { + $stats = fstat($options[self::OSS_FILE_UPLOAD]); + if ($stats && $stats[self::OSS_SIZE] >= 0) { + $length = $stats[self::OSS_SIZE] - (integer)$options[self::OSS_SEEK_TO]; + } + } + $request->set_read_stream($options[self::OSS_FILE_UPLOAD], $length); + } else { + $request->set_read_file($options[self::OSS_FILE_UPLOAD]); + $length = $request->read_stream_size; + if (isset($options[self::OSS_CONTENT_LENGTH])) { + $length = $options[self::OSS_CONTENT_LENGTH]; + } elseif (isset($options[self::OSS_SEEK_TO]) && isset($length)) { + $length -= (integer)$options[self::OSS_SEEK_TO]; + } + $request->set_read_stream_size($length); + } + } + if (isset($options[self::OSS_SEEK_TO])) { + $request->set_seek_position((integer)$options[self::OSS_SEEK_TO]); + } + if (isset($options[self::OSS_FILE_DOWNLOAD])) { + if (is_resource($options[self::OSS_FILE_DOWNLOAD])) { + $request->set_write_stream($options[self::OSS_FILE_DOWNLOAD]); + } else { + $request->set_write_file($options[self::OSS_FILE_DOWNLOAD]); + } + } + + if (isset($options[self::OSS_METHOD])) { + $request->set_method($options[self::OSS_METHOD]); + $string_to_sign .= $options[self::OSS_METHOD] . "\n"; + } + + if (isset($options[self::OSS_CONTENT])) { + $request->set_body($options[self::OSS_CONTENT]); + if ($headers[self::OSS_CONTENT_TYPE] === 'application/x-www-form-urlencoded') { + $headers[self::OSS_CONTENT_TYPE] = 'application/octet-stream'; + } + + $headers[self::OSS_CONTENT_LENGTH] = strlen($options[self::OSS_CONTENT]); + $headers[self::OSS_CONTENT_MD5] = base64_encode(md5($options[self::OSS_CONTENT], true)); + } + + if (isset($options[self::OSS_CALLBACK])) { + $headers[self::OSS_CALLBACK] = base64_encode($options[self::OSS_CALLBACK]); + } + if (isset($options[self::OSS_CALLBACK_VAR])) { + $headers[self::OSS_CALLBACK_VAR] = base64_encode($options[self::OSS_CALLBACK_VAR]); + } + + if (!isset($headers[self::OSS_ACCEPT_ENCODING])) { + $headers[self::OSS_ACCEPT_ENCODING] = ''; + } + + uksort($headers, 'strnatcasecmp'); + + foreach ($headers as $header_key => $header_value) { + $header_value = str_replace(array("\r", "\n"), '', $header_value); + if ($header_value !== '' || $header_key === self::OSS_ACCEPT_ENCODING) { + $request->add_header($header_key, $header_value); + } + + if ( + strtolower($header_key) === 'content-md5' || + strtolower($header_key) === 'content-type' || + strtolower($header_key) === 'date' || + (isset($options['self::OSS_PREAUTH']) && (integer)$options['self::OSS_PREAUTH'] > 0) + ) { + $string_to_sign .= $header_value . "\n"; + } elseif (substr(strtolower($header_key), 0, 6) === self::OSS_DEFAULT_PREFIX) { + $string_to_sign .= strtolower($header_key) . ':' . $header_value . "\n"; + } + } + // 生成 signable_resource + $signable_resource = $this->generateSignableResource($options); + $string_to_sign .= rawurldecode($signable_resource) . urldecode($signable_query_string); + + //对?后面的要签名的string字母序排序 + $string_to_sign_ordered = $this->stringToSignSorted($string_to_sign); + + $signature = base64_encode(hash_hmac('sha1', $string_to_sign_ordered, $this->accessKeySecret, true)); + $request->add_header('Authorization', 'OSS ' . $this->accessKeyId . ':' . $signature); + + if (isset($options[self::OSS_PREAUTH]) && (integer)$options[self::OSS_PREAUTH] > 0) { + $signed_url = $this->requestUrl . $conjunction . self::OSS_URL_ACCESS_KEY_ID . '=' . rawurlencode($this->accessKeyId) . '&' . self::OSS_URL_EXPIRES . '=' . $options[self::OSS_PREAUTH] . '&' . self::OSS_URL_SIGNATURE . '=' . rawurlencode($signature); + return $signed_url; + } elseif (isset($options[self::OSS_PREAUTH])) { + return $this->requestUrl; + } + + if ($this->timeout !== 0) { + $request->timeout = $this->timeout; + } + if ($this->connectTimeout !== 0) { + $request->connect_timeout = $this->connectTimeout; + } + + try { + $request->send_request(); + } catch (RequestCore_Exception $e) { + throw(new OssException('RequestCoreException: ' . $e->getMessage())); + } + $response_header = $request->get_response_header(); + $response_header['oss-request-url'] = $this->requestUrl; + $response_header['oss-redirects'] = $this->redirects; + $response_header['oss-stringtosign'] = $string_to_sign; + $response_header['oss-requestheaders'] = $request->request_headers; + + $data = new ResponseCore($response_header, $request->get_response_body(), $request->get_response_code()); + //retry if OSS Internal Error + if ((integer)$request->get_response_code() === 500) { + if ($this->redirects <= $this->maxRetries) { + //设置休眠 + $delay = (integer)(pow(4, $this->redirects) * 100000); + usleep($delay); + $this->redirects++; + $data = $this->auth($options); + } + } + + $this->redirects = 0; + return $data; + } + + /** + * 设置最大尝试次数 + * + * @param int $maxRetries + * @return void + */ + public function setMaxTries($maxRetries = 3) + { + $this->maxRetries = $maxRetries; + } + + /** + * 获取最大尝试次数 + * + * @return int + */ + public function getMaxRetries() + { + return $this->maxRetries; + } + + /** + * 打开sts enable标志,使用户构造函数中传入的$sts生效 + * + * @param boolean $enable + */ + public function setSignStsInUrl($enable) + { + $this->enableStsInUrl = $enable; + } + + /** + * @return boolean + */ + public function isUseSSL() + { + return $this->useSSL; + } + + /** + * @param boolean $useSSL + */ + public function setUseSSL($useSSL) + { + $this->useSSL = $useSSL; + } + + /** + * 检查bucket名称格式是否正确,如果非法抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckBucket($options) + { + if (!(('/' == $options[self::OSS_OBJECT]) && ('' == $options[self::OSS_BUCKET]) && ('GET' == $options[self::OSS_METHOD])) && !OssUtil::validateBucket($options[self::OSS_BUCKET])) { + throw new OssException('"' . $options[self::OSS_BUCKET] . '"' . 'bucket name is invalid'); + } + } + + /** + * + * 检查object名称格式是否正确,如果非法抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckObject($options) + { + if (isset($options[self::OSS_OBJECT]) && $options[self::OSS_OBJECT] === '/') { + return; + } + + if (isset($options[self::OSS_OBJECT]) && !OssUtil::validateObject($options[self::OSS_OBJECT])) { + throw new OssException('"' . $options[self::OSS_OBJECT] . '"' . ' object name is invalid'); + } + } + + /** + * 检查object的编码,如果是gbk或者gb2312则尝试将其转化为utf8编码 + * + * @param mixed $options 参数 + */ + private function authPrecheckObjectEncoding(&$options) + { + $tmp_object = $options[self::OSS_OBJECT]; + try { + if (OssUtil::isGb2312($options[self::OSS_OBJECT])) { + $options[self::OSS_OBJECT] = iconv('GB2312', "UTF-8//IGNORE", $options[self::OSS_OBJECT]); + } elseif (OssUtil::checkChar($options[self::OSS_OBJECT], true)) { + $options[self::OSS_OBJECT] = iconv('GBK', "UTF-8//IGNORE", $options[self::OSS_OBJECT]); + } + } catch (\Exception $e) { + try { + $tmp_object = iconv(mb_detect_encoding($tmp_object), "UTF-8", $tmp_object); + } catch (\Exception $e) { + } + } + $options[self::OSS_OBJECT] = $tmp_object; + } + + /** + * 检查ACL是否是预定义中三种之一,如果不是抛出异常 + * + * @param $options + * @throws OssException + */ + private function authPrecheckAcl($options) + { + if (isset($options[self::OSS_HEADERS][self::OSS_ACL]) && !empty($options[self::OSS_HEADERS][self::OSS_ACL])) { + if (!in_array(strtolower($options[self::OSS_HEADERS][self::OSS_ACL]), self::$OSS_ACL_TYPES)) { + throw new OssException($options[self::OSS_HEADERS][self::OSS_ACL] . ':' . 'acl is invalid(private,public-read,public-read-write)'); + } + } + } + + /** + * 获得档次请求使用的域名 + * bucket在前的三级域名,或者二级域名,如果是cname或者ip的话,则是二级域名 + * + * @param $bucket + * @return string 剥掉协议头的域名 + */ + private function generateHostname($bucket) + { + if ($this->hostType === self::OSS_HOST_TYPE_IP) { + $hostname = $this->hostname; + } elseif ($this->hostType === self::OSS_HOST_TYPE_CNAME) { + $hostname = $this->hostname; + } else { + // 专有域或者官网endpoint + $hostname = ($bucket == '') ? $this->hostname : ($bucket . '.') . $this->hostname; + } + return $hostname; + } + + /** + * 获得当次请求的资源定位字段 + * + * @param $options + * @return string 资源定位字段 + */ + private function generateResourceUri($options) + { + $resource_uri = ""; + + // resource_uri + bucket + if (isset($options[self::OSS_BUCKET]) && '' !== $options[self::OSS_BUCKET]) { + if ($this->hostType === self::OSS_HOST_TYPE_IP) { + $resource_uri = '/' . $options[self::OSS_BUCKET]; + } + } + + // resource_uri + object + if (isset($options[self::OSS_OBJECT]) && '/' !== $options[self::OSS_OBJECT]) { + $resource_uri .= '/' . str_replace(array('%2F', '%25'), array('/', '%'), rawurlencode($options[self::OSS_OBJECT])); + } + + // resource_uri + sub_resource + $conjunction = '?'; + if (isset($options[self::OSS_SUB_RESOURCE])) { + $resource_uri .= $conjunction . $options[self::OSS_SUB_RESOURCE]; + } + return $resource_uri; + } + + /** + * 生成signalbe_query_string_param, array类型 + * + * @param array $options + * @return array + */ + private function generateSignableQueryStringParam($options) + { + $signableQueryStringParams = array(); + $signableList = array( + self::OSS_PART_NUM, + 'response-content-type', + 'response-content-language', + 'response-cache-control', + 'response-content-encoding', + 'response-expires', + 'response-content-disposition', + self::OSS_UPLOAD_ID, + self::OSS_COMP, + self::OSS_LIVE_CHANNEL_STATUS, + self::OSS_LIVE_CHANNEL_START_TIME, + self::OSS_LIVE_CHANNEL_END_TIME, + self::OSS_PROCESS, + self::OSS_POSITION, + self::OSS_SYMLINK, + self::OSS_RESTORE, + ); + + foreach ($signableList as $item) { + if (isset($options[$item])) { + $signableQueryStringParams[$item] = $options[$item]; + } + } + + if ($this->enableStsInUrl && (!is_null($this->securityToken))) { + $signableQueryStringParams["security-token"] = $this->securityToken; + } + + return $signableQueryStringParams; + } + + /** + * 生成用于签名resource段 + * + * @param mixed $options + * @return string + */ + private function generateSignableResource($options) + { + $signableResource = ""; + $signableResource .= '/'; + if (isset($options[self::OSS_BUCKET]) && '' !== $options[self::OSS_BUCKET]) { + $signableResource .= $options[self::OSS_BUCKET]; + // 如果操作没有Object操作的话,这里最后是否有斜线有个trick,ip的域名下,不需要加'/', 否则需要加'/' + if ($options[self::OSS_OBJECT] == '/') { + if ($this->hostType !== self::OSS_HOST_TYPE_IP) { + $signableResource .= "/"; + } + } + } + //signable_resource + object + if (isset($options[self::OSS_OBJECT]) && '/' !== $options[self::OSS_OBJECT]) { + $signableResource .= '/' . str_replace(array('%2F', '%25'), array('/', '%'), rawurlencode($options[self::OSS_OBJECT])); + } + if (isset($options[self::OSS_SUB_RESOURCE])) { + $signableResource .= '?' . $options[self::OSS_SUB_RESOURCE]; + } + return $signableResource; + } + + /** + * 生成query_string + * + * @param mixed $options + * @return string + */ + private function generateQueryString($options) + { + //请求参数 + $queryStringParams = array(); + if (isset($options[self::OSS_QUERY_STRING])) { + $queryStringParams = array_merge($queryStringParams, $options[self::OSS_QUERY_STRING]); + } + return OssUtil::toQueryString($queryStringParams); + } + + private function stringToSignSorted($string_to_sign) + { + $queryStringSorted = ''; + $explodeResult = explode('?', $string_to_sign); + $index = count($explodeResult); + if ($index === 1) + return $string_to_sign; + + $queryStringParams = explode('&', $explodeResult[$index - 1]); + sort($queryStringParams); + + foreach ($queryStringParams as $params) { + $queryStringSorted .= $params . '&'; + } + + $queryStringSorted = substr($queryStringSorted, 0, -1); + + return $explodeResult[0] . '?' . $queryStringSorted; + } + + /** + * 初始化headers + * + * @param mixed $options + * @param string $hostname hostname + * @return array + */ + private function generateHeaders($options, $hostname) + { + $headers = array( + self::OSS_CONTENT_MD5 => '', + self::OSS_CONTENT_TYPE => isset($options[self::OSS_CONTENT_TYPE]) ? $options[self::OSS_CONTENT_TYPE] : self::DEFAULT_CONTENT_TYPE, + self::OSS_DATE => isset($options[self::OSS_DATE]) ? $options[self::OSS_DATE] : gmdate('D, d M Y H:i:s \G\M\T'), + self::OSS_HOST => $hostname, + ); + if (isset($options[self::OSS_CONTENT_MD5])) { + $headers[self::OSS_CONTENT_MD5] = $options[self::OSS_CONTENT_MD5]; + } + + //添加stsSecurityToken + if ((!is_null($this->securityToken)) && (!$this->enableStsInUrl)) { + $headers[self::OSS_SECURITY_TOKEN] = $this->securityToken; + } + //合并HTTP headers + if (isset($options[self::OSS_HEADERS])) { + $headers = array_merge($headers, $options[self::OSS_HEADERS]); + } + return $headers; + } + + /** + * 生成请求用的UserAgent + * + * @return string + */ + private function generateUserAgent() + { + return self::OSS_NAME . "/" . self::OSS_VERSION . " (" . php_uname('s') . "/" . php_uname('r') . "/" . php_uname('m') . ";" . PHP_VERSION . ")"; + } + + /** + * 检查endpoint的种类 + * 如有有协议头,剥去协议头 + * 并且根据参数 is_cname 和endpoint本身,判定域名类型,是ip,cname,还是专有域或者官网域名 + * + * @param string $endpoint + * @param boolean $isCName + * @return string 剥掉协议头的域名 + */ + private function checkEndpoint($endpoint, $isCName) + { + $ret_endpoint = null; + if (strpos($endpoint, 'http://') === 0) { + $ret_endpoint = substr($endpoint, strlen('http://')); + } elseif (strpos($endpoint, 'https://') === 0) { + $ret_endpoint = substr($endpoint, strlen('https://')); + $this->useSSL = true; + } else { + $ret_endpoint = $endpoint; + } + + if ($isCName) { + $this->hostType = self::OSS_HOST_TYPE_CNAME; + } elseif (OssUtil::isIPFormat($ret_endpoint)) { + $this->hostType = self::OSS_HOST_TYPE_IP; + } else { + $this->hostType = self::OSS_HOST_TYPE_NORMAL; + } + return $ret_endpoint; + } + + /** + * 用来检查sdk所以来的扩展是否打开 + * + * @throws OssException + */ + public static function checkEnv() + { + if (function_exists('get_loaded_extensions')) { + //检测curl扩展 + $enabled_extension = array("curl"); + $extensions = get_loaded_extensions(); + if ($extensions) { + foreach ($enabled_extension as $item) { + if (!in_array($item, $extensions)) { + throw new OssException("Extension {" . $item . "} is not installed or not enabled, please check your php env."); + } + } + } else { + throw new OssException("function get_loaded_extensions not found."); + } + } else { + throw new OssException('Function get_loaded_extensions has been disabled, please check php config.'); + } + } + + /** + * //* 设置http库的请求超时时间,单位秒 + * + * @param int $timeout + */ + public function setTimeout($timeout) + { + $this->timeout = $timeout; + } + + /** + * 设置http库的连接超时时间,单位秒 + * + * @param int $connectTimeout + */ + public function setConnectTimeout($connectTimeout) + { + $this->connectTimeout = $connectTimeout; + } + + // 生命周期相关常量 + const OSS_LIFECYCLE_EXPIRATION = "Expiration"; + const OSS_LIFECYCLE_TIMING_DAYS = "Days"; + const OSS_LIFECYCLE_TIMING_DATE = "Date"; + //OSS 内部常量 + const OSS_BUCKET = 'bucket'; + const OSS_OBJECT = 'object'; + const OSS_HEADERS = OssUtil::OSS_HEADERS; + const OSS_METHOD = 'method'; + const OSS_QUERY = 'query'; + const OSS_BASENAME = 'basename'; + const OSS_MAX_KEYS = 'max-keys'; + const OSS_UPLOAD_ID = 'uploadId'; + const OSS_PART_NUM = 'partNumber'; + const OSS_COMP = 'comp'; + const OSS_LIVE_CHANNEL_STATUS = 'status'; + const OSS_LIVE_CHANNEL_START_TIME = 'startTime'; + const OSS_LIVE_CHANNEL_END_TIME = 'endTime'; + const OSS_POSITION = 'position'; + const OSS_MAX_KEYS_VALUE = 100; + const OSS_MAX_OBJECT_GROUP_VALUE = OssUtil::OSS_MAX_OBJECT_GROUP_VALUE; + const OSS_MAX_PART_SIZE = OssUtil::OSS_MAX_PART_SIZE; + const OSS_MID_PART_SIZE = OssUtil::OSS_MID_PART_SIZE; + const OSS_MIN_PART_SIZE = OssUtil::OSS_MIN_PART_SIZE; + const OSS_FILE_SLICE_SIZE = 8192; + const OSS_PREFIX = 'prefix'; + const OSS_DELIMITER = 'delimiter'; + const OSS_MARKER = 'marker'; + const OSS_ACCEPT_ENCODING = 'Accept-Encoding'; + const OSS_CONTENT_MD5 = 'Content-Md5'; + const OSS_SELF_CONTENT_MD5 = 'x-oss-meta-md5'; + const OSS_CONTENT_TYPE = 'Content-Type'; + const OSS_CONTENT_LENGTH = 'Content-Length'; + const OSS_IF_MODIFIED_SINCE = 'If-Modified-Since'; + const OSS_IF_UNMODIFIED_SINCE = 'If-Unmodified-Since'; + const OSS_IF_MATCH = 'If-Match'; + const OSS_IF_NONE_MATCH = 'If-None-Match'; + const OSS_CACHE_CONTROL = 'Cache-Control'; + const OSS_EXPIRES = 'Expires'; + const OSS_PREAUTH = 'preauth'; + const OSS_CONTENT_COING = 'Content-Coding'; + const OSS_CONTENT_DISPOSTION = 'Content-Disposition'; + const OSS_RANGE = 'range'; + const OSS_ETAG = 'etag'; + const OSS_LAST_MODIFIED = 'lastmodified'; + const OS_CONTENT_RANGE = 'Content-Range'; + const OSS_CONTENT = OssUtil::OSS_CONTENT; + const OSS_BODY = 'body'; + const OSS_LENGTH = OssUtil::OSS_LENGTH; + const OSS_HOST = 'Host'; + const OSS_DATE = 'Date'; + const OSS_AUTHORIZATION = 'Authorization'; + const OSS_FILE_DOWNLOAD = 'fileDownload'; + const OSS_FILE_UPLOAD = 'fileUpload'; + const OSS_PART_SIZE = 'partSize'; + const OSS_SEEK_TO = 'seekTo'; + const OSS_SIZE = 'size'; + const OSS_QUERY_STRING = 'query_string'; + const OSS_SUB_RESOURCE = 'sub_resource'; + const OSS_DEFAULT_PREFIX = 'x-oss-'; + const OSS_CHECK_MD5 = 'checkmd5'; + const DEFAULT_CONTENT_TYPE = 'application/octet-stream'; + const OSS_SYMLINK_TARGET = 'x-oss-symlink-target'; + const OSS_SYMLINK = 'symlink'; + const OSS_HTTP_CODE = 'http_code'; + const OSS_REQUEST_ID = 'x-oss-request-id'; + const OSS_INFO = 'info'; + const OSS_STORAGE = 'storage'; + const OSS_RESTORE = 'restore'; + const OSS_STORAGE_STANDARD = 'Standard'; + const OSS_STORAGE_IA = 'IA'; + const OSS_STORAGE_ARCHIVE = 'Archive'; + + //私有URL变量 + const OSS_URL_ACCESS_KEY_ID = 'OSSAccessKeyId'; + const OSS_URL_EXPIRES = 'Expires'; + const OSS_URL_SIGNATURE = 'Signature'; + //HTTP方法 + const OSS_HTTP_GET = 'GET'; + const OSS_HTTP_PUT = 'PUT'; + const OSS_HTTP_HEAD = 'HEAD'; + const OSS_HTTP_POST = 'POST'; + const OSS_HTTP_DELETE = 'DELETE'; + const OSS_HTTP_OPTIONS = 'OPTIONS'; + //其他常量 + const OSS_ACL = 'x-oss-acl'; + const OSS_OBJECT_ACL = 'x-oss-object-acl'; + const OSS_OBJECT_GROUP = 'x-oss-file-group'; + const OSS_MULTI_PART = 'uploads'; + const OSS_MULTI_DELETE = 'delete'; + const OSS_OBJECT_COPY_SOURCE = 'x-oss-copy-source'; + const OSS_OBJECT_COPY_SOURCE_RANGE = "x-oss-copy-source-range"; + const OSS_PROCESS = "x-oss-process"; + const OSS_CALLBACK = "x-oss-callback"; + const OSS_CALLBACK_VAR = "x-oss-callback-var"; + //支持STS SecurityToken + const OSS_SECURITY_TOKEN = "x-oss-security-token"; + const OSS_ACL_TYPE_PRIVATE = 'private'; + const OSS_ACL_TYPE_PUBLIC_READ = 'public-read'; + const OSS_ACL_TYPE_PUBLIC_READ_WRITE = 'public-read-write'; + const OSS_ENCODING_TYPE = "encoding-type"; + const OSS_ENCODING_TYPE_URL = "url"; + + // 域名类型 + const OSS_HOST_TYPE_NORMAL = "normal";//http://bucket.oss-cn-hangzhou.aliyuncs.com/object + const OSS_HOST_TYPE_IP = "ip"; //http://1.1.1.1/bucket/object + const OSS_HOST_TYPE_SPECIAL = 'special'; //http://bucket.guizhou.gov/object + const OSS_HOST_TYPE_CNAME = "cname"; //http://mydomain.com/object + //OSS ACL数组 + static $OSS_ACL_TYPES = array( + self::OSS_ACL_TYPE_PRIVATE, + self::OSS_ACL_TYPE_PUBLIC_READ, + self::OSS_ACL_TYPE_PUBLIC_READ_WRITE + ); + // OssClient版本信息 + const OSS_NAME = "aliyun-sdk-php"; + const OSS_VERSION = "2.3.0"; + const OSS_BUILD = "20180105"; + const OSS_AUTHOR = ""; + const OSS_OPTIONS_ORIGIN = 'Origin'; + const OSS_OPTIONS_REQUEST_METHOD = 'Access-Control-Request-Method'; + const OSS_OPTIONS_REQUEST_HEADERS = 'Access-Control-Request-Headers'; + + //是否使用ssl + private $useSSL = false; + private $maxRetries = 3; + private $redirects = 0; + + // 用户提供的域名类型,有四种 OSS_HOST_TYPE_NORMAL, OSS_HOST_TYPE_IP, OSS_HOST_TYPE_SPECIAL, OSS_HOST_TYPE_CNAME + private $hostType = self::OSS_HOST_TYPE_NORMAL; + private $requestUrl; + private $accessKeyId; + private $accessKeySecret; + private $hostname; + private $securityToken; + private $requestProxy = null; + private $enableStsInUrl = false; + private $timeout = 0; + private $connectTimeout = 0; +} diff --git a/addons/alioss/library/OSS/Result/AclResult.php b/addons/alioss/library/OSS/Result/AclResult.php new file mode 100755 index 0000000..6da0860 --- /dev/null +++ b/addons/alioss/library/OSS/Result/AclResult.php @@ -0,0 +1,32 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + if (isset($xml->AccessControlList->Grant)) { + return strval($xml->AccessControlList->Grant); + } else { + throw new OssException("xml format exception"); + } + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/AppendResult.php b/addons/alioss/library/OSS/Result/AppendResult.php new file mode 100755 index 0000000..433c03e --- /dev/null +++ b/addons/alioss/library/OSS/Result/AppendResult.php @@ -0,0 +1,27 @@ +rawResponse->header; + if (isset($header["x-oss-next-append-position"])) { + return intval($header["x-oss-next-append-position"]); + } + throw new OssException("cannot get next-append-position"); + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/BodyResult.php b/addons/alioss/library/OSS/Result/BodyResult.php new file mode 100755 index 0000000..44ba15e --- /dev/null +++ b/addons/alioss/library/OSS/Result/BodyResult.php @@ -0,0 +1,19 @@ +rawResponse->body) ? "" : $this->rawResponse->body; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/CallbackResult.php b/addons/alioss/library/OSS/Result/CallbackResult.php new file mode 100755 index 0000000..514e985 --- /dev/null +++ b/addons/alioss/library/OSS/Result/CallbackResult.php @@ -0,0 +1,21 @@ +rawResponse->status; + if ((int)(intval($status) / 100) == 2 && (int)(intval($status)) !== 203) { + return true; + } + return false; + } + +} diff --git a/addons/alioss/library/OSS/Result/CopyObjectResult.php b/addons/alioss/library/OSS/Result/CopyObjectResult.php new file mode 100755 index 0000000..498723e --- /dev/null +++ b/addons/alioss/library/OSS/Result/CopyObjectResult.php @@ -0,0 +1,30 @@ +rawResponse->body; + $xml = simplexml_load_string($body); + $result = array(); + + if (isset($xml->LastModified)) { + $result[] = $xml->LastModified; + } + if (isset($xml->ETag)) { + $result[] = $xml->ETag; + } + + return $result; + } +} diff --git a/addons/alioss/library/OSS/Result/DeleteObjectsResult.php b/addons/alioss/library/OSS/Result/DeleteObjectsResult.php new file mode 100755 index 0000000..dc373b8 --- /dev/null +++ b/addons/alioss/library/OSS/Result/DeleteObjectsResult.php @@ -0,0 +1,27 @@ +rawResponse->body; + $xml = simplexml_load_string($body); + $objects = array(); + + if (isset($xml->Deleted)) { + foreach($xml->Deleted as $deleteKey) + $objects[] = $deleteKey->Key; + } + return $objects; + } +} diff --git a/addons/alioss/library/OSS/Result/ExistResult.php b/addons/alioss/library/OSS/Result/ExistResult.php new file mode 100755 index 0000000..f7aa287 --- /dev/null +++ b/addons/alioss/library/OSS/Result/ExistResult.php @@ -0,0 +1,35 @@ +rawResponse->status) === 200 ? true : false; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 判断是否存在的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetCnameResult.php b/addons/alioss/library/OSS/Result/GetCnameResult.php new file mode 100755 index 0000000..eed01f9 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetCnameResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $config = new CnameConfig(); + $config->parseFromXml($content); + return $config; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetCorsResult.php b/addons/alioss/library/OSS/Result/GetCorsResult.php new file mode 100755 index 0000000..a51afe2 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetCorsResult.php @@ -0,0 +1,35 @@ +rawResponse->body; + $config = new CorsConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetLifecycleResult.php b/addons/alioss/library/OSS/Result/GetLifecycleResult.php new file mode 100755 index 0000000..6b440c3 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLifecycleResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new LifecycleConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetLiveChannelHistoryResult.php b/addons/alioss/library/OSS/Result/GetLiveChannelHistoryResult.php new file mode 100755 index 0000000..202a668 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLiveChannelHistoryResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelHistory(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/addons/alioss/library/OSS/Result/GetLiveChannelInfoResult.php b/addons/alioss/library/OSS/Result/GetLiveChannelInfoResult.php new file mode 100755 index 0000000..d5a9005 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLiveChannelInfoResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelInfo(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/addons/alioss/library/OSS/Result/GetLiveChannelStatusResult.php b/addons/alioss/library/OSS/Result/GetLiveChannelStatusResult.php new file mode 100755 index 0000000..6b8a60f --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLiveChannelStatusResult.php @@ -0,0 +1,19 @@ +rawResponse->body; + $channelList = new GetLiveChannelStatus(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/addons/alioss/library/OSS/Result/GetLocationResult.php b/addons/alioss/library/OSS/Result/GetLocationResult.php new file mode 100755 index 0000000..71c4c96 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLocationResult.php @@ -0,0 +1,30 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + return $xml; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetLoggingResult.php b/addons/alioss/library/OSS/Result/GetLoggingResult.php new file mode 100755 index 0000000..72fc3ae --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetLoggingResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new LoggingConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetRefererResult.php b/addons/alioss/library/OSS/Result/GetRefererResult.php new file mode 100755 index 0000000..aee50d3 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetRefererResult.php @@ -0,0 +1,41 @@ +rawResponse->body; + $config = new RefererConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetStorageCapacityResult.php b/addons/alioss/library/OSS/Result/GetStorageCapacityResult.php new file mode 100755 index 0000000..84e4916 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetStorageCapacityResult.php @@ -0,0 +1,34 @@ +rawResponse->body; + if (empty($content)) { + throw new OssException("body is null"); + } + $xml = simplexml_load_string($content); + if (isset($xml->StorageCapacity)) { + return intval($xml->StorageCapacity); + } else { + throw new OssException("xml format exception"); + } + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/GetWebsiteResult.php b/addons/alioss/library/OSS/Result/GetWebsiteResult.php new file mode 100755 index 0000000..3099172 --- /dev/null +++ b/addons/alioss/library/OSS/Result/GetWebsiteResult.php @@ -0,0 +1,40 @@ +rawResponse->body; + $config = new WebsiteConfig(); + $config->parseFromXml($content); + return $config; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK, 获取bucket相关配置的接口,404也认为是一种 + * 有效响应 + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/HeaderResult.php b/addons/alioss/library/OSS/Result/HeaderResult.php new file mode 100755 index 0000000..c9aae56 --- /dev/null +++ b/addons/alioss/library/OSS/Result/HeaderResult.php @@ -0,0 +1,23 @@ +rawResponse->header) ? array() : $this->rawResponse->header; + } + +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/InitiateMultipartUploadResult.php b/addons/alioss/library/OSS/Result/InitiateMultipartUploadResult.php new file mode 100755 index 0000000..af985f2 --- /dev/null +++ b/addons/alioss/library/OSS/Result/InitiateMultipartUploadResult.php @@ -0,0 +1,29 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + if (isset($xml->UploadId)) { + return strval($xml->UploadId); + } + throw new OssException("cannot get UploadId"); + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/ListBucketsResult.php b/addons/alioss/library/OSS/Result/ListBucketsResult.php new file mode 100755 index 0000000..a58fb2d --- /dev/null +++ b/addons/alioss/library/OSS/Result/ListBucketsResult.php @@ -0,0 +1,33 @@ +rawResponse->body; + $xml = new \SimpleXMLElement($content); + if (isset($xml->Buckets) && isset($xml->Buckets->Bucket)) { + foreach ($xml->Buckets->Bucket as $bucket) { + $bucketInfo = new BucketInfo(strval($bucket->Location), + strval($bucket->Name), + strval($bucket->CreationDate)); + $bucketList[] = $bucketInfo; + } + } + return new BucketListInfo($bucketList); + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/ListLiveChannelResult.php b/addons/alioss/library/OSS/Result/ListLiveChannelResult.php new file mode 100755 index 0000000..1a6e2a4 --- /dev/null +++ b/addons/alioss/library/OSS/Result/ListLiveChannelResult.php @@ -0,0 +1,16 @@ +rawResponse->body; + $channelList = new LiveChannelListInfo(); + $channelList->parseFromXml($content); + return $channelList; + } +} diff --git a/addons/alioss/library/OSS/Result/ListMultipartUploadResult.php b/addons/alioss/library/OSS/Result/ListMultipartUploadResult.php new file mode 100755 index 0000000..bcb20bf --- /dev/null +++ b/addons/alioss/library/OSS/Result/ListMultipartUploadResult.php @@ -0,0 +1,55 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + + $encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : ""; + $bucket = isset($xml->Bucket) ? strval($xml->Bucket) : ""; + $keyMarker = isset($xml->KeyMarker) ? strval($xml->KeyMarker) : ""; + $keyMarker = OssUtil::decodeKey($keyMarker, $encodingType); + $uploadIdMarker = isset($xml->UploadIdMarker) ? strval($xml->UploadIdMarker) : ""; + $nextKeyMarker = isset($xml->NextKeyMarker) ? strval($xml->NextKeyMarker) : ""; + $nextKeyMarker = OssUtil::decodeKey($nextKeyMarker, $encodingType); + $nextUploadIdMarker = isset($xml->NextUploadIdMarker) ? strval($xml->NextUploadIdMarker) : ""; + $delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : ""; + $delimiter = OssUtil::decodeKey($delimiter, $encodingType); + $prefix = isset($xml->Prefix) ? strval($xml->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $maxUploads = isset($xml->MaxUploads) ? intval($xml->MaxUploads) : 0; + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $listUpload = array(); + + if (isset($xml->Upload)) { + foreach ($xml->Upload as $upload) { + $key = isset($upload->Key) ? strval($upload->Key) : ""; + $key = OssUtil::decodeKey($key, $encodingType); + $uploadId = isset($upload->UploadId) ? strval($upload->UploadId) : ""; + $initiated = isset($upload->Initiated) ? strval($upload->Initiated) : ""; + $listUpload[] = new UploadInfo($key, $uploadId, $initiated); + } + } + return new ListMultipartUploadInfo($bucket, $keyMarker, $uploadIdMarker, + $nextKeyMarker, $nextUploadIdMarker, + $delimiter, $prefix, $maxUploads, $isTruncated, $listUpload); + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/ListObjectsResult.php b/addons/alioss/library/OSS/Result/ListObjectsResult.php new file mode 100755 index 0000000..fcf493d --- /dev/null +++ b/addons/alioss/library/OSS/Result/ListObjectsResult.php @@ -0,0 +1,71 @@ +rawResponse->body); + $encodingType = isset($xml->EncodingType) ? strval($xml->EncodingType) : ""; + $objectList = $this->parseObjectList($xml, $encodingType); + $prefixList = $this->parsePrefixList($xml, $encodingType); + $bucketName = isset($xml->Name) ? strval($xml->Name) : ""; + $prefix = isset($xml->Prefix) ? strval($xml->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $marker = isset($xml->Marker) ? strval($xml->Marker) : ""; + $marker = OssUtil::decodeKey($marker, $encodingType); + $maxKeys = isset($xml->MaxKeys) ? intval($xml->MaxKeys) : 0; + $delimiter = isset($xml->Delimiter) ? strval($xml->Delimiter) : ""; + $delimiter = OssUtil::decodeKey($delimiter, $encodingType); + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $nextMarker = isset($xml->NextMarker) ? strval($xml->NextMarker) : ""; + $nextMarker = OssUtil::decodeKey($nextMarker, $encodingType); + return new ObjectListInfo($bucketName, $prefix, $marker, $nextMarker, $maxKeys, $delimiter, $isTruncated, $objectList, $prefixList); + } + + private function parseObjectList($xml, $encodingType) + { + $retList = array(); + if (isset($xml->Contents)) { + foreach ($xml->Contents as $content) { + $key = isset($content->Key) ? strval($content->Key) : ""; + $key = OssUtil::decodeKey($key, $encodingType); + $lastModified = isset($content->LastModified) ? strval($content->LastModified) : ""; + $eTag = isset($content->ETag) ? strval($content->ETag) : ""; + $type = isset($content->Type) ? strval($content->Type) : ""; + $size = isset($content->Size) ? intval($content->Size) : 0; + $storageClass = isset($content->StorageClass) ? strval($content->StorageClass) : ""; + $retList[] = new ObjectInfo($key, $lastModified, $eTag, $type, $size, $storageClass); + } + } + return $retList; + } + + private function parsePrefixList($xml, $encodingType) + { + $retList = array(); + if (isset($xml->CommonPrefixes)) { + foreach ($xml->CommonPrefixes as $commonPrefix) { + $prefix = isset($commonPrefix->Prefix) ? strval($commonPrefix->Prefix) : ""; + $prefix = OssUtil::decodeKey($prefix, $encodingType); + $retList[] = new PrefixInfo($prefix); + } + } + return $retList; + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/ListPartsResult.php b/addons/alioss/library/OSS/Result/ListPartsResult.php new file mode 100755 index 0000000..fd8a1b8 --- /dev/null +++ b/addons/alioss/library/OSS/Result/ListPartsResult.php @@ -0,0 +1,42 @@ +rawResponse->body; + $xml = simplexml_load_string($content); + $bucket = isset($xml->Bucket) ? strval($xml->Bucket) : ""; + $key = isset($xml->Key) ? strval($xml->Key) : ""; + $uploadId = isset($xml->UploadId) ? strval($xml->UploadId) : ""; + $nextPartNumberMarker = isset($xml->NextPartNumberMarker) ? intval($xml->NextPartNumberMarker) : ""; + $maxParts = isset($xml->MaxParts) ? intval($xml->MaxParts) : ""; + $isTruncated = isset($xml->IsTruncated) ? strval($xml->IsTruncated) : ""; + $partList = array(); + if (isset($xml->Part)) { + foreach ($xml->Part as $part) { + $partNumber = isset($part->PartNumber) ? intval($part->PartNumber) : ""; + $lastModified = isset($part->LastModified) ? strval($part->LastModified) : ""; + $eTag = isset($part->ETag) ? strval($part->ETag) : ""; + $size = isset($part->Size) ? intval($part->Size) : ""; + $partList[] = new PartInfo($partNumber, $lastModified, $eTag, $size); + } + } + return new ListPartsInfo($bucket, $key, $uploadId, $nextPartNumberMarker, $maxParts, $isTruncated, $partList); + } +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/PutLiveChannelResult.php b/addons/alioss/library/OSS/Result/PutLiveChannelResult.php new file mode 100755 index 0000000..dcac86b --- /dev/null +++ b/addons/alioss/library/OSS/Result/PutLiveChannelResult.php @@ -0,0 +1,16 @@ +rawResponse->body; + $channel = new LiveChannelInfo(); + $channel->parseFromXml($content); + return $channel; + } +} diff --git a/addons/alioss/library/OSS/Result/PutSetDeleteResult.php b/addons/alioss/library/OSS/Result/PutSetDeleteResult.php new file mode 100755 index 0000000..97af003 --- /dev/null +++ b/addons/alioss/library/OSS/Result/PutSetDeleteResult.php @@ -0,0 +1,20 @@ + $this->rawResponse->body); + return array_merge($this->rawResponse->header, $body); + } +} diff --git a/addons/alioss/library/OSS/Result/Result.php b/addons/alioss/library/OSS/Result/Result.php new file mode 100755 index 0000000..491256f --- /dev/null +++ b/addons/alioss/library/OSS/Result/Result.php @@ -0,0 +1,175 @@ +rawResponse = $response; + $this->parseResponse(); + } + + /** + * 获取requestId + * + * @return string + */ + public function getRequestId() + { + if (isset($this->rawResponse) && + isset($this->rawResponse->header) && + isset($this->rawResponse->header['x-oss-request-id']) + ) { + return $this->rawResponse->header['x-oss-request-id']; + } else { + return ''; + } + } + + /** + * 得到返回数据,不同的请求返回数据格式不同 + * + * $return mixed + */ + public function getData() + { + return $this->parsedData; + } + + /** + * 由子类实现,不同的请求返回数据有不同的解析逻辑,由子类实现 + * + * @return mixed + */ + abstract protected function parseDataFromResponse(); + + /** + * 操作是否成功 + * + * @return mixed + */ + public function isOK() + { + return $this->isOk; + } + + /** + * @throws OssException + */ + public function parseResponse() + { + $this->isOk = $this->isResponseOk(); + if ($this->isOk) { + $this->parsedData = $this->parseDataFromResponse(); + } else { + $httpStatus = strval($this->rawResponse->status); + $requestId = strval($this->getRequestId()); + $code = $this->retrieveErrorCode($this->rawResponse->body); + $message = $this->retrieveErrorMessage($this->rawResponse->body); + $body = $this->rawResponse->body; + + $details = array( + 'status' => $httpStatus, + 'request-id' => $requestId, + 'code' => $code, + 'message' => $message, + 'body' => $body + ); + throw new OssException($details); + } + } + + /** + * 尝试从body中获取错误Message + * + * @param $body + * @return string + */ + private function retrieveErrorMessage($body) + { + if (empty($body) || false === strpos($body, 'Message)) { + return strval($xml->Message); + } + return ''; + } + + /** + * 尝试从body中获取错误Code + * + * @param $body + * @return string + */ + private function retrieveErrorCode($body) + { + if (empty($body) || false === strpos($body, 'Code)) { + return strval($xml->Code); + } + return ''; + } + + /** + * 根据返回http状态码判断,[200-299]即认为是OK + * + * @return bool + */ + protected function isResponseOk() + { + $status = $this->rawResponse->status; + if ((int)(intval($status) / 100) == 2) { + return true; + } + return false; + } + + /** + * 返回原始的返回数据 + * + * @return ResponseCore + */ + public function getRawResponse() + { + return $this->rawResponse; + } + + /** + * 标示请求是否成功 + */ + protected $isOk = false; + /** + * 由子类解析过的数据 + */ + protected $parsedData = null; + /** + * 存放auth函数返回的原始Response + * + * @var ResponseCore + */ + protected $rawResponse; +} \ No newline at end of file diff --git a/addons/alioss/library/OSS/Result/SymlinkResult.php b/addons/alioss/library/OSS/Result/SymlinkResult.php new file mode 100755 index 0000000..9c6d861 --- /dev/null +++ b/addons/alioss/library/OSS/Result/SymlinkResult.php @@ -0,0 +1,24 @@ +rawResponse->header[OssClient::OSS_SYMLINK_TARGET] = rawurldecode($this->rawResponse->header[OssClient::OSS_SYMLINK_TARGET]); + return $this->rawResponse->header; + } +} + diff --git a/addons/alioss/library/OSS/Result/UploadPartResult.php b/addons/alioss/library/OSS/Result/UploadPartResult.php new file mode 100755 index 0000000..c6b66d4 --- /dev/null +++ b/addons/alioss/library/OSS/Result/UploadPartResult.php @@ -0,0 +1,28 @@ +rawResponse->header; + if (isset($header["etag"])) { + return $header["etag"]; + } + throw new OssException("cannot get ETag"); + + } +} \ No newline at end of file diff --git a/application/admin/lang/zh-cn/cars.php b/application/admin/lang/zh-cn/cars.php index 3f807ce..b0c3a03 100644 --- a/application/admin/lang/zh-cn/cars.php +++ b/application/admin/lang/zh-cn/cars.php @@ -4,7 +4,7 @@ return [ 'Title' => '标题', 'Brand_id' => '品牌', 'Series_id' => '品牌/车系', - 'Price' => '展示价格(万元)', + 'Price' => '展示价格(万元)/日租每天(元)', 'Cover_image' => '图片', 'Location' => '城市名', 'Contact_id' => '联系人', diff --git a/application/admin/view/cars/add.html b/application/admin/view/cars/add.html index d5e332f..f87605e 100644 --- a/application/admin/view/cars/add.html +++ b/application/admin/view/cars/add.html @@ -24,13 +24,13 @@
- +
- +
diff --git a/application/admin/view/cars/edit.html b/application/admin/view/cars/edit.html index 3dadb14..1dffd97 100644 --- a/application/admin/view/cars/edit.html +++ b/application/admin/view/cars/edit.html @@ -33,7 +33,7 @@
- +
diff --git a/application/extra/addons.php b/application/extra/addons.php index 2177468..1bd42df 100644 --- a/application/extra/addons.php +++ b/application/extra/addons.php @@ -2,7 +2,20 @@ return [ 'autoload' => false, - 'hooks' => [], + 'hooks' => [ + 'app_init' => [ + 'alioss', + ], + 'module_init' => [ + 'alioss', + ], + 'upload_config_init' => [ + 'alioss', + ], + 'upload_delete' => [ + 'alioss', + ], + ], 'route' => [], 'priority' => [], 'domain' => '', diff --git a/public/assets/addons/alioss/js/spark.js b/public/assets/addons/alioss/js/spark.js new file mode 100644 index 0000000..5a22f70 --- /dev/null +++ b/public/assets/addons/alioss/js/spark.js @@ -0,0 +1 @@ +(function(factory){if(typeof exports==="object"){module.exports=factory()}else if(typeof define==="function"&&define.amd){define(factory)}else{var glob;try{glob=window}catch(e){glob=self}glob.SparkMD5=factory()}})(function(undefined){"use strict";var add32=function(a,b){return a+b&4294967295},hex_chr=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];function cmn(q,a,b,x,s,t){a=add32(add32(a,q),add32(x,t));return add32(a<>>32-s,b)}function md5cycle(x,k){var a=x[0],b=x[1],c=x[2],d=x[3];a+=(b&c|~b&d)+k[0]-680876936|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[1]-389564586|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[2]+606105819|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[3]-1044525330|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[4]-176418897|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[5]+1200080426|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[6]-1473231341|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[7]-45705983|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[8]+1770035416|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[9]-1958414417|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[10]-42063|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[11]-1990404162|0;b=(b<<22|b>>>10)+c|0;a+=(b&c|~b&d)+k[12]+1804603682|0;a=(a<<7|a>>>25)+b|0;d+=(a&b|~a&c)+k[13]-40341101|0;d=(d<<12|d>>>20)+a|0;c+=(d&a|~d&b)+k[14]-1502002290|0;c=(c<<17|c>>>15)+d|0;b+=(c&d|~c&a)+k[15]+1236535329|0;b=(b<<22|b>>>10)+c|0;a+=(b&d|c&~d)+k[1]-165796510|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[6]-1069501632|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[11]+643717713|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[0]-373897302|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[5]-701558691|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[10]+38016083|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[15]-660478335|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[4]-405537848|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[9]+568446438|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[14]-1019803690|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[3]-187363961|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[8]+1163531501|0;b=(b<<20|b>>>12)+c|0;a+=(b&d|c&~d)+k[13]-1444681467|0;a=(a<<5|a>>>27)+b|0;d+=(a&c|b&~c)+k[2]-51403784|0;d=(d<<9|d>>>23)+a|0;c+=(d&b|a&~b)+k[7]+1735328473|0;c=(c<<14|c>>>18)+d|0;b+=(c&a|d&~a)+k[12]-1926607734|0;b=(b<<20|b>>>12)+c|0;a+=(b^c^d)+k[5]-378558|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[8]-2022574463|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[11]+1839030562|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[14]-35309556|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[1]-1530992060|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[4]+1272893353|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[7]-155497632|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[10]-1094730640|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[13]+681279174|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[0]-358537222|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[3]-722521979|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[6]+76029189|0;b=(b<<23|b>>>9)+c|0;a+=(b^c^d)+k[9]-640364487|0;a=(a<<4|a>>>28)+b|0;d+=(a^b^c)+k[12]-421815835|0;d=(d<<11|d>>>21)+a|0;c+=(d^a^b)+k[15]+530742520|0;c=(c<<16|c>>>16)+d|0;b+=(c^d^a)+k[2]-995338651|0;b=(b<<23|b>>>9)+c|0;a+=(c^(b|~d))+k[0]-198630844|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[7]+1126891415|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[14]-1416354905|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[5]-57434055|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[12]+1700485571|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[3]-1894986606|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[10]-1051523|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[1]-2054922799|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[8]+1873313359|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[15]-30611744|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[6]-1560198380|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[13]+1309151649|0;b=(b<<21|b>>>11)+c|0;a+=(c^(b|~d))+k[4]-145523070|0;a=(a<<6|a>>>26)+b|0;d+=(b^(a|~c))+k[11]-1120210379|0;d=(d<<10|d>>>22)+a|0;c+=(a^(d|~b))+k[2]+718787259|0;c=(c<<15|c>>>17)+d|0;b+=(d^(c|~a))+k[9]-343485551|0;b=(b<<21|b>>>11)+c|0;x[0]=a+x[0]|0;x[1]=b+x[1]|0;x[2]=c+x[2]|0;x[3]=d+x[3]|0}function md5blk(s){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=s.charCodeAt(i)+(s.charCodeAt(i+1)<<8)+(s.charCodeAt(i+2)<<16)+(s.charCodeAt(i+3)<<24)}return md5blks}function md5blk_array(a){var md5blks=[],i;for(i=0;i<64;i+=4){md5blks[i>>2]=a[i]+(a[i+1]<<8)+(a[i+2]<<16)+(a[i+3]<<24)}return md5blks}function md51(s){var n=s.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk(s.substring(i-64,i)))}s=s.substring(i-64);length=s.length;tail=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(i=0;i>2]|=s.charCodeAt(i)<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function md51_array(a){var n=a.length,state=[1732584193,-271733879,-1732584194,271733878],i,length,tail,tmp,lo,hi;for(i=64;i<=n;i+=64){md5cycle(state,md5blk_array(a.subarray(i-64,i)))}a=i-64>2]|=a[i]<<(i%4<<3)}tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(state,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=n*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(state,tail);return state}function rhex(n){var s="",j;for(j=0;j<4;j+=1){s+=hex_chr[n>>j*8+4&15]+hex_chr[n>>j*8&15]}return s}function hex(x){var i;for(i=0;i>16)+(y>>16)+(lsw>>16);return msw<<16|lsw&65535}}if(typeof ArrayBuffer!=="undefined"&&!ArrayBuffer.prototype.slice){(function(){function clamp(val,length){val=val|0||0;if(val<0){return Math.max(val+length,0)}return Math.min(val,length)}ArrayBuffer.prototype.slice=function(from,to){var length=this.byteLength,begin=clamp(from,length),end=length,num,target,targetArray,sourceArray;if(to!==undefined){end=clamp(to,length)}if(begin>end){return new ArrayBuffer(0)}num=end-begin;target=new ArrayBuffer(num);targetArray=new Uint8Array(target);sourceArray=new Uint8Array(this,begin,num);targetArray.set(sourceArray);return target}})()}function toUtf8(str){if(/[\u0080-\uFFFF]/.test(str)){str=unescape(encodeURIComponent(str))}return str}function utf8Str2ArrayBuffer(str,returnUInt8Array){var length=str.length,buff=new ArrayBuffer(length),arr=new Uint8Array(buff),i;for(i=0;i>2]|=buff.charCodeAt(i)<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.prototype.reset=function(){this._buff="";this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}};SparkMD5.prototype.setState=function(state){this._buff=state.buff;this._length=state.length;this._hash=state.hash;return this};SparkMD5.prototype.destroy=function(){delete this._hash;delete this._buff;delete this._length};SparkMD5.prototype._finish=function(tail,length){var i=length,tmp,lo,hi;tail[i>>2]|=128<<(i%4<<3);if(i>55){md5cycle(this._hash,tail);for(i=0;i<16;i+=1){tail[i]=0}}tmp=this._length*8;tmp=tmp.toString(16).match(/(.*?)(.{0,8})$/);lo=parseInt(tmp[2],16);hi=parseInt(tmp[1],16)||0;tail[14]=lo;tail[15]=hi;md5cycle(this._hash,tail)};SparkMD5.hash=function(str,raw){return SparkMD5.hashBinary(toUtf8(str),raw)};SparkMD5.hashBinary=function(content,raw){var hash=md51(content),ret=hex(hash);return raw?hexToBinaryString(ret):ret};SparkMD5.ArrayBuffer=function(){this.reset()};SparkMD5.ArrayBuffer.prototype.append=function(arr){var buff=concatenateArrayBuffers(this._buff.buffer,arr,true),length=buff.length,i;this._length+=arr.byteLength;for(i=64;i<=length;i+=64){md5cycle(this._hash,md5blk_array(buff.subarray(i-64,i)))}this._buff=i-64>2]|=buff[i]<<(i%4<<3)}this._finish(tail,length);ret=hex(this._hash);if(raw){ret=hexToBinaryString(ret)}this.reset();return ret};SparkMD5.ArrayBuffer.prototype.reset=function(){this._buff=new Uint8Array(0);this._length=0;this._hash=[1732584193,-271733879,-1732584194,271733878];return this};SparkMD5.ArrayBuffer.prototype.getState=function(){var state=SparkMD5.prototype.getState.call(this);state.buff=arrayBuffer2Utf8Str(state.buff);return state};SparkMD5.ArrayBuffer.prototype.setState=function(state){state.buff=utf8Str2ArrayBuffer(state.buff,true);return SparkMD5.prototype.setState.call(this,state)};SparkMD5.ArrayBuffer.prototype.destroy=SparkMD5.prototype.destroy;SparkMD5.ArrayBuffer.prototype._finish=SparkMD5.prototype._finish;SparkMD5.ArrayBuffer.hash=function(arr,raw){var hash=md51_array(new Uint8Array(arr)),ret=hex(hash);return raw?hexToBinaryString(ret):ret};return SparkMD5}); diff --git a/public/assets/js/addons.js b/public/assets/js/addons.js index d2c1d20..6bfd76e 100644 --- a/public/assets/js/addons.js +++ b/public/assets/js/addons.js @@ -1,3 +1,244 @@ define([], function () { - + if (typeof Config.upload.storage !== 'undefined' && Config.upload.storage === 'alioss') { + require(['upload'], function (Upload) { + //获取文件MD5值 + var getFileMd5 = function (file, cb) { + //如果savekey中未检测到md5,则无需获取文件md5,直接返回upload的uuid + if (!Config.upload.savekey.match(/\{(file)?md5\}/)) { + cb && cb(file.upload.uuid); + return; + } + require(['../addons/alioss/js/spark'], function (SparkMD5) { + var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, + chunkSize = 10 * 1024 * 1024, + chunks = Math.ceil(file.size / chunkSize), + currentChunk = 0, + spark = new SparkMD5.ArrayBuffer(), + fileReader = new FileReader(); + + fileReader.onload = function (e) { + spark.append(e.target.result); + currentChunk++; + if (currentChunk < chunks) { + loadNext(); + } else { + cb && cb(spark.end()); + } + }; + + fileReader.onerror = function () { + console.warn('文件读取错误'); + }; + + function loadNext() { + var start = currentChunk * chunkSize, + end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; + + fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); + } + + loadNext(); + + }); + }; + + var _onInit = Upload.events.onInit; + //初始化中完成判断 + Upload.events.onInit = function () { + _onInit.apply(this, Array.prototype.slice.apply(arguments)); + //如果上传接口不是阿里OSS,则不处理 + if (this.options.url !== Config.upload.uploadurl) { + return; + } + $.extend(this.options, { + //关闭自动处理队列功能 + autoQueue: false, + params: function (files, xhr, chunk) { + var params = Config.upload.multipart; + if (chunk) { + return $.extend({}, params, { + filesize: chunk.file.size, + filename: chunk.file.name, + chunkid: chunk.file.upload.uuid, + chunkindex: chunk.index, + chunkcount: chunk.file.upload.totalChunkCount, + chunksize: this.options.chunkSize, + chunkfilesize: chunk.dataBlock.data.size, + width: chunk.file.width || 0, + height: chunk.file.height || 0, + type: chunk.file.type, + uploadId: chunk.file.uploadId, + key: chunk.file.key, + }); + } else { + params = $.extend({}, params, files[0].params); + params.category = files[0].category || ''; + } + return params; + }, + chunkSuccess: function (chunk, file, response) { + var etag = chunk.xhr.getResponseHeader("ETag").replace(/(^")|("$)/g, ''); + file.etags = file.etags ? file.etags : []; + file.etags[chunk.index] = etag; + }, + chunksUploaded: function (file, done) { + var that = this; + Fast.api.ajax({ + url: "/addons/alioss/index/upload", + data: { + action: 'merge', + filesize: file.size, + filename: file.name, + chunkid: file.upload.uuid, + chunkcount: file.upload.totalChunkCount, + md5: file.md5, + key: file.key, + uploadId: file.uploadId, + etags: file.etags, + category: file.category || '', + aliosstoken: Config.upload.multipart.aliosstoken, + }, + }, function (data, ret) { + done(JSON.stringify(ret)); + return false; + }, function (data, ret) { + file.accepted = false; + that._errorProcessing([file], ret.msg); + return false; + }); + + }, + }); + + var _success = this.options.success; + //先移除已有的事件 + this.off("success", _success).on("success", function (file, response) { + var ret = {code: 0, msg: response}; + try { + if (response) { + ret = typeof response === 'string' ? JSON.parse(response) : response; + } + if (file.xhr.status === 200) { + if (Config.upload.uploadmode === 'client') { + ret = {code: 1, data: {url: '/' + file.key}}; + var url = ret.data.url || ''; + + Fast.api.ajax({ + url: "/addons/alioss/index/notify", + data: {name: file.name, url: url, md5: file.md5, size: file.size, width: file.width || 0, height: file.height || 0, type: file.type, category: file.category || '', aliosstoken: Config.upload.multipart.aliosstoken} + }, function () { + return false; + }, function () { + return false; + }); + } + } else { + console.error(file.xhr); + } + } catch (e) { + console.error(e); + } + _success.call(this, file, ret); + }); + + this.on("addedfile", function (file) { + var that = this; + setTimeout(function () { + if (file.status === 'error') { + return; + } + getFileMd5(file, function (md5) { + var chunk = that.options.chunking && file.size > that.options.chunkSize ? 1 : 0; + var params = $(that.element).data("params") || {}; + var category = typeof params.category !== 'undefined' ? params.category : ($(that.element).data("category") || ''); + category = typeof category === 'function' ? category.call(that, file) : category; + Fast.api.ajax({ + url: "/addons/alioss/index/params", + data: {method: 'POST', category: category, md5: md5, name: file.name, type: file.type, size: file.size, chunk: chunk, chunksize: that.options.chunkSize, aliosstoken: Config.upload.multipart.aliosstoken}, + }, function (data) { + file.md5 = md5; + file.id = data.id; + file.key = data.key; + file.date = data.date; + file.uploadId = data.uploadId; + file.policy = data.policy; + file.signature = data.signature; + file.partsAuthorization = data.partsAuthorization; + file.params = data; + file.category = category; + + if (file.status != 'error') { + //开始上传 + that.enqueueFile(file); + } else { + that.removeFile(file); + } + return false; + }, function () { + that.removeFile(file); + }); + }); + }, 0); + }); + + if (Config.upload.uploadmode === 'client') { + var _method = this.options.method; + var _url = this.options.url; + this.options.method = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + if (!chunk) { + return "POST"; + } else { + return "PUT"; + } + } + return _method; + }; + this.options.url = function (files) { + if (files[0].upload.chunked) { + var chunk = null; + files[0].upload.chunks.forEach(function (item) { + if (item.status === 'uploading') { + chunk = item; + } + }); + var index = chunk.dataBlock.chunkIndex; + // debugger; + this.options.headers = {"Authorization": "OSS " + files[0]['id'] + ":" + files[0]['partsAuthorization'][index], "x-oss-date": files[0]['date']}; + if (!chunk) { + return Config.upload.uploadurl + "/" + files[0].key + "?uploadId=" + files[0].uploadId; + } else { + return Config.upload.uploadurl + "/" + files[0].key + "?partNumber=" + (index + 1) + "&uploadId=" + files[0].uploadId; + } + } + return _url; + }; + this.on("sending", function (file, xhr, formData) { + var that = this; + if (file.upload.chunked) { + var _send = xhr.send; + xhr.send = function () { + var chunk = null; + file.upload.chunks.forEach(function (item) { + if (item.status == 'uploading') { + chunk = item; + } + }); + if (chunk) { + _send.call(xhr, chunk.dataBlock.data); + } + }; + } + }); + } + }; + }); +} + }); \ No newline at end of file