├── tests ├── demo.txt ├── demo.jpg ├── demo.html ├── FileFunctionTest.php ├── helper.php ├── config.php ├── OssTest.php ├── QiniuTest.php ├── UpyunTest.php └── CosTest.php ├── autoload.php ├── .gitignore ├── src ├── Cos │ ├── cos-php-sdk-v4 │ │ ├── qcloudcos │ │ │ ├── error_code.php │ │ │ ├── conf.php │ │ │ ├── libcurl_helper.php │ │ │ ├── auth.php │ │ │ ├── http_client.php │ │ │ ├── libcurl_wrapper.php │ │ │ ├── slice_uploading.php │ │ │ └── cosapi.php │ │ ├── include.php │ │ ├── sample.php │ │ └── README.md │ ├── Cos │ │ ├── Response.php │ │ ├── Auth.php │ │ ├── HttpClient.php │ │ ├── Directory.php │ │ ├── LibcurlWrapper.php │ │ ├── Cos.php │ │ ├── SliceUploading.php │ │ └── File.php │ └── CosAdapter.php ├── Functions │ └── FileFunction.php ├── UploadServiceProvider.php ├── Upyun │ ├── UpyunAdapter.php │ └── UpYun.php ├── Qiniu │ └── QiniuAdapter.php └── Oss │ └── OssAdapter.php ├── phpunit.xml ├── composer.json ├── LICENSE └── README.md /tests/demo.txt: -------------------------------------------------------------------------------- 1 | demo -------------------------------------------------------------------------------- /tests/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tyua07/laravel-upload/HEAD/tests/demo.jpg -------------------------------------------------------------------------------- /tests/demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | demo html 4 | 5 | 6 |

demo body

7 | 8 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | tests 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/conf.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Cos/Cos/Response.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Cos\Cos; 12 | 13 | class Response 14 | { 15 | /** 16 | * 响应的 code 相关 17 | */ 18 | const COSAPI_SUCCESS = 0; 19 | const COSAPI_PARAMS_ERROR = -1; 20 | const COSAPI_NETWORK_ERROR = -2; 21 | const COSAPI_INTEGRITY_ERROR = -3; 22 | 23 | /** 24 | * 解析响应 25 | * 26 | * @param $response 27 | * @return bool|mixed 28 | */ 29 | public static function parseResponse($response) 30 | { 31 | $response = is_array($response) ? $response : json_decode($response, true); 32 | 33 | if ( $response && $response['code'] == self::COSAPI_SUCCESS) { 34 | return $response; 35 | } 36 | 37 | return false; 38 | } 39 | } -------------------------------------------------------------------------------- /tests/FileFunctionTest.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | use PHPUnit\Framework\TestCase; 12 | use Yangyifan\Upload\Functions\FileFunction; 13 | 14 | class FileFunctionTest extends TestCase 15 | { 16 | public function testGetFileMimeType() 17 | { 18 | $this->assertEquals('text/html', FileFunction::getFileMimeType(base_path('demo.html'))); 19 | } 20 | 21 | 22 | public function testGetTmpFile() 23 | { 24 | $dirname = FileFunction::getTmpFile(); 25 | $this->assertTrue(is_file($dirname)); 26 | return $dirname; 27 | } 28 | 29 | /** 30 | * @depends testGetTmpFile 31 | */ 32 | public function testDeleteTmpFile($dirname) 33 | { 34 | $dirname = FileFunction::deleteTmpFile($dirname); 35 | $this->assertFalse(is_file($dirname)); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/libcurl_helper.php: -------------------------------------------------------------------------------- 1 | value fields to post. 6 | * @return $boundary and encoded post fields. 7 | */ 8 | function buildCustomPostFields($fields) { 9 | // invalid characters for "name" and "filename" 10 | static $disallow = array("\0", "\"", "\r", "\n"); 11 | 12 | // initialize body 13 | $body = array(); 14 | 15 | // build normal parameters 16 | foreach ($fields as $key => $value) { 17 | $key = str_replace($disallow, "_", $key); 18 | $body[] = implode("\r\n", array( 19 | "Content-Disposition: form-data; name=\"{$key}\"", 20 | '', 21 | filter_var($value), 22 | )); 23 | } 24 | 25 | // generate safe boundary 26 | do { 27 | $boundary = "---------------------" . md5(mt_rand() . microtime()); 28 | } while (preg_grep("/{$boundary}/", $body)); 29 | 30 | // add boundary for each parameters 31 | foreach ($body as &$part) { 32 | $part = "--{$boundary}\r\n{$part}"; 33 | } 34 | unset($part); 35 | 36 | // add final boundary 37 | $body[] = "--{$boundary}--"; 38 | $body[] = ''; 39 | 40 | return array($boundary, implode("\r\n", $body)); 41 | } 42 | -------------------------------------------------------------------------------- /tests/helper.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | if ( !function_exists('env') ) { 12 | function env($name, $default = ''){ 13 | return isset($_ENV[$name]) ? $_ENV[$name] : $default; 14 | } 15 | } 16 | 17 | if ( !function_exists('base_path') ) { 18 | function base_path($path = null) { 19 | return !is_null($path) ? __DIR__ . '/' . trim($path, '//') : __DIR__ ; 20 | } 21 | } 22 | 23 | if ( !function_exists('dd') ) { 24 | function dd($data) { 25 | var_dump($data);die; 26 | } 27 | } 28 | 29 | if ( !function_exists('config') ) { 30 | function config($config, $name) 31 | { 32 | if (is_null($name)) { 33 | return $config; 34 | } 35 | 36 | if (isset($config[$name])) { 37 | return $config[$name]; 38 | } 39 | 40 | foreach (explode('.', $name) as $segment) { 41 | if (is_array($config) && array_key_exists($segment, $config)) { 42 | $config = $config[$segment]; 43 | } 44 | 45 | } 46 | 47 | return $config; 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/Functions/FileFunction.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Functions; 12 | 13 | 14 | class FileFunction 15 | { 16 | 17 | /** 18 | * 获得文件mime_type 19 | * 20 | * @param $file 21 | * @return bool|mixed 22 | * @author yangyifan 23 | */ 24 | public static function getFileMimeType($file) 25 | { 26 | $finfo = finfo_open(FILEINFO_MIME_TYPE); 27 | if ($finfo) { 28 | $mime_type = finfo_file($finfo, $file); 29 | finfo_close($finfo); 30 | return $mime_type; 31 | } 32 | return false; 33 | } 34 | 35 | /** 36 | * 获得一个临时文件 37 | * 38 | * @return string 39 | * @author yangyifan 40 | */ 41 | public static function getTmpFile() 42 | { 43 | $tmpfname = tempnam("/tmp", "dir"); 44 | chmod($tmpfname, 0777); 45 | return $tmpfname; 46 | } 47 | 48 | /** 49 | * 删除一个临时文件 50 | * 51 | * @param $file_name 52 | * @return bool 53 | * @author yangyifan 54 | */ 55 | public static function deleteTmpFile($file_name) 56 | { 57 | return unlink($file_name); 58 | } 59 | } -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/sample.php: -------------------------------------------------------------------------------- 1 | gz 16 | // 华中 -> sh 17 | // 华北 -> tj 18 | Cosapi::setRegion('gz'); 19 | 20 | // Create folder in bucket. 21 | $ret = Cosapi::createFolder($bucket, $folder); 22 | var_dump($ret); 23 | 24 | // Upload file into bucket. 25 | $ret = Cosapi::upload($bucket, $src, $dst); 26 | var_dump($ret); 27 | 28 | // List folder. 29 | $ret = Cosapi::listFolder($bucket, $folder); 30 | var_dump($ret); 31 | 32 | // Update folder information. 33 | $bizAttr = ""; 34 | $ret = Cosapi::updateFolder($bucket, $folder, $bizAttr); 35 | var_dump($ret); 36 | 37 | // Update file information. 38 | $bizAttr = ''; 39 | $authority = 'eWPrivateRPublic'; 40 | $customerHeaders = array( 41 | 'Cache-Control' => 'no', 42 | 'Content-Type' => 'application/pdf', 43 | 'Content-Language' => 'ch', 44 | ); 45 | $ret = Cosapi::update($bucket, $dst, $bizAttr,$authority, $customerHeaders); 46 | var_dump($ret); 47 | 48 | // Stat folder. 49 | $ret = Cosapi::statFolder($bucket, $folder); 50 | var_dump($ret); 51 | 52 | // Stat file. 53 | $ret = Cosapi::stat($bucket, $dst); 54 | var_dump($ret); 55 | 56 | // Copy file. 57 | $ret = Cosapi::copyFile($bucket, $dst, $dst . '_copy'); 58 | var_dump($ret); 59 | 60 | // Move file. 61 | $ret = Cosapi::moveFile($bucket, $dst, $dst . '_move'); 62 | var_dump($ret); 63 | 64 | // Delete file. 65 | $ret = Cosapi::delFile($bucket, $dst . '_copy'); 66 | var_dump($ret); 67 | $ret = Cosapi::delFile($bucket, $dst . '_move'); 68 | var_dump($ret); 69 | 70 | // Delete folder. 71 | $ret = Cosapi::delFolder($bucket, $folder); 72 | var_dump($ret); 73 | -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/README.md: -------------------------------------------------------------------------------- 1 | cos-php-sdk:php sdk for [腾讯云对象存储服务](https://www.qcloud.com/product/cos.html) 2 | =================================================================================================== 3 | 4 | ### 安装(直接下载源码集成) 5 | 直接从[github](https://github.com/tencentyun/cos-php-sdk-v4)下载源码,然后在您的程序中加载cos-php-sdk-v4/include.php就可以了。 6 | 7 | ### 修改配置 8 | 修改cos-php-sdk-v4/qcloudcos/conf.php内的APPID、SECRET_ID、SECRET_KEY为您的配置。 9 | 10 | ### 示例程序 11 | 请参考sample.php 12 | 13 | ```php 14 | // 包含cos-php-sdk-v4/include.php文件 15 | require('cos-php-sdk-v4/include.php'); 16 | use qcloudcos\Cosapi; 17 | 18 | // 设置COS所在的区域,对应关系如下: 19 | // 华南 -> gz 20 | // 华中 -> sh 21 | // 华北 -> tj 22 | Cosapi::setRegion('gz'); 23 | 24 | // 创建文件夹 25 | $ret = Cosapi::createFolder($bucket, $folder); 26 | var_dump($ret); 27 | 28 | // 上传文件 29 | $ret = Cosapi::upload($bucket, $src, $dst); 30 | var_dump($ret); 31 | 32 | // 目录列表 33 | $ret = Cosapi::listFolder($bucket, $folder); 34 | var_dump($ret); 35 | 36 | // 更新目录信息 37 | $bizAttr = ""; 38 | $ret = Cosapi::updateFolder($bucket, $folder, $bizAttr); 39 | var_dump($ret); 40 | 41 | // 更新文件信息 42 | $bizAttr = ''; 43 | $authority = 'eWPrivateRPublic'; 44 | $customerHeaders = array( 45 | 'Cache-Control' => 'no', 46 | 'Content-Type' => 'application/pdf', 47 | 'Content-Language' => 'ch', 48 | ); 49 | $ret = Cosapi::update($bucket, $dst, $bizAttr, $authority, $customerHeaders); 50 | var_dump($ret); 51 | 52 | // 查询目录信息 53 | $ret = Cosapi::statFolder($bucket, $folder); 54 | var_dump($ret); 55 | 56 | // 查询文件信息 57 | $ret = Cosapi::stat($bucket, $dst); 58 | var_dump($ret); 59 | 60 | // 删除文件 61 | $ret = Cosapi::delFile($bucket, $dst); 62 | var_dump($ret); 63 | 64 | // 删除目录 65 | $ret = Cosapi::delFolder($bucket, $folder); 66 | var_dump($ret); 67 | 68 | // 复制文件 69 | $ret = Cosapi::copyFile($bucket, '/111.txt', '/111_2.txt'); 70 | var_dump($ret); 71 | 72 | // 移动文件 73 | $ret = Cosapi::moveFile($bucket, '/111.txt', '/111_3.txt'); 74 | var_dump($ret); 75 | ``` 76 | -------------------------------------------------------------------------------- /tests/config.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'driver' => 'qiniu', 6 | 'domain' => '', // 你的七牛域名 7 | 'access_key' => '', // AccessKey 8 | 'secret_key' => '', // SecretKey 9 | 'bucket' => '', // Bucket 名字 10 | 'transport' => 'http', // 如果支持 https,请填写 https,如果不支持请填写 http 11 | ], 12 | 13 | 'upyun' => [ 14 | 'driver' => 'upyun', 15 | 'domain' => '', // 你的 upyun 域名 16 | 'username' => '', // UserName 17 | 'password' => '', // Password 18 | 'bucket' => '', // Bucket名字 19 | 'timeout' => 130, // 超时时间 20 | 'endpoint' => null, // 线路 21 | 'transport' => 'http', // 如果支持 https,请填写 https,如果不支持请填写 http 22 | ], 23 | 24 | 'oss' => [ 25 | 'driver' => 'oss', 26 | 'accessKeyId' => '', 27 | 'accessKeySecret' => '', 28 | 'endpoint' => '', 29 | 'isCName' => false, 30 | 'securityToken' => null, 31 | 'bucket' => '', 32 | 'timeout' => '5184000', 33 | 'connectTimeout' => '10', 34 | 'transport' => 'http', // 如果支持 https,请填写 https,如果不支持请填写 http 35 | 'max_keys' => 1000, // max-keys 用于限定此次返回 object 的最大数,如果不设定,默认为100,max-keys 取值不能大于 1000 36 | ], 37 | 38 | 'cos' => [ 39 | 'driver' => 'cos', 40 | 'domain' => '', // 你的 COS 域名 41 | 'app_id' => '', 42 | 'secret_id' => '', 43 | 'secret_key' => '', 44 | 'region' => 'gz', // 设置COS所在的区域 45 | 'transport' => 'http', // 如果支持 https,请填写 https,如果不支持请填写 http 46 | 'sign_type' => 'once', // 签名是否允许多次 [once => '单次', 'repeatedly' => '多次'] 47 | 'timeout' => 60, // 超时时间 48 | 'bucket' => '', 49 | ], 50 | ]; 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/Cos/Cos/Auth.php: -------------------------------------------------------------------------------- 1 | createSignature($expiration, $bucket, null); 19 | } else { 20 | if (preg_match('/^\//', $filepath) == 0) { 21 | $filepath = '/' . $filepath; 22 | } 23 | 24 | return $this->createSignature($expiration, $bucket, $filepath); 25 | } 26 | } 27 | 28 | /** 29 | * 创建单次签名 30 | * 31 | * @param string $bucket bucket 名称 32 | * @param string $filepath 文件路径 33 | * @return string 34 | */ 35 | protected function createNonreusableSignature($bucket, $filepath) 36 | { 37 | if (preg_match('/^\//', $filepath) == 0) { 38 | $filepath = '/' . $filepath; 39 | } 40 | 41 | $fileId = '/' . $this->config->get('app_id') . '/' . $bucket . $filepath; 42 | 43 | return $this->createSignature(0, $bucket, $fileId); 44 | } 45 | 46 | /** 47 | * 创建签名 48 | * 49 | * @param int $expiration 时间戳 50 | * @param string $bucket bucket 名称 51 | * @param string $fileId 文件路径 52 | * @return string 53 | */ 54 | private function createSignature($expiration, $bucket, $fileId) 55 | { 56 | $appId = $this->config->get('app_id'); 57 | $secretId = $this->config->get('secret_id'); 58 | $secretKey = $this->config->get('secret_key'); 59 | $now = time(); 60 | $random = rand(); 61 | $plainText = "a=$appId&k=$secretId&e=$expiration&t=$now&r=$random&f=$fileId&b=$bucket"; 62 | $bin = hash_hmac('SHA1', $plainText, $secretKey, true); 63 | $bin = $bin.$plainText; 64 | $signature = base64_encode($bin); 65 | 66 | return $signature; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/UploadServiceProvider.php: -------------------------------------------------------------------------------- 1 | extendQiniuStorage(); 23 | 24 | // 实现 upyun 文件系统 25 | $this->extendUpyunStorage(); 26 | 27 | // 实现 oss 文件系统 28 | $this->extendOssStorage(); 29 | 30 | // 实现 oss 文件系统 31 | $this->extendCosStorage(); 32 | } 33 | 34 | /** 35 | * Register any application services. 36 | * 37 | * This service provider is a great spot to register your various container 38 | * bindings with the application. As you can see, we are registering our 39 | * "Registrar" implementation here. You can add your own bindings too! 40 | * 41 | * @return void 42 | */ 43 | public function register() 44 | { 45 | 46 | } 47 | 48 | /** 49 | * 实现七牛文件系统 50 | * 51 | * @author yangyifan 52 | */ 53 | protected function extendQiniuStorage() 54 | { 55 | \Storage::extend('qiniu', function($app, $config){ 56 | return new Filesystem(new QiniuAdapter($config), $config); 57 | }); 58 | } 59 | 60 | /** 61 | * 实现 upyun 文件系统 62 | * 63 | * @author yangyifan 64 | */ 65 | protected function extendUpyunStorage() 66 | { 67 | \Storage::extend('upyun', function($app, $config){ 68 | return new Filesystem(new UpyunAdapter($config), $config); 69 | }); 70 | } 71 | 72 | /** 73 | * 实现 Oss 文件系统 74 | * 75 | * @author yangyifan 76 | */ 77 | protected function extendOssStorage() 78 | { 79 | \Storage::extend('oss', function($app, $config){ 80 | return new Filesystem(new OssAdapter($config), $config); 81 | }); 82 | } 83 | 84 | /** 85 | * 实现 Cos 文件系统 86 | * 87 | * @author yangyifan 88 | */ 89 | protected function extendCosStorage() 90 | { 91 | \Storage::extend('cos', function($app, $config){ 92 | return new Filesystem(new CosAdapter($config), $config); 93 | }); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/auth.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Cos\Cos; 12 | 13 | trait Directory 14 | { 15 | /* 16 | * 创建目录 17 | * 18 | * @param string $bucket bucket名称 19 | * @param string $folder 目录路径 20 | * @param string $bizAttr 目录属性 21 | */ 22 | public function createFolder($bucket, $folder, $bizAttr = null) 23 | { 24 | if (!self::isValidPath($folder)) { 25 | return array( 26 | 'code' => Response::COSAPI_PARAMS_ERROR, 27 | 'message' => 'folder ' . $folder . ' is not a valid folder name', 28 | 'data' => array() 29 | ); 30 | } 31 | 32 | $folder = self::normalizerPath($folder, True); 33 | $folder = self::cosUrlEncode($folder); 34 | $expired = time() + self::EXPIRED_SECONDS; 35 | $url = self::generateResUrl($bucket, $folder); 36 | $signature = $this->createReusableSignature($expired, $bucket); 37 | 38 | $data = array( 39 | 'op' => 'create', 40 | 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), 41 | ); 42 | 43 | $data = json_encode($data); 44 | 45 | $req = [ 46 | 'url' => $url, 47 | 'method' => 'post', 48 | 'timeout' => self::$timeout, 49 | 'data' => $data, 50 | 'header' => [ 51 | 'Authorization: ' . $signature, 52 | 'Content-Type: application/json', 53 | ], 54 | ]; 55 | 56 | return self::parseRequest($req); 57 | } 58 | 59 | /* 60 | * 目录列表 61 | * @param string $bucket bucket名称 62 | * @param string $path 目录路径,sdk会补齐末尾的 '/' 63 | * @param int $num 拉取的总数 64 | * @param string $pattern eListBoth,ListDirOnly,eListFileOnly 默认both 65 | * @param int $order 默认正序(=0), 填1为反序, 66 | * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 67 | */ 68 | public function listFolder( 69 | $bucket, $folder, $num = 20, 70 | $pattern = 'eListBoth', $order = 0, 71 | $context = null) { 72 | $folder = self::normalizerPath($folder, True); 73 | 74 | return $this->listBase($bucket, $folder, $num, $pattern, $order, $context); 75 | } 76 | 77 | /* 78 | * 目录列表(前缀搜索) 79 | * @param string $bucket bucket名称 80 | * @param string $prefix 列出含此前缀的所有文件 81 | * @param int $num 拉取的总数 82 | * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly 83 | * @param int $order 默认正序(=0), 填1为反序, 84 | * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 85 | */ 86 | public static function prefixSearch( 87 | $bucket, $prefix, $num = 20, 88 | $pattern = 'eListBoth', $order = 0, 89 | $context = null) { 90 | $path = self::normalizerPath($prefix); 91 | 92 | return self::listBase($bucket, $prefix, $num, $pattern, $order, $context); 93 | } 94 | 95 | /* 96 | * 目录更新 97 | * @param string $bucket bucket名称 98 | * @param string $folder 文件夹路径,SDK会补齐末尾的 '/' 99 | * @param string $bizAttr 目录属性 100 | */ 101 | public static function updateFolder($bucket, $folder, $bizAttr = null) { 102 | $folder = self::normalizerPath($folder, True); 103 | 104 | return self::updateBase($bucket, $folder, $bizAttr); 105 | } 106 | 107 | /* 108 | * 查询目录信息 109 | * @param string $bucket bucket名称 110 | * @param string $folder 目录路径 111 | */ 112 | public static function statFolder($bucket, $folder) { 113 | $folder = self::normalizerPath($folder, True); 114 | 115 | return self::statBase($bucket, $folder); 116 | } 117 | 118 | /* 119 | * 删除目录 120 | * @param string $bucket bucket名称 121 | * @param string $folder 目录路径 122 | * 注意不能删除bucket下根目录/ 123 | */ 124 | public function delFolder($bucket, $folder) 125 | { 126 | if (empty($bucket) || empty($folder)) { 127 | return array( 128 | 'code' => Response::COSAPI_PARAMS_ERROR, 129 | 'message' => 'bucket or path is empty'); 130 | } 131 | 132 | $folder = self::normalizerPath($folder, True); 133 | 134 | return $this->delBase($bucket, $folder); 135 | } 136 | } -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/http_client.php: -------------------------------------------------------------------------------- 1 | getMetadata($image2)); //判断文件是否存在 27 | dump($drive->geturl($image2)); //获得文件的 url 28 | dump($drive->has($image2)); //判断文件是否存在 29 | dump($drive->listContents('')); //列出文件列表 30 | dump($drive->getSize($image2)); //获得图片大小 31 | dump($drive->getMimetype($image2)); //获得图片mime类型 32 | dump($drive->getTimestamp($image2)); //获得图片上传时间戳 33 | dump($drive->read($image3)); //获得文件信息 34 | dump($drive->readStream($image3)); //获得文件信息 35 | dump($drive->rename($image3, '4.txt/')); //重命名文件 36 | dump($drive->copy('4.txt/', '/txt/5.txt')); //复制文件 37 | dump($drive->delete('/txt/5.txt')); //删除文件 38 | dump ($drive->write("/txt/4.txt", $drive->read("/4.txt")) ); //上传文件 39 | dump($drive->write("/test2.txt", "111222")); //上传文件 40 | dump($drive->deleteDir('txt/')); //删除文件夹 41 | dump($drive->createDir('test3/')); //创建文件夹 42 | $handle = fopen('/tmp/email.png', 'r'); 43 | dump ($drive->writeStream("/write/test3.png", $handle ) ); //上传文件(文件流方式) 44 | dump ($drive->writeStream("/test6.png", $drive->readStream('/write/test3.png') ) ); //上传文件(文件流方式) 45 | 46 | ``` 47 | 48 | >注意:详细使用,可以参考单元测试里面的代码。 49 | 50 | ###### 配置信息 51 | 52 | >注意:请在 ```config\filesystems.php``` 中的 ```disks``` 数组下面加入以下配置。 53 | 54 | ``` 55 | 56 | 'qiniu' => [ 57 | 'driver' => 'qiniu', 58 | 'domain' => '',//你的七牛域名 59 | 'access_key' => '',//AccessKey 60 | 'secret_key' => '',//SecretKey 61 | 'bucket' => '',//Bucket名字 62 | 'transport' => 'http',//如果支持https,请填写https,如果不支持请填写http 63 | ], 64 | 65 | 'upyun' => [ 66 | 'driver' => 'upyun', 67 | 'domain' => '',//你的upyun域名 68 | 'username' => '',//UserName 69 | 'password' => '',//Password 70 | 'bucket' => '',//Bucket名字 71 | 'timeout' => 130,//超时时间 72 | 'endpoint' => null,//线路 73 | 'transport' => 'http',//如果支持https,请填写https,如果不支持请填写http 74 | ], 75 | 76 | 'oss' => [ 77 | 'driver' => 'oss', 78 | 'accessKeyId' => '', 79 | 'accessKeySecret' => '', 80 | 'endpoint' => '', 81 | 'isCName' => false, 82 | 'securityToken' => null, 83 | 'bucket' => '', 84 | 'timeout' => '5184000', 85 | 'connectTimeout' => '10', 86 | 'transport' => 'http',//如果支持https,请填写https,如果不支持请填写http 87 | 'max_keys' => 1000,//max-keys用于限定此次返回object的最大数,如果不设定,默认为100,max-keys取值不能大于1000 88 | ], 89 | 90 | 'cos' => [ 91 | 'driver' => 'cos', 92 | 'domain' => '', // 你的 COS 域名 93 | 'app_id' => '', 94 | 'secret_id' => '', 95 | 'secret_key' => '', 96 | 'region' => 'gz', // 设置COS所在的区域 97 | 'transport' => 'http', // 如果支持 https,请填写 https,如果不支持请填写 http 98 | 'timeout' => 60, // 超时时间 99 | 'bucket' => '', 100 | ], 101 | ``` 102 | 103 | ###### 其他 104 | 105 | * 如果需要支持其他的上传引擎,请联系我,如果我有空,我会去扩展,希望这个能帮助大家开发,谢谢,有问题pr我,或者邮件联系我,我的邮箱是:yangyifanphp@gmail.com。 106 | 107 | ###### 更新 108 | 109 | ###### 2016-12-03 110 | * 新增单元测试 111 | 112 | ###### 2016-12-03 113 | * 新增腾讯 COS 对象存储(如果可以不要去用,坑有点多。) 114 | * 新增 getUrl 方法 115 | 116 | ###### 计划 117 | * 新增分片上传 118 | * 继续完善更多场景的单元测试 119 | 120 | ###### 协议 121 | 122 | MIT -------------------------------------------------------------------------------- /tests/OssTest.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | use PHPUnit\Framework\TestCase; 12 | use Yangyifan\Upload\Oss\OssAdapter; 13 | 14 | class OssTest extends TestCase 15 | { 16 | /** 17 | * 上传引擎 18 | * 19 | * @var \Yangyifan\Upload\Oss\OssAdapter 20 | */ 21 | protected static $drive; 22 | 23 | /** 24 | * 配置信息 25 | * 26 | * @var Config 27 | */ 28 | protected static $config; 29 | 30 | public static function setUpBeforeClass() 31 | { 32 | $ossConfig = array_merge(config(require base_path('/config.php'), 'oss'), require_once base_path('/ossConfig.php')); 33 | static::$drive = new Yangyifan\Upload\Oss\OssAdapter($ossConfig); 34 | static::$config = new \League\Flysystem\Config($ossConfig); 35 | 36 | //上传资源 37 | if ( !static::$drive->has('demo.txt') ) { 38 | static::$drive->write('demo.txt', file_get_contents(base_path('/demo.txt')), static::$config); 39 | } 40 | 41 | if ( !static::$drive->has('demo.jpg') ) { 42 | static::$drive->write('demo.jpg', file_get_contents(base_path('/demo.jpg')), static::$config); 43 | } 44 | } 45 | 46 | public static function tearDownAfterClass() 47 | { 48 | self::$config = NULL; 49 | self::$drive = NULL; 50 | } 51 | 52 | //测试 Debug 53 | public function testgetMetadata() 54 | { 55 | return static::$drive->getMetadata('demo.txt'); 56 | } 57 | 58 | public function testHas() 59 | { 60 | $this->assertEquals(true, static::$drive->has('demo.txt')); 61 | $this->assertEquals(false, static::$drive->has('demo1.txt')); 62 | } 63 | 64 | public function testListContents() 65 | { 66 | $this->assertNotEmpty(static::$drive->listContents('/')); 67 | } 68 | 69 | public function testGetSizes() 70 | { 71 | $size = static::$drive->getSize('demo.txt')['size']; 72 | 73 | $this->assertEquals($size, 4); 74 | } 75 | 76 | public function testGetMimetype() 77 | { 78 | $mimetype1 = static::$drive->getMimetype('demo.txt')['mimetype']; 79 | $mimetype2 = static::$drive->getMimetype('demo.jpg')['mimetype']; 80 | 81 | $this->assertEquals($mimetype1, 'text/plain'); 82 | $this->assertEquals($mimetype2, 'image/jpeg'); 83 | } 84 | 85 | public function testGetTimestamp() 86 | { 87 | $timestamp1 = static::$drive->getTimestamp('demo.txt')['timestamp']; 88 | $timestamp2 = static::$drive->getTimestamp('demo.jpg')['timestamp']; 89 | 90 | // $this->assertEquals($timestamp1, '1480663159'); 91 | // $this->assertEquals($timestamp2, '1480663159'); 92 | } 93 | 94 | public function testRead() 95 | { 96 | $content = static::$drive->read('demo.txt')['contents']; 97 | 98 | $this->assertEquals($content, 'demo'); 99 | } 100 | 101 | public function testReadStream() 102 | { 103 | $resource = static::$drive->readStream('demo.txt')['stream']; 104 | 105 | $content = fgets($resource); 106 | fclose($resource); 107 | 108 | $this->assertEquals($content, 'demo'); 109 | } 110 | 111 | public function testWrite() 112 | { 113 | $this->assertTrue(static::$drive->write('demo.php', '', static::$config)); 114 | } 115 | 116 | public function testWriteStream() 117 | { 118 | $file = base_path('/demo.html'); 119 | $config = static::$config; 120 | $handle = fopen($file, 'r'); 121 | $config->set('mimetype', \Yangyifan\Upload\Functions\FileFunction::getFileMimeType($file)); 122 | $this->assertTrue(static::$drive->writeStream('demo.html', $handle, $config)); 123 | fclose($handle); 124 | } 125 | 126 | public function testRename() 127 | { 128 | if (static::$drive->has('demo.php') && !static::$drive->has('demoClass.php')) { 129 | $this->assertTrue(static::$drive->rename('demo.php', 'demoClass.php')); 130 | } 131 | 132 | } 133 | 134 | public function testCopy() 135 | { 136 | if (!static::$drive->has('app/demoClass1.php') && !static::$drive->has('app/demoClass2.php') && static::$drive->has('demoClass.php')) { 137 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo1Class.php')); 138 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo2Class.php')); 139 | } 140 | } 141 | 142 | public function testDelete() 143 | { 144 | if (static::$drive->has('app/demoClass1.php')) { 145 | $this->assertTrue(static::$drive->delete('app/demoClass1.php')); 146 | } 147 | } 148 | 149 | public function testDeleteDir() 150 | { 151 | $this->assertTrue(static::$drive->deleteDir('app')); 152 | } 153 | 154 | 155 | public function testcreateDir() 156 | { 157 | $this->assertTrue(static::$drive->createDir('/test/11212', static::$config), true); 158 | } 159 | 160 | /** 161 | * 获得相关对象 162 | * 163 | */ 164 | public function testGetUploadManager() 165 | { 166 | static::$drive->getInstance(); 167 | 168 | } 169 | } -------------------------------------------------------------------------------- /tests/QiniuTest.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | use PHPUnit\Framework\TestCase; 12 | use Yangyifan\Upload\Qiniu\QiniuAdapter; 13 | 14 | class QiniuTest extends TestCase 15 | { 16 | /** 17 | * 上传引擎 18 | * 19 | * @var QiniuAdapter 20 | */ 21 | protected static $drive; 22 | 23 | /** 24 | * 配置信息 25 | * 26 | * @var Config 27 | */ 28 | protected static $config; 29 | 30 | public static function setUpBeforeClass() 31 | { 32 | $qiniuConfig = array_merge(config(require base_path('/config.php'), 'qiniu'), require_once base_path('/qiniuConfig.php')); 33 | static::$drive = new QiniuAdapter($qiniuConfig); 34 | static::$config = new \League\Flysystem\Config($qiniuConfig); 35 | 36 | //上传资源 37 | if ( !static::$drive->has('demo.txt') ) { 38 | static::$drive->write('demo.txt', file_get_contents(base_path('/demo.txt')), static::$config); 39 | } 40 | 41 | if ( !static::$drive->has('demo.jpg') ) { 42 | static::$drive->write('demo.jpg', file_get_contents(base_path('/demo.jpg')), static::$config); 43 | } 44 | } 45 | 46 | public static function tearDownAfterClass() 47 | { 48 | self::$config = NULL; 49 | self::$drive = NULL; 50 | } 51 | 52 | //测试 Debug 53 | public function testgetMetadata() 54 | { 55 | return static::$drive->getMetadata('demo.txt'); 56 | } 57 | 58 | public function testHas() 59 | { 60 | $this->assertEquals(true, static::$drive->has('demo.txt')); 61 | $this->assertEquals(false, static::$drive->has('demo1.txt')); 62 | } 63 | 64 | public function testListContents() 65 | { 66 | $this->assertNotEmpty(static::$drive->listContents('/')); 67 | } 68 | 69 | public function testGetSizes() 70 | { 71 | $size = static::$drive->getSize('demo.txt')['size']; 72 | 73 | $this->assertEquals($size, 4); 74 | } 75 | 76 | public function testGetMimetype() 77 | { 78 | $mimetype1 = static::$drive->getMimetype('demo.txt')['mimetype']; 79 | $mimetype2 = static::$drive->getMimetype('demo.jpg')['mimetype']; 80 | 81 | $this->assertEquals($mimetype1, 'text/plain'); 82 | $this->assertEquals($mimetype2, 'image/jpeg'); 83 | } 84 | 85 | public function testGetTimestamp() 86 | { 87 | $timestamp1 = static::$drive->getTimestamp('demo.txt')['timestamp']; 88 | $timestamp2 = static::$drive->getTimestamp('demo.jpg')['timestamp']; 89 | 90 | // $this->assertEquals($timestamp1, '1480663159'); 91 | // $this->assertEquals($timestamp2, '1480663159'); 92 | } 93 | 94 | public function testRead() 95 | { 96 | $content = static::$drive->read('demo.txt')['contents']; 97 | 98 | $this->assertEquals($content, 'demo'); 99 | } 100 | 101 | public function testReadStream() 102 | { 103 | $resource = static::$drive->readStream('demo.txt')['stream']; 104 | 105 | $content = fgets($resource); 106 | fclose($resource); 107 | 108 | $this->assertEquals($content, 'demo'); 109 | } 110 | 111 | public function testWrite() 112 | { 113 | $this->assertTrue(static::$drive->write('demo.php', '', static::$config)); 114 | } 115 | 116 | public function testWriteStream() 117 | { 118 | $file = base_path('/demo.html'); 119 | $config = static::$config; 120 | $handle = fopen($file, 'r'); 121 | $this->assertTrue(static::$drive->writeStream('/demo.html', $handle, $config)); 122 | fclose($handle); 123 | } 124 | 125 | public function testRename() 126 | { 127 | if (static::$drive->has('demo.php') && !static::$drive->has('demoClass.php')) { 128 | $this->assertTrue(static::$drive->rename('demo.php', 'demoClass.php')); 129 | } 130 | 131 | } 132 | 133 | public function testCopy() 134 | { 135 | if (!static::$drive->has('app/demoClass1.php') && !static::$drive->has('app/demoClass2.php') && static::$drive->has('demoClass.php')) { 136 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo1Class.php')); 137 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo2Class.php')); 138 | } 139 | } 140 | 141 | public function testDelete() 142 | { 143 | if (static::$drive->has('app/demoClass1.php')) { 144 | $this->assertTrue(static::$drive->delete('app/demoClass1.php')); 145 | } 146 | } 147 | 148 | public function testDeleteDir() 149 | { 150 | $this->assertTrue(static::$drive->deleteDir('app')); 151 | } 152 | 153 | //没有实现该方法 154 | public function testcreateDir() 155 | { 156 | $this->assertTrue(static::$drive->createDir('', static::$config), true); 157 | } 158 | 159 | /** 160 | * 获得相关对象 161 | * 162 | */ 163 | public function testGetUploadManager() 164 | { 165 | static::$drive->getInstance(); 166 | 167 | } 168 | 169 | /** 170 | * 获得 url 171 | * 172 | */ 173 | public function testGetUrl() 174 | { 175 | (static::$drive->getUrl('demo.txt')); 176 | } 177 | } -------------------------------------------------------------------------------- /tests/UpyunTest.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | use PHPUnit\Framework\TestCase; 12 | use Yangyifan\Upload\Upyun\UpyunAdapter; 13 | 14 | class UpyunTest extends TestCase 15 | { 16 | /** 17 | * 上传引擎 18 | * 19 | * @var \Yangyifan\Upload\Upyun\UpyunAdapter 20 | */ 21 | protected static $drive; 22 | 23 | /** 24 | * 配置信息 25 | * 26 | * @var Config 27 | */ 28 | protected static $config; 29 | 30 | public static function setUpBeforeClass() 31 | { 32 | $upyunConfig = array_merge(config(require base_path('/config.php'), 'upyun'), require_once base_path('/upyunConfig.php')); 33 | static::$drive = new UpyunAdapter($upyunConfig); 34 | static::$config = new \League\Flysystem\Config($upyunConfig); 35 | 36 | //上传资源 37 | if ( !static::$drive->has('demo.txt') ) { 38 | static::$drive->write('demo.txt', file_get_contents(base_path('/demo.txt')), static::$config); 39 | } 40 | 41 | if ( !static::$drive->has('demo.jpg') ) { 42 | static::$drive->write('demo.jpg', file_get_contents(base_path('/demo.jpg')), static::$config); 43 | } 44 | 45 | //不知道为什么 upyun 上传的文件不能马上获取 46 | sleep(10); 47 | } 48 | 49 | public static function tearDownAfterClass() 50 | { 51 | self::$config = NULL; 52 | self::$drive = NULL; 53 | } 54 | 55 | //测试 Debug 56 | public function testgetMetadata() 57 | { 58 | return static::$drive->getMetadata('demo.txt'); 59 | } 60 | 61 | public function testHas() 62 | { 63 | $this->assertEquals(true, static::$drive->has('demo.txt')); 64 | $this->assertEquals(false, static::$drive->has('demo1.txt')); 65 | } 66 | 67 | public function testListContents() 68 | { 69 | $this->assertNotEmpty(static::$drive->listContents('/')); 70 | } 71 | 72 | public function testGetSizes() 73 | { 74 | $size = static::$drive->getSize('demo.txt')['size']; 75 | 76 | $this->assertEquals($size, 4); 77 | } 78 | 79 | public function testGetMimetype() 80 | { 81 | $mimetype1 = static::$drive->getMimetype('demo.txt')['mimetype']; 82 | $mimetype2 = static::$drive->getMimetype('demo.jpg')['mimetype']; 83 | 84 | $this->assertEquals($mimetype1, 'text/plain'); 85 | $this->assertEquals($mimetype2, 'image/jpeg'); 86 | } 87 | 88 | public function testGetTimestamp() 89 | { 90 | $timestamp1 = static::$drive->getTimestamp('demo.txt')['timestamp']; 91 | $timestamp2 = static::$drive->getTimestamp('demo.jpg')['timestamp']; 92 | 93 | // $this->assertEquals($timestamp1, '1480663159'); 94 | // $this->assertEquals($timestamp2, '1480663159'); 95 | } 96 | 97 | public function testRead() 98 | { 99 | $content = static::$drive->read('demo.txt')['contents']; 100 | 101 | $this->assertEquals($content, 'demo'); 102 | } 103 | 104 | public function testReadStream() 105 | { 106 | $resource = static::$drive->readStream('demo.txt')['stream']; 107 | 108 | $content = fgets($resource); 109 | fclose($resource); 110 | 111 | $this->assertEquals($content, 'demo'); 112 | } 113 | 114 | public function testWrite() 115 | { 116 | if (!static::$drive->has('demo.txt') ) { 117 | $this->assertTrue(static::$drive->write('demo.txt', '', static::$config)); 118 | } 119 | } 120 | 121 | public function testWriteStream() 122 | { 123 | $file = base_path('/demo.html'); 124 | $config = static::$config; 125 | $handle = fopen($file, 'r'); 126 | $config->set('mimetype', \Yangyifan\Upload\Functions\FileFunction::getFileMimeType($file)); 127 | $this->assertTrue(static::$drive->writeStream('demo.html', $handle, $config)); 128 | fclose($handle); 129 | } 130 | 131 | public function testRename() 132 | { 133 | if (static::$drive->has('demo.php') && !static::$drive->has('demoClass.php')) { 134 | $this->assertTrue(static::$drive->rename('demo.php', 'demoClass.php')); 135 | } 136 | 137 | } 138 | 139 | public function testCopy() 140 | { 141 | if (!static::$drive->has('app/demoClass1.php') && !static::$drive->has('app/demoClass2.php') && static::$drive->has('demoClass.php')) { 142 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo1Class.php')); 143 | $this->assertTrue(static::$drive->copy('demoClass.php', 'app/demo2Class.php')); 144 | } 145 | } 146 | 147 | public function testDelete() 148 | { 149 | if (static::$drive->has('app/demoClass1.php')) { 150 | $this->assertTrue(static::$drive->delete('app/demoClass1.php')); 151 | } 152 | } 153 | 154 | public function testcreateDir() 155 | { 156 | $this->assertTrue(static::$drive->createDir('/test/', static::$config), true); 157 | $this->assertTrue(static::$drive->createDir('/test1/', static::$config), true); 158 | } 159 | 160 | public function testDeleteDir() 161 | { 162 | if (static::$drive->has('test1')) { 163 | $this->assertTrue(static::$drive->deleteDir('/test1')); 164 | } 165 | } 166 | 167 | /** 168 | * 获得相关对象 169 | * 170 | */ 171 | public function testGetUploadManager() 172 | { 173 | static::$drive->getInstance(); 174 | 175 | } 176 | 177 | /** 178 | * 获得 url 179 | * 180 | */ 181 | public function testGetUrl() 182 | { 183 | (static::$drive->getUrl('demo.txt')); 184 | } 185 | } -------------------------------------------------------------------------------- /tests/CosTest.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | use PHPUnit\Framework\TestCase; 12 | use Yangyifan\Upload\Cos\CosAdapter; 13 | 14 | class CosTest extends TestCase 15 | { 16 | /** 17 | * 上传引擎 18 | * 19 | * @var CosAdapter 20 | */ 21 | protected static $drive; 22 | 23 | /** 24 | * 配置信息 25 | * 26 | * @var Config 27 | */ 28 | protected static $config; 29 | 30 | public static function setUpBeforeClass() 31 | { 32 | $cosConfig = array_merge(config(require base_path('/config.php'), 'cos'), require_once base_path('/cosConfig.php')); 33 | static::$drive = new CosAdapter($cosConfig); 34 | static::$config = new \League\Flysystem\Config($cosConfig); 35 | 36 | //上传资源 37 | if ( !static::$drive->has('demo.txt') ) { 38 | static::$drive->write('demo.txt', file_get_contents(base_path('/demo.txt')), static::$config); 39 | } 40 | 41 | if ( !static::$drive->has('demo.jpg') ) { 42 | static::$drive->write('demo.jpg', file_get_contents(base_path('/demo.jpg')), static::$config); 43 | } 44 | if ( !static::$drive->has('demo.zip') ) { 45 | //static::$drive->write('demo.zip', file_get_contents(base_path('/demo.zip')), static::$config); 46 | } 47 | } 48 | 49 | public static function tearDownAfterClass() 50 | { 51 | self::$config = NULL; 52 | self::$drive = NULL; 53 | } 54 | 55 | // 测试 Debug 56 | public function testgetMetadata() 57 | { 58 | return static::$drive->getMetadata('demo.txt'); 59 | } 60 | 61 | public function testHas() 62 | { 63 | $this->assertEquals(true, static::$drive->has('demo.txt')); 64 | $this->assertEquals(false, static::$drive->has('demo1.txt')); 65 | } 66 | 67 | public function testListContents() 68 | { 69 | $this->assertNotEmpty(static::$drive->listContents('')); 70 | } 71 | 72 | public function testGetSizes() 73 | { 74 | $size = static::$drive->getSize('demo.txt')['size']; 75 | $this->assertEquals($size, 4); 76 | } 77 | 78 | public function testGetMimetype() 79 | { 80 | $mimetype1 = static::$drive->getMimetype('demo.txt')['mimetype']; 81 | $mimetype2 = static::$drive->getMimetype('demo.jpg')['mimetype']; 82 | 83 | $this->assertEquals($mimetype1, 'text/plain'); 84 | $this->assertEquals($mimetype2, 'image/jpeg'); 85 | } 86 | 87 | public function testGetTimestamp() 88 | { 89 | $timestamp1 = static::$drive->getTimestamp('demo.txt')['timestamp']; 90 | $timestamp2 = static::$drive->getTimestamp('demo.jpg')['timestamp']; 91 | 92 | //$this->assertEquals($timestamp1, '1482638242'); 93 | //$this->assertEquals($timestamp2, '1482638241'); 94 | } 95 | 96 | public function testRead() 97 | { 98 | $content = static::$drive->read('demo.txt')['contents']; 99 | 100 | $this->assertEquals($content, 'demo'); 101 | } 102 | 103 | public function testReadStream() 104 | { 105 | $resource = static::$drive->readStream('demo.txt')['stream']; 106 | 107 | $content = fgets($resource); 108 | fclose($resource); 109 | 110 | $this->assertEquals($content, 'demo'); 111 | } 112 | 113 | public function testWrite() 114 | { 115 | $this->assertTrue(static::$drive->write('demo.php', '121', static::$config)); 116 | } 117 | 118 | public function testWriteStream() 119 | { 120 | $file = base_path('/demo.html'); 121 | $config = static::$config; 122 | $handle = fopen($file, 'r'); 123 | $this->assertTrue(static::$drive->writeStream('demo.html', $handle, $config)); 124 | fclose($handle); 125 | } 126 | 127 | public function testRename() 128 | { 129 | if (static::$drive->has('/demo.php') && !static::$drive->has('/demoClass.php')) { 130 | $this->assertTrue(static::$drive->rename('/demo.php', '/demoClass.php')); 131 | } 132 | 133 | } 134 | 135 | public function testCopy() 136 | { 137 | if (!static::$drive->has('/app/demoClass1.php') && !static::$drive->has('/app/demoClass2.php') && static::$drive->has('/demoClass.php')) { 138 | $this->assertTrue(static::$drive->copy('/demoClass.php', 'app/demo1Class.php')); 139 | $this->assertTrue(static::$drive->copy('/demoClass.php', 'app/demo2Class.php')); 140 | } 141 | } 142 | 143 | public function testDelete() 144 | { 145 | if (static::$drive->has('app/demoClass1.php')) { 146 | $this->assertTrue(static::$drive->delete('app/demoClass1.php')); 147 | } 148 | 149 | if (static::$drive->has('demoClass.php')) { 150 | $this->assertTrue(static::$drive->delete('demoClass.php')); 151 | } 152 | } 153 | 154 | public function testDeleteDir() 155 | { 156 | $this->assertTrue(static::$drive->deleteDir('app')); 157 | } 158 | 159 | 160 | public function testcreateDir() 161 | { 162 | $this->assertTrue(static::$drive->createDir('/test/123/456/789', static::$config)); 163 | $this->assertTrue(static::$drive->createDir('/test/abc/efg/hij', static::$config)); 164 | $this->assertTrue(static::$drive->deleteDir('/test')); 165 | } 166 | 167 | /** 168 | * 获得相关对象 169 | * 170 | */ 171 | public function testGetUploadManager() 172 | { 173 | static::$drive->getInstance(); 174 | 175 | } 176 | 177 | /** 178 | * 获得 url 179 | * 180 | */ 181 | public function testGetUrl() 182 | { 183 | (static::$drive->getUrl('demo.txt')); 184 | } 185 | 186 | /** 187 | * 删除全部文件 188 | * 189 | */ 190 | public function testDeleteAll() 191 | { 192 | $this->assertTrue(static::$drive->delete('demo.txt')); 193 | $this->assertTrue(static::$drive->delete('demo.jpg')); 194 | $this->assertTrue(static::$drive->delete('demo.html')); 195 | 196 | } 197 | } -------------------------------------------------------------------------------- /src/Cos/Cos/LibcurlWrapper.php: -------------------------------------------------------------------------------- 1 | curlMultiHandle = curl_multi_init(); 30 | $this->idleCurlHandle = array(); 31 | } 32 | 33 | public function __destruct() { 34 | curl_multi_close($this->curlMultiHandle); 35 | foreach ($this->idleCurlHandle as $handle) { 36 | curl_close($handle); 37 | } 38 | $this->idleCurlHandle = array(); 39 | } 40 | 41 | public function startSendingRequest($httpRequest, $done) { 42 | if (count($this->idleCurlHandle) !== 0) { 43 | $curlHandle = array_pop($this->idleCurlHandle); 44 | } else { 45 | $curlHandle = curl_init(); 46 | if ($curlHandle === false) { 47 | return false; 48 | } 49 | } 50 | 51 | curl_setopt($curlHandle, CURLOPT_TIMEOUT_MS, $httpRequest->timeoutMs); 52 | curl_setopt($curlHandle, CURLOPT_URL, $httpRequest->url); 53 | curl_setopt($curlHandle, CURLOPT_HEADER, 1); 54 | curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1); 55 | $headers = $httpRequest->customHeaders; 56 | array_push($headers, 'User-Agent:cos'); 57 | if ($httpRequest->method === 'POST') { 58 | if (defined('CURLOPT_SAFE_UPLOAD')) { 59 | curl_setopt($curlHandle, CURLOPT_SAFE_UPLOAD, true); 60 | } 61 | 62 | curl_setopt($curlHandle, CURLOPT_POST, true); 63 | $arr = buildCustomPostFields($httpRequest->dataToPost); 64 | array_push($headers, 'Expect: 100-continue'); 65 | array_push($headers, 'Content-Type: multipart/form-data; boundary=' . $arr[0]); 66 | curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $arr[1]); 67 | } 68 | curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headers); 69 | 70 | curl_multi_add_handle($this->curlMultiHandle, $curlHandle); 71 | 72 | $this->curlHandleInfo[$curlHandle]['done'] = $done; 73 | $this->curlHandleInfo[$curlHandle]['request'] = $httpRequest; 74 | } 75 | 76 | public function performSendingRequest() { 77 | for (;;) { 78 | $active = null; 79 | 80 | do { 81 | $mrc = curl_multi_exec($this->curlMultiHandle, $active); 82 | $info = curl_multi_info_read($this->curlMultiHandle); 83 | if ($info !== false) { 84 | $this->processResult($info); 85 | } 86 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 87 | 88 | while ($active && $mrc == CURLM_OK) { 89 | if (curl_multi_select($this->curlMultiHandle) == -1) { 90 | usleep(1); 91 | } 92 | 93 | do { 94 | $mrc = curl_multi_exec($this->curlMultiHandle, $active); 95 | $info = curl_multi_info_read($this->curlMultiHandle); 96 | if ($info !== false) { 97 | $this->processResult($info); 98 | } 99 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 100 | } 101 | 102 | if (count($this->curlHandleInfo) == 0) { 103 | break; 104 | } 105 | } 106 | } 107 | 108 | private function processResult($info) { 109 | $result = $info['result']; 110 | $handle = $info['handle']; 111 | $request = $this->curlHandleInfo[$handle]['request']; 112 | $done = $this->curlHandleInfo[$handle]['done']; 113 | $response = new HttpResponse(); 114 | 115 | if ($result !== CURLE_OK) { 116 | $response->curlErrorCode = $result; 117 | $response->curlErrorMessage = curl_error($handle); 118 | 119 | call_user_func($done, $request, $response); 120 | } else { 121 | $responseStr = curl_multi_getcontent($handle); 122 | $headerSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE); 123 | $headerStr = substr($responseStr, 0, $headerSize); 124 | $body = substr($responseStr, $headerSize); 125 | 126 | $response->curlErrorCode = curl_errno($handle); 127 | $response->curlErrorMessage = curl_error($handle); 128 | $response->statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE); 129 | $headLines = explode("\r\n", $headerStr); 130 | foreach ($headLines as $head) { 131 | $arr = explode(':', $head); 132 | if (count($arr) >= 2) { 133 | $response->headers[trim($arr[0])] = trim($arr[1]); 134 | } 135 | } 136 | $response->body = $body; 137 | 138 | call_user_func($done, $request, $response); 139 | } 140 | 141 | unset($this->curlHandleInfo[$handle]); 142 | curl_multi_remove_handle($this->curlMultiHandle, $handle); 143 | 144 | array_push($this->idleCurlHandle, $handle); 145 | } 146 | 147 | private function resetCurl($handle) { 148 | if (function_exists('curl_reset')) { 149 | curl_reset($handle); 150 | } else { 151 | curl_setopt($handler, CURLOPT_URL, ''); 152 | curl_setopt($handler, CURLOPT_HTTPHEADER, array()); 153 | curl_setopt($handler, CURLOPT_POSTFIELDS, array()); 154 | curl_setopt($handler, CURLOPT_TIMEOUT, 0); 155 | curl_setopt($handler, CURLOPT_SSL_VERIFYPEER, false); 156 | curl_setopt($handler, CURLOPT_SSL_VERIFYHOST, 0); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/libcurl_wrapper.php: -------------------------------------------------------------------------------- 1 | curlMultiHandle = curl_multi_init(); 30 | $this->idleCurlHandle = array(); 31 | } 32 | 33 | public function __destruct() { 34 | curl_multi_close($this->curlMultiHandle); 35 | foreach ($this->idleCurlHandle as $handle) { 36 | curl_close($handle); 37 | } 38 | $this->idleCurlHandle = array(); 39 | } 40 | 41 | public function startSendingRequest($httpRequest, $done) { 42 | if (count($this->idleCurlHandle) !== 0) { 43 | $curlHandle = array_pop($this->idleCurlHandle); 44 | } else { 45 | $curlHandle = curl_init(); 46 | if ($curlHandle === false) { 47 | return false; 48 | } 49 | } 50 | 51 | curl_setopt($curlHandle, CURLOPT_TIMEOUT_MS, $httpRequest->timeoutMs); 52 | curl_setopt($curlHandle, CURLOPT_URL, $httpRequest->url); 53 | curl_setopt($curlHandle, CURLOPT_HEADER, 1); 54 | curl_setopt($curlHandle, CURLOPT_RETURNTRANSFER, 1); 55 | $headers = $httpRequest->customHeaders; 56 | array_push($headers, 'User-Agent:'.Conf::getUserAgent()); 57 | if ($httpRequest->method === 'POST') { 58 | if (defined('CURLOPT_SAFE_UPLOAD')) { 59 | curl_setopt($curlHandle, CURLOPT_SAFE_UPLOAD, true); 60 | } 61 | 62 | curl_setopt($curlHandle, CURLOPT_POST, true); 63 | $arr = buildCustomPostFields($httpRequest->dataToPost); 64 | array_push($headers, 'Expect: 100-continue'); 65 | array_push($headers, 'Content-Type: multipart/form-data; boundary=' . $arr[0]); 66 | curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $arr[1]); 67 | } 68 | curl_setopt($curlHandle, CURLOPT_HTTPHEADER, $headers); 69 | 70 | curl_multi_add_handle($this->curlMultiHandle, $curlHandle); 71 | 72 | $this->curlHandleInfo[$curlHandle]['done'] = $done; 73 | $this->curlHandleInfo[$curlHandle]['request'] = $httpRequest; 74 | } 75 | 76 | public function performSendingRequest() { 77 | for (;;) { 78 | $active = null; 79 | 80 | do { 81 | $mrc = curl_multi_exec($this->curlMultiHandle, $active); 82 | $info = curl_multi_info_read($this->curlMultiHandle); 83 | if ($info !== false) { 84 | $this->processResult($info); 85 | } 86 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 87 | 88 | while ($active && $mrc == CURLM_OK) { 89 | if (curl_multi_select($this->curlMultiHandle) == -1) { 90 | usleep(1); 91 | } 92 | 93 | do { 94 | $mrc = curl_multi_exec($this->curlMultiHandle, $active); 95 | $info = curl_multi_info_read($this->curlMultiHandle); 96 | if ($info !== false) { 97 | $this->processResult($info); 98 | } 99 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 100 | } 101 | 102 | if (count($this->curlHandleInfo) == 0) { 103 | break; 104 | } 105 | } 106 | } 107 | 108 | private function processResult($info) { 109 | $result = $info['result']; 110 | $handle = $info['handle']; 111 | $request = $this->curlHandleInfo[$handle]['request']; 112 | $done = $this->curlHandleInfo[$handle]['done']; 113 | $response = new HttpResponse(); 114 | 115 | if ($result !== CURLE_OK) { 116 | $response->curlErrorCode = $result; 117 | $response->curlErrorMessage = curl_error($handle); 118 | 119 | call_user_func($done, $request, $response); 120 | } else { 121 | $responseStr = curl_multi_getcontent($handle); 122 | $headerSize = curl_getinfo($handle, CURLINFO_HEADER_SIZE); 123 | $headerStr = substr($responseStr, 0, $headerSize); 124 | $body = substr($responseStr, $headerSize); 125 | 126 | $response->curlErrorCode = curl_errno($handle); 127 | $response->curlErrorMessage = curl_error($handle); 128 | $response->statusCode = curl_getinfo($handle, CURLINFO_HTTP_CODE); 129 | $headLines = explode("\r\n", $headerStr); 130 | foreach ($headLines as $head) { 131 | $arr = explode(':', $head); 132 | if (count($arr) >= 2) { 133 | $response->headers[trim($arr[0])] = trim($arr[1]); 134 | } 135 | } 136 | $response->body = $body; 137 | 138 | call_user_func($done, $request, $response); 139 | } 140 | 141 | unset($this->curlHandleInfo[$handle]); 142 | curl_multi_remove_handle($this->curlMultiHandle, $handle); 143 | 144 | array_push($this->idleCurlHandle, $handle); 145 | } 146 | 147 | private function resetCurl($handle) { 148 | if (function_exists('curl_reset')) { 149 | curl_reset($handle); 150 | } else { 151 | curl_setopt($handler, CURLOPT_URL, ''); 152 | curl_setopt($handler, CURLOPT_HTTPHEADER, array()); 153 | curl_setopt($handler, CURLOPT_POSTFIELDS, array()); 154 | curl_setopt($handler, CURLOPT_TIMEOUT, 0); 155 | curl_setopt($handler, CURLOPT_SSL_VERIFYPEER, false); 156 | curl_setopt($handler, CURLOPT_SSL_VERIFYHOST, 0); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Cos/Cos/Cos.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Cos\Cos; 12 | 13 | class Cos 14 | { 15 | use Auth, HttpClient, Directory, File; 16 | 17 | const VERSION = 'v4.2.1'; 18 | const API_COSAPI_END_POINT = 'http://region.file.myqcloud.com/files/v2/'; 19 | // Secret id or secret key is not valid. 20 | const AUTH_SECRET_ID_KEY_ERROR = -1; 21 | //计算sign签名的时间参数 22 | const EXPIRED_SECONDS = 180; 23 | //1M 24 | const SLICE_SIZE_1M = 1048576; 25 | //20M 大于20M的文件需要进行分片传输 26 | const MAX_UNSLICE_FILE_SIZE = 20971520; 27 | //失败尝试次数 28 | const MAX_RETRY_TIMES = 3; 29 | 30 | private static $timeout = 60; // HTTP 请求超时时间 31 | private static $region = 'gz'; // 默认的区域 32 | 33 | protected $config; 34 | 35 | 36 | /** 37 | * Cos constructor. 38 | * 39 | * @description 方法说明 40 | * @author @author yangyifan 41 | */ 42 | public function __construct($config) 43 | { 44 | $this->config = new \Yangyifan\Config\Config($config); 45 | } 46 | 47 | /** 48 | * 设置HTTP请求超时时间 49 | * 50 | * @param int $timeout 超时时长 51 | * @return bool 52 | */ 53 | public static function setTimeout($timeout = 60) 54 | { 55 | if (!is_int($timeout) || $timeout < 0) { 56 | return false; 57 | } 58 | 59 | self::$timeout = $timeout; 60 | return true; 61 | } 62 | 63 | /** 64 | * 设置区域 65 | * 66 | * @param $region 67 | */ 68 | public static function setRegion($region) 69 | { 70 | self::$region = $region; 71 | } 72 | 73 | /* 74 | * 内部方法, 规整文件路径 75 | * 76 | * @param string $path 文件路径 77 | * @param string $isfolder 是否为文件夹 78 | * @return string 79 | */ 80 | private static function normalizerPath($path, $isfolder = False) 81 | { 82 | if (preg_match('/^\//', $path) == 0) { 83 | $path = '/' . $path; 84 | } 85 | 86 | if ($isfolder == True) { 87 | if (preg_match('/\/$/', $path) == 0) { 88 | $path = $path . '/'; 89 | } 90 | } 91 | 92 | $path = preg_replace('#/+#', '/', $path); 93 | 94 | return $path; 95 | } 96 | 97 | /* 98 | * 内部公共方法, 发送消息 99 | * 100 | * @param string $req 101 | */ 102 | private static function parseRequest($req) 103 | { 104 | $rsp = HttpClient::sendRequest($req); 105 | 106 | if ($rsp === false) { 107 | return array( 108 | 'code' => Response::COSAPI_NETWORK_ERROR, 109 | 'message' => 'network error', 110 | ); 111 | } 112 | 113 | $info = HttpClient::info(); 114 | $ret = json_decode($rsp, true); 115 | 116 | if ($ret === NULL) { 117 | return array( 118 | 'code' => Response::COSAPI_NETWORK_ERROR, 119 | 'message' => $rsp, 120 | 'data' => array() 121 | ); 122 | } 123 | 124 | return $ret; 125 | } 126 | 127 | /** 128 | * 检查路径是否合法 129 | * 130 | * @param $path 131 | * @return bool 132 | */ 133 | protected static function isValidPath($path) 134 | { 135 | if (strpos($path, '?') !== false) { 136 | return false; 137 | } 138 | if (strpos($path, '*') !== false) { 139 | return false; 140 | } 141 | if (strpos($path, ':') !== false) { 142 | return false; 143 | } 144 | if (strpos($path, '|') !== false) { 145 | return false; 146 | } 147 | if (strpos($path, '\\') !== false) { 148 | return false; 149 | } 150 | if (strpos($path, '<') !== false) { 151 | return false; 152 | } 153 | if (strpos($path, '>') !== false) { 154 | return false; 155 | } 156 | if (strpos($path, '"') !== false) { 157 | return false; 158 | } 159 | 160 | return true; 161 | } 162 | 163 | /* 164 | * 内部公共方法, 路径编码 165 | * 166 | * @param string $path 待编码路径 167 | * @return string 168 | */ 169 | private static function cosUrlEncode($path) 170 | { 171 | return str_replace('%2F', '/', rawurlencode($path)); 172 | } 173 | 174 | /* 175 | * 内部公共方法, 构造URL 176 | * 177 | * @param string $bucket 178 | * @param string $dstPath 179 | * @return string 180 | */ 181 | private function generateResUrl($bucket, $dstPath) 182 | { 183 | $endPoint = self::API_COSAPI_END_POINT; 184 | $endPoint = str_replace('region', self::$region, $endPoint); 185 | 186 | return $endPoint . $this->config->get('app_id') . '/' . $bucket . $dstPath; 187 | } 188 | 189 | /* 190 | * 内部私有方法 191 | * 192 | * @param string $bucket bucket名称 193 | * @param string $path 文件/目录路径路径 194 | * 195 | * @return array 196 | */ 197 | private function delBase($bucket, $path) 198 | { 199 | if ($path == "/") { 200 | return [ 201 | 'code' => Response::COSAPI_PARAMS_ERROR, 202 | 'message' => 'can not delete bucket using api! go to ' . 203 | 'http://console.qcloud.com/cos to operate bucket' 204 | ]; 205 | } 206 | 207 | $path = self::cosUrlEncode($path); 208 | $url = self::generateResUrl($bucket, $path); 209 | $signature = $this->createNonreusableSignature($bucket, $path); 210 | $data = ['op' => 'delete']; 211 | $data = json_encode($data); 212 | 213 | $req = [ 214 | 'url' => $url, 215 | 'method' => 'post', 216 | 'timeout' => self::$timeout, 217 | 'data' => $data, 218 | 'header' => [ 219 | 'Authorization: ' . $signature, 220 | 'Content-Type: application/json', 221 | ], 222 | ]; 223 | 224 | return self::sendRequest($req); 225 | } 226 | 227 | /* 228 | * 内部方法 229 | * @param string $bucket bucket名称 230 | * @param string $path 文件/目录路径 231 | */ 232 | public function statBase($bucket, $path) 233 | { 234 | $path = self::cosUrlEncode($path); 235 | $expired = time() + self::EXPIRED_SECONDS; 236 | $url = self::generateResUrl($bucket, $path); 237 | $signature = $this->createReusableSignature($expired, $bucket); 238 | $data = array('op' => 'stat'); 239 | $url = $url . '?' . http_build_query($data); 240 | 241 | $req = [ 242 | 'url' => $url, 243 | 'method' => 'get', 244 | 'timeout' => self::$timeout, 245 | 'header' => [ 246 | 'Authorization: ' . $signature, 247 | ], 248 | ]; 249 | 250 | return self::sendRequest($req); 251 | } 252 | 253 | /* 254 | * 内部公共函数 255 | * @param string $bucket bucket名称 256 | * @param string $path 文件夹路径 257 | * @param int $num 拉取的总数 258 | * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly 259 | * @param int $order 默认正序(=0), 填1为反序, 260 | * @param string $context 在翻页查询时候用到 261 | */ 262 | private function listBase( 263 | $bucket, $path, $num = 20, $pattern = 'eListBoth', $order = 0, $context = null) 264 | { 265 | $path = self::cosUrlEncode($path); 266 | $expired = time() + self::EXPIRED_SECONDS; 267 | $url = $this->generateResUrl($bucket, $path); 268 | $signature = $this->createReusableSignature($expired, $bucket); 269 | $data = ['op' => 'list',]; 270 | 271 | if (self::isPatternValid($pattern) == false) { 272 | return [ 273 | 'code' => Response::COSAPI_PARAMS_ERROR, 274 | 'message' => 'parameter pattern invalid', 275 | ]; 276 | } 277 | 278 | $data['pattern'] = $pattern; 279 | 280 | if ($order != 0 && $order != 1) { 281 | return [ 282 | 'code' => Response::COSAPI_PARAMS_ERROR, 283 | 'message' => 'parameter order invalid', 284 | ]; 285 | } 286 | $data['order'] = $order; 287 | 288 | if ($num < 0 || $num > 199) { 289 | return [ 290 | 'code' => Response::COSAPI_PARAMS_ERROR, 291 | 'message' => 'parameter num invalid, num need less then 200', 292 | ]; 293 | } 294 | $data['num'] = $num; 295 | 296 | if (isset($context)) { 297 | $data['context'] = $context; 298 | } 299 | 300 | $url = $url . '?' . http_build_query($data); 301 | 302 | $req = [ 303 | 'url' => $url, 304 | 'method' => 'get', 305 | 'timeout' => self::$timeout, 306 | 'header' => [ 307 | 'Authorization: ' . $signature, 308 | ], 309 | ]; 310 | 311 | return self::sendRequest($req); 312 | } 313 | 314 | /** 315 | * 判断pattern值是否正确 316 | * @param string $authority 317 | * @return [type] bool 318 | */ 319 | private static function isPatternValid($pattern) 320 | { 321 | if ($pattern == 'eListBoth' || $pattern == 'eListDirOnly' || $pattern == 'eListFileOnly') { 322 | return true; 323 | } 324 | 325 | return false; 326 | } 327 | 328 | } -------------------------------------------------------------------------------- /src/Cos/Cos/SliceUploading.php: -------------------------------------------------------------------------------- 1 | timeoutMs = $timeoutMs; 39 | $this->maxRetryCount = $maxRetryCount; 40 | $this->errorCode = Response::COSAPI_SUCCESS; 41 | $this->errorMessage = ''; 42 | $this->concurrentTaskNumber = self::DEFAULT_CONCURRENT_TASK_NUMBER; 43 | 44 | $this->offset = 0; 45 | 46 | $this->libcurlWrapper = new LibcurlWrapper(); 47 | } 48 | 49 | public function __destruct() { 50 | } 51 | 52 | public function getLastErrorCode() { 53 | return $this->errorCode; 54 | } 55 | 56 | public function getLastErrorMessage() { 57 | return $this->errorMessage; 58 | } 59 | 60 | public function getRequestId() { 61 | return $this->requestId; 62 | } 63 | 64 | public function getAccessUrl() { 65 | return $this->accessUrl; 66 | } 67 | 68 | public function getResourcePath() { 69 | return $this->resourcePath; 70 | } 71 | 72 | public function getSourceUrl() { 73 | return $this->sourceUrl; 74 | } 75 | 76 | /** 77 | * Return true on success and return false on failure. 78 | */ 79 | public function initUploading( 80 | $signature, $srcFpath, $url, $fileSize, $sliceSize, $bizAttr, $insertOnly) { 81 | $this->signature = $signature; 82 | $this->srcFpath = $srcFpath; 83 | $this->url = $url; 84 | $this->fileSize = $fileSize; 85 | $this->sliceSize = $sliceSize; 86 | 87 | // Clear error so caller can successfully retry. 88 | $this->clearError(); 89 | 90 | $request = array( 91 | 'url' => $url, 92 | 'method' => 'post', 93 | 'timeout' => $this->timeoutMs / 1000, 94 | 'data' => array( 95 | 'op' => 'upload_slice_init', 96 | 'filesize' => $fileSize, 97 | 'slice_size' => $sliceSize, 98 | 'insertOnly' => $insertOnly, 99 | ), 100 | 'header' => array( 101 | 'Authorization: ' . $signature, 102 | ), 103 | ); 104 | 105 | if (isset($bizAttr) && strlen($bizAttr)) { 106 | $request['data']['biz_attr'] = $bizAttr; 107 | } 108 | 109 | $response = $this->sendRequest($request); 110 | if ($response === false) { 111 | return false; 112 | } 113 | $this->session = $response['data']['session']; 114 | 115 | if (isset($response['data']['slice_size'])) { 116 | $this->sliceSize = $response['data']['slice_size']; 117 | } 118 | 119 | if (isset($response['data']['serial_upload']) && $response['data']['serial_upload'] == 1) { 120 | $this->concurrentTaskNumber = 1; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | /** 127 | * Return true on success and return false on failure. 128 | */ 129 | public function performUploading() { 130 | for ($i = 0; $i < $this->concurrentTaskNumber; ++$i) { 131 | if ($this->offset >= $this->fileSize) { 132 | break; 133 | } 134 | 135 | $sliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize); 136 | if ($sliceContent === false) { 137 | $this->setError(Response::COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error'); 138 | return false; 139 | } 140 | 141 | $request = new HttpRequest(); 142 | $request->timeoutMs = $this->timeoutMs; 143 | $request->url = $this->url; 144 | $request->method = 'POST'; 145 | $request->customHeaders = array( 146 | 'Authorization: ' . $this->signature, 147 | ); 148 | $request->dataToPost = array( 149 | 'op' => 'upload_slice_data', 150 | 'session' => $this->session, 151 | 'offset' => $this->offset, 152 | 'filecontent' => $sliceContent, 153 | 'datamd5' => md5($sliceContent), 154 | ); 155 | $request->userData = array( 156 | 'retryCount' => 0, 157 | ); 158 | 159 | $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback')); 160 | 161 | $this->offset += $this->sliceSize; 162 | } 163 | 164 | $this->libcurlWrapper->performSendingRequest(); 165 | 166 | if ($this->errorCode !== Response::COSAPI_SUCCESS) { 167 | return false; 168 | } 169 | 170 | return true; 171 | } 172 | 173 | /** 174 | * Return true on success and return false on failure. 175 | */ 176 | public function finishUploading() { 177 | $request = array( 178 | 'url' => $this->url, 179 | 'method' => 'post', 180 | 'timeout' => $this->timeoutMs / 1000, 181 | 'data' => array( 182 | 'op' => 'upload_slice_finish', 183 | 'session' => $this->session, 184 | 'filesize' => $this->fileSize, 185 | ), 186 | 'header' => array( 187 | 'Authorization: ' . $this->signature, 188 | ), 189 | ); 190 | 191 | $response = $this->sendRequest($request); 192 | if ($response === false) { 193 | return false; 194 | } 195 | 196 | $this->accessUrl = $response['data']['access_url']; 197 | $this->resourcePath = $response['data']['resource_path']; 198 | $this->sourceUrl = $response['data']['source_url']; 199 | 200 | return true; 201 | } 202 | 203 | private function sendRequest($request) { 204 | $response = HttpClient::sendRequest($request); 205 | if ($response === false) { 206 | $this->setError(Response::COSAPI_NETWORK_ERROR, 'network error'); 207 | return false; 208 | } 209 | 210 | $responseJson = json_decode($response, true); 211 | if ($responseJson === NULL) { 212 | $this->setError(Response::COSAPI_NETWORK_ERROR, 'network error'); 213 | return false; 214 | } 215 | 216 | $this->requestId = $responseJson['request_id']; 217 | if ($responseJson['code'] != 0) { 218 | $this->setError($responseJson['code'], $responseJson['message']); 219 | return false; 220 | } 221 | 222 | return $responseJson; 223 | } 224 | 225 | private function clearError() { 226 | $this->errorCode = Response::COSAPI_SUCCESS; 227 | $this->errorMessage = 'success'; 228 | } 229 | 230 | private function setError($errorCode, $errorMessage) { 231 | $this->errorCode = $errorCode; 232 | $this->errorMessage = $errorMessage; 233 | } 234 | 235 | public function uploadCallback($request, $response) { 236 | if ($this->errorCode !== Response::COSAPI_SUCCESS) { 237 | return; 238 | } 239 | 240 | $requestErrorCode = Response::COSAPI_SUCCESS; 241 | $requestErrorMessage = 'success'; 242 | $retryCount = $request->userData['retryCount']; 243 | 244 | $responseJson = json_decode($response->body, true); 245 | if ($responseJson === NULL) { 246 | $requestErrorCode = Response::COSAPI_NETWORK_ERROR; 247 | $requestErrorMessage = 'network error'; 248 | } 249 | 250 | if ($response->curlErrorCode !== CURLE_OK) { 251 | $requestErrorCode = Response::COSAPI_NETWORK_ERROR; 252 | $requestErrorMessage = 'network error: curl errno ' . $response->curlErrorCode; 253 | } 254 | 255 | $this->requestId = $responseJson['request_id']; 256 | if ($responseJson['code'] != 0) { 257 | $requestErrorCode = $responseJson['code']; 258 | $requestErrorMessage = $responseJson['message']; 259 | } 260 | 261 | if (isset($responseJson['data']['datamd5']) && 262 | $responseJson['data']['datamd5'] !== $request->dataToPost['datamd5']) { 263 | $requestErrorCode = Response::COSAPI_INTEGRITY_ERROR; 264 | $requestErrorMessage = 'cosapi integrity error'; 265 | } 266 | 267 | if ($requestErrorCode !== Response::COSAPI_SUCCESS) { 268 | if ($retryCount >= $this->maxRetryCount) { 269 | $this->setError($requestErrorCode, $requestErrorMessage); 270 | } else { 271 | $request->userData['retryCount'] += 1; 272 | $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback')); 273 | } 274 | return; 275 | } 276 | 277 | if ($this->offset >= $this->fileSize) { 278 | return; 279 | } 280 | 281 | // Send next slice. 282 | $nextSliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize); 283 | if ($nextSliceContent === false) { 284 | $this->setError(Response::COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error'); 285 | return; 286 | } 287 | 288 | $nextSliceRequest = new HttpRequest(); 289 | $nextSliceRequest->timeoutMs = $this->timeoutMs; 290 | $nextSliceRequest->url = $this->url; 291 | $nextSliceRequest->method = 'POST'; 292 | $nextSliceRequest->customHeaders = array( 293 | 'Authorization: ' . $this->signature, 294 | ); 295 | $nextSliceRequest->dataToPost = array( 296 | 'op' => 'upload_slice_data', 297 | 'session' => $this->session, 298 | 'offset' => $this->offset, 299 | 'filecontent' => $nextSliceContent, 300 | 'datamd5' => md5($nextSliceContent), 301 | ); 302 | $nextSliceRequest->userData = array( 303 | 'retryCount' => 0, 304 | ); 305 | 306 | $this->libcurlWrapper->startSendingRequest($nextSliceRequest, array($this, 'uploadCallback')); 307 | 308 | $this->offset += $this->sliceSize; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/Upyun/UpyunAdapter.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Upyun; 12 | 13 | use League\Flysystem\Adapter\AbstractAdapter; 14 | use League\Flysystem\Config; 15 | use Yangyifan\Library\PathLibrary; 16 | use Yangyifan\Upload\Functions\FileFunction; 17 | use Exception; 18 | 19 | class UpyunAdapter extends AbstractAdapter 20 | { 21 | /** 22 | * 配置信息 23 | * 24 | * @var 25 | */ 26 | protected $config; 27 | 28 | /** 29 | * upyun上传对象 30 | * 31 | * @var UpYun 32 | */ 33 | protected $upload; 34 | 35 | /** 36 | * 37 | * 文件类型 38 | * 39 | */ 40 | const FILE_TYPE_FILE = 'file';//文件类型为文件 41 | const FILE_TYPE_FOLDER = 'folder';//文件类型是文件夹 42 | 43 | /** 44 | * 构造方法 45 | * 46 | * @param array $config 配置信息 47 | * @author yangyifan 48 | */ 49 | public function __construct($config) 50 | { 51 | $this->config = $config; 52 | 53 | //设置路径前缀 54 | $this->setPathPrefix($this->config['transport'] . '://' . $this->config['domain']); 55 | } 56 | 57 | /** 58 | * 获得Upyun上传对象 59 | * 60 | * @return UpYun 61 | * @author yangyifan 62 | */ 63 | protected function getUpyun() 64 | { 65 | if (!$this->upload) { 66 | $this->upload = new UpYun( 67 | $this->config['bucket'],//空间名称 68 | $this->config['username'],//用户名 69 | $this->config['password'],//密码 70 | $this->config['endpoint'],//线路 71 | $this->config['timeout']//超时时间 72 | ); 73 | } 74 | return $this->upload; 75 | } 76 | 77 | /** 78 | * 获得 Upyun 实例 79 | * 80 | * @return UpYun 81 | */ 82 | public function getInstance() 83 | { 84 | return $this->getUpyun(); 85 | } 86 | 87 | /** 88 | * 格式化路径 89 | * 90 | * @param $path 91 | * @return string 92 | */ 93 | protected static function normalizerPath($path, $is_dir = false) 94 | { 95 | return PathLibrary::normalizerPath($path, $is_dir); 96 | } 97 | 98 | /** 99 | * 判断文件是否存在 100 | * 101 | * @param string $path 102 | * @return bool 103 | * @author yangyifan 104 | */ 105 | public function has($path) 106 | { 107 | return $this->getMetadata($path) != false ? true : false; 108 | } 109 | 110 | /** 111 | * 读取文件 112 | * 113 | * @param $file_name 114 | * @author yangyifan 115 | */ 116 | public function read($path) 117 | { 118 | return ['contents' => file_get_contents($this->applyPathPrefix($path)) ]; 119 | } 120 | 121 | /** 122 | * 获得文件流 123 | * 124 | * @param string $path 125 | * @return array 126 | * @author yangyifan 127 | */ 128 | public function readStream($path) 129 | { 130 | //获得一个临时文件 131 | $tmpfname = FileFunction::getTmpFile(); 132 | 133 | file_put_contents($tmpfname, file_get_contents($this->applyPathPrefix($path)) ); 134 | 135 | $handle = fopen($tmpfname, 'r'); 136 | 137 | //删除临时文件 138 | FileFunction::deleteTmpFile($tmpfname); 139 | 140 | return ['stream' => $handle]; 141 | } 142 | 143 | /** 144 | * 写入文件 145 | * 146 | * @param $file_name 147 | * @param $contents 148 | * @author yangyifan 149 | */ 150 | public function write($path, $contents, Config $config) 151 | { 152 | return $this->getUpyun()->writeFile(static::normalizerPath($path), $contents, $auto_mkdir = true); 153 | } 154 | 155 | /** 156 | * 写入文件流 157 | * 158 | * @param string $path 159 | * @param resource $resource 160 | * @param array $config 161 | */ 162 | public function writeStream($path, $resource, Config $config) 163 | { 164 | $status = $this->getUpyun()->writeFile(static::normalizerPath($path), $resource, true); 165 | 166 | return $status; 167 | } 168 | 169 | /** 170 | * 更新文件 171 | * 172 | * @param string $path 173 | * @param string $contents 174 | * @param array $config 175 | */ 176 | public function update($path, $contents, Config $config) 177 | { 178 | return $this->write($path, $contents, $config); 179 | } 180 | 181 | /** 182 | * 更新文件流 183 | * 184 | * @param string $path 185 | * @param resource $resource 186 | * @param array $config 187 | */ 188 | public function updateStream($path, $resource, Config $config) 189 | { 190 | return $this->writeStream($path, $resource, $config); 191 | } 192 | 193 | /** 194 | * 列出目录文件 195 | * 196 | * @param string $directory 197 | * @param bool|false $recursive 198 | * @return array 199 | * @author yangyifan 200 | */ 201 | public function listContents($directory = '', $recursive = false) 202 | { 203 | try{ 204 | //组合目录 205 | $directory = static::normalizerPath($directory); 206 | 207 | $file_list = $this->getUpyun()->getList($directory); 208 | 209 | if (is_array($file_list) && count($file_list) > 0 ) { 210 | foreach ($file_list as &$file) { 211 | $file['path'] = static::normalizerPath($directory) . DIRECTORY_SEPARATOR . $file['name']; 212 | } 213 | } 214 | return $file_list; 215 | }catch (Exception $e){ 216 | 217 | } 218 | return []; 219 | } 220 | 221 | /** 222 | * 获取资源的元信息,但不返回文件内容 223 | * 224 | * @param $path 225 | * @return mixed 226 | * @author yangyifan 227 | */ 228 | public function getMetadata($path) 229 | { 230 | try { 231 | $file_info = $this->getUpyun()->getFileInfo($path); 232 | if ( !empty($file_info) ) { 233 | return $file_info; 234 | } 235 | }catch (Exception $exception){ 236 | //调试信息 237 | //echo $exception->getMessage(); 238 | } 239 | return false; 240 | } 241 | 242 | /** 243 | * 获得文件大小 244 | * 245 | * @param string $path 246 | * @return array 247 | * @author yangyifan 248 | */ 249 | public function getSize($path) 250 | { 251 | $file_info = $this->getMetadata($path); 252 | return $file_info != false && $file_info['x-upyun-file-size'] > 0 ? [ 'size' => $file_info['x-upyun-file-size'] ] : ['size' => 0]; 253 | } 254 | 255 | /** 256 | * 获得文件Mime类型 257 | * 258 | * @param string $path 259 | * @return array 260 | * @author yangyifan 261 | */ 262 | public function getMimetype($path) 263 | { 264 | //创建一个临时文件 265 | $tmp_file = FileFunction::getTmpFile(); 266 | 267 | file_put_contents($tmp_file, $this->readStream($path)['stream']); 268 | 269 | $mime_type = FileFunction::getFileMimeType($tmp_file); 270 | 271 | //删除临时文件 272 | 273 | FileFunction::deleteTmpFile($tmp_file); 274 | 275 | return ['mimetype' => $mime_type]; 276 | } 277 | 278 | /** 279 | * 获得文件最后修改时间 280 | * 281 | * @param string $path 282 | * @return array 时间戳 283 | * @author yangyifan 284 | */ 285 | public function getTimestamp($path) 286 | { 287 | $file_info = $this->getMetadata($path); 288 | return $file_info != false && !empty($file_info['x-upyun-file-date']) 289 | ? ['timestamp' => $file_info['x-upyun-file-date'] ] 290 | : ['timestamp' => 0]; 291 | } 292 | 293 | /** 294 | * 获得文件模式 (未实现) 295 | * 296 | * @param string $path 297 | * @author yangyifan 298 | */ 299 | public function getVisibility($path) 300 | { 301 | return self::VISIBILITY_PUBLIC; 302 | } 303 | 304 | /** 305 | * 重命名文件 306 | * 307 | * @param $oldname 308 | * @param $newname 309 | * @return boolean 310 | * @author yangyifan 311 | */ 312 | public function rename($path, $newpath) 313 | { 314 | $newpath = static::normalizerPath($newpath); 315 | 316 | $this->writeStream($newpath, $this->readStream($path)['stream'], new Config() ); 317 | 318 | $this->delete($path); 319 | 320 | return true; 321 | } 322 | 323 | /** 324 | * 复制文件 325 | * 326 | * @param $path 327 | * @param $newpath 328 | * @return boolean 329 | * @author yangyifan 330 | */ 331 | public function copy($path, $newpath) 332 | { 333 | $this->writeStream(static::normalizerPath($newpath), $this->readStream(static::normalizerPath($path))['stream'], new Config() ); 334 | 335 | return true; 336 | } 337 | 338 | /** 339 | * 删除文件或者文件夹 340 | * 341 | * @param $path 342 | * @author yangyifan 343 | */ 344 | public function delete($path) 345 | { 346 | return $this->getUpyun()->delete(static::normalizerPath($path)); 347 | } 348 | 349 | /** 350 | * 删除文件夹 351 | * 352 | * @param string $path 353 | * @return mixed 354 | * @author yangyifan 355 | */ 356 | public function deleteDir($path) 357 | { 358 | if ( $this->has($path) ) { 359 | //递归删除全部子文件 360 | $this->recursiveDeleteDir($path); 361 | return true; 362 | } 363 | return false; 364 | } 365 | 366 | /** 367 | * 递归删除全部文件夹 368 | * 369 | * @param $path 370 | * @author yangyifan 371 | */ 372 | protected function recursiveDeleteDir($path) 373 | { 374 | $path = static::normalizerPath($path); 375 | $file_list = $this->listContents($path); 376 | 377 | if ( is_array($file_list) && count($file_list) > 0 ) { 378 | foreach ($file_list as $file) { 379 | //如果是文件,则把文件删除 380 | if ($file['type'] == self::FILE_TYPE_FILE) { 381 | $this->delete($path . $this->pathSeparator . $file['name']); 382 | } else { 383 | $this->recursiveDeleteDir($path . $this->pathSeparator . $file['name']); 384 | } 385 | } 386 | } 387 | $this->getUpyun()->rmDir($path); 388 | } 389 | 390 | /** 391 | * 创建文件夹 392 | * 393 | * @param string $dirname 394 | * @param array $config 395 | * @author yangyifan 396 | */ 397 | public function createDir($dirname, Config $config) 398 | { 399 | $this->getUpyun()->makeDir(static::normalizerPath($dirname, true)); 400 | return true; 401 | } 402 | 403 | /** 404 | * 设置文件模式 (未实现) 405 | * 406 | * @param string $path 407 | * @param string $visibility 408 | * @return bool 409 | * @author yangyifan 410 | */ 411 | public function setVisibility($path, $visibility) 412 | { 413 | return true; 414 | } 415 | 416 | /** 417 | * 获取当前文件的URL访问路径 418 | * 419 | * @param $file 420 | * @param int $expire_at 421 | * @return mixed 422 | */ 423 | public function getUrl($file, $expire_at = 3600) 424 | { 425 | return $this->applyPathPrefix($file); 426 | } 427 | 428 | } -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/slice_uploading.php: -------------------------------------------------------------------------------- 1 | timeoutMs = $timeoutMs; 45 | $this->maxRetryCount = $maxRetryCount; 46 | $this->errorCode = COSAPI_SUCCESS; 47 | $this->errorMessage = ''; 48 | $this->concurrentTaskNumber = self::DEFAULT_CONCURRENT_TASK_NUMBER; 49 | 50 | $this->offset = 0; 51 | 52 | $this->libcurlWrapper = new LibcurlWrapper(); 53 | } 54 | 55 | public function __destruct() { 56 | } 57 | 58 | public function getLastErrorCode() { 59 | return $this->errorCode; 60 | } 61 | 62 | public function getLastErrorMessage() { 63 | return $this->errorMessage; 64 | } 65 | 66 | public function getRequestId() { 67 | return $this->requestId; 68 | } 69 | 70 | public function getAccessUrl() { 71 | return $this->accessUrl; 72 | } 73 | 74 | public function getResourcePath() { 75 | return $this->resourcePath; 76 | } 77 | 78 | public function getSourceUrl() { 79 | return $this->sourceUrl; 80 | } 81 | 82 | /** 83 | * Return true on success and return false on failure. 84 | */ 85 | public function initUploading( 86 | $signature, $srcFpath, $url, $fileSize, $sliceSize, $bizAttr, $insertOnly) { 87 | $this->signature = $signature; 88 | $this->srcFpath = $srcFpath; 89 | $this->url = $url; 90 | $this->fileSize = $fileSize; 91 | $this->sliceSize = $sliceSize; 92 | 93 | // Clear error so caller can successfully retry. 94 | $this->clearError(); 95 | 96 | $request = array( 97 | 'url' => $url, 98 | 'method' => 'post', 99 | 'timeout' => $this->timeoutMs / 1000, 100 | 'data' => array( 101 | 'op' => 'upload_slice_init', 102 | 'filesize' => $fileSize, 103 | 'slice_size' => $sliceSize, 104 | 'insertOnly' => $insertOnly, 105 | ), 106 | 'header' => array( 107 | 'Authorization: ' . $signature, 108 | ), 109 | ); 110 | 111 | if (isset($bizAttr) && strlen($bizAttr)) { 112 | $request['data']['biz_attr'] = $bizAttr; 113 | } 114 | 115 | $response = $this->sendRequest($request); 116 | if ($response === false) { 117 | return false; 118 | } 119 | $this->session = $response['data']['session']; 120 | 121 | if (isset($response['data']['slice_size'])) { 122 | $this->sliceSize = $response['data']['slice_size']; 123 | } 124 | 125 | if (isset($response['data']['serial_upload']) && $response['data']['serial_upload'] == 1) { 126 | $this->concurrentTaskNumber = 1; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | /** 133 | * Return true on success and return false on failure. 134 | */ 135 | public function performUploading() { 136 | for ($i = 0; $i < $this->concurrentTaskNumber; ++$i) { 137 | if ($this->offset >= $this->fileSize) { 138 | break; 139 | } 140 | 141 | $sliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize); 142 | if ($sliceContent === false) { 143 | $this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error'); 144 | return false; 145 | } 146 | 147 | $request = new HttpRequest(); 148 | $request->timeoutMs = $this->timeoutMs; 149 | $request->url = $this->url; 150 | $request->method = 'POST'; 151 | $request->customHeaders = array( 152 | 'Authorization: ' . $this->signature, 153 | ); 154 | $request->dataToPost = array( 155 | 'op' => 'upload_slice_data', 156 | 'session' => $this->session, 157 | 'offset' => $this->offset, 158 | 'filecontent' => $sliceContent, 159 | 'datamd5' => md5($sliceContent), 160 | ); 161 | $request->userData = array( 162 | 'retryCount' => 0, 163 | ); 164 | 165 | $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback')); 166 | 167 | $this->offset += $this->sliceSize; 168 | } 169 | 170 | $this->libcurlWrapper->performSendingRequest(); 171 | 172 | if ($this->errorCode !== COSAPI_SUCCESS) { 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | /** 180 | * Return true on success and return false on failure. 181 | */ 182 | public function finishUploading() { 183 | $request = array( 184 | 'url' => $this->url, 185 | 'method' => 'post', 186 | 'timeout' => $this->timeoutMs / 1000, 187 | 'data' => array( 188 | 'op' => 'upload_slice_finish', 189 | 'session' => $this->session, 190 | 'filesize' => $this->fileSize, 191 | ), 192 | 'header' => array( 193 | 'Authorization: ' . $this->signature, 194 | ), 195 | ); 196 | 197 | $response = $this->sendRequest($request); 198 | if ($response === false) { 199 | return false; 200 | } 201 | 202 | $this->accessUrl = $response['data']['access_url']; 203 | $this->resourcePath = $response['data']['resource_path']; 204 | $this->sourceUrl = $response['data']['source_url']; 205 | 206 | return true; 207 | } 208 | 209 | private function sendRequest($request) { 210 | $response = HttpClient::sendRequest($request); 211 | if ($response === false) { 212 | $this->setError(COSAPI_NETWORK_ERROR, 'network error'); 213 | return false; 214 | } 215 | 216 | $responseJson = json_decode($response, true); 217 | if ($responseJson === NULL) { 218 | $this->setError(COSAPI_NETWORK_ERROR, 'network error'); 219 | return false; 220 | } 221 | 222 | $this->requestId = $responseJson['request_id']; 223 | if ($responseJson['code'] != 0) { 224 | $this->setError($responseJson['code'], $responseJson['message']); 225 | return false; 226 | } 227 | 228 | return $responseJson; 229 | } 230 | 231 | private function clearError() { 232 | $this->errorCode = COSAPI_SUCCESS; 233 | $this->errorMessage = 'success'; 234 | } 235 | 236 | private function setError($errorCode, $errorMessage) { 237 | $this->errorCode = $errorCode; 238 | $this->errorMessage = $errorMessage; 239 | } 240 | 241 | public function uploadCallback($request, $response) { 242 | if ($this->errorCode !== COSAPI_SUCCESS) { 243 | return; 244 | } 245 | 246 | $requestErrorCode = COSAPI_SUCCESS; 247 | $requestErrorMessage = 'success'; 248 | $retryCount = $request->userData['retryCount']; 249 | 250 | $responseJson = json_decode($response->body, true); 251 | if ($responseJson === NULL) { 252 | $requestErrorCode = COSAPI_NETWORK_ERROR; 253 | $requestErrorMessage = 'network error'; 254 | } 255 | 256 | if ($response->curlErrorCode !== CURLE_OK) { 257 | $requestErrorCode = COSAPI_NETWORK_ERROR; 258 | $requestErrorMessage = 'network error: curl errno ' . $response->curlErrorCode; 259 | } 260 | 261 | $this->requestId = $responseJson['request_id']; 262 | if ($responseJson['code'] != 0) { 263 | $requestErrorCode = $responseJson['code']; 264 | $requestErrorMessage = $responseJson['message']; 265 | } 266 | 267 | if (isset($responseJson['data']['datamd5']) && 268 | $responseJson['data']['datamd5'] !== $request->dataToPost['datamd5']) { 269 | $requestErrorCode = COSAPI_INTEGRITY_ERROR; 270 | $requestErrorMessage = 'cosapi integrity error'; 271 | } 272 | 273 | if ($requestErrorCode !== COSAPI_SUCCESS) { 274 | if ($retryCount >= $this->maxRetryCount) { 275 | $this->setError($requestErrorCode, $requestErrorMessage); 276 | } else { 277 | $request->userData['retryCount'] += 1; 278 | $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback')); 279 | } 280 | return; 281 | } 282 | 283 | if ($this->offset >= $this->fileSize) { 284 | return; 285 | } 286 | 287 | // Send next slice. 288 | $nextSliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize); 289 | if ($nextSliceContent === false) { 290 | $this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error'); 291 | return; 292 | } 293 | 294 | $nextSliceRequest = new HttpRequest(); 295 | $nextSliceRequest->timeoutMs = $this->timeoutMs; 296 | $nextSliceRequest->url = $this->url; 297 | $nextSliceRequest->method = 'POST'; 298 | $nextSliceRequest->customHeaders = array( 299 | 'Authorization: ' . $this->signature, 300 | ); 301 | $nextSliceRequest->dataToPost = array( 302 | 'op' => 'upload_slice_data', 303 | 'session' => $this->session, 304 | 'offset' => $this->offset, 305 | 'filecontent' => $nextSliceContent, 306 | 'datamd5' => md5($nextSliceContent), 307 | ); 308 | $nextSliceRequest->userData = array( 309 | 'retryCount' => 0, 310 | ); 311 | 312 | $this->libcurlWrapper->startSendingRequest($nextSliceRequest, array($this, 'uploadCallback')); 313 | 314 | $this->offset += $this->sliceSize; 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /src/Qiniu/QiniuAdapter.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Qiniu; 12 | 13 | use League\Flysystem\Adapter\AbstractAdapter; 14 | use League\Flysystem\Config; 15 | use Qiniu\Storage\ResumeUploader; 16 | use Qiniu\Storage\UploadManager; 17 | use Qiniu\Auth; 18 | use Qiniu\Storage\BucketManager; 19 | use Qiniu\Config AS QiniuConfig; 20 | use Symfony\Component\Finder\SplFileInfo; 21 | use InvalidArgumentException; 22 | use Yangyifan\Library\PathLibrary; 23 | use Yangyifan\Upload\Functions\FileFunction; 24 | use League\Flysystem\Util\MimeType; 25 | 26 | class QiniuAdapter extends AbstractAdapter 27 | { 28 | /** 29 | * Auth 30 | * 31 | * @var Auth 32 | */ 33 | protected $auth; 34 | 35 | /** 36 | * token 37 | * 38 | * @var string 39 | */ 40 | protected $token; 41 | 42 | /** 43 | * bucket 44 | * 45 | * @var 46 | */ 47 | protected $bucket; 48 | 49 | /** 50 | * 七牛空间管理对象 51 | * 52 | * @var 53 | */ 54 | protected $bucketManager; 55 | 56 | /** 57 | * 上传对象 58 | * 59 | * @var 60 | */ 61 | protected $uploadManager; 62 | 63 | /** 64 | * 配置信息 65 | * 66 | * @var array 67 | */ 68 | protected $config; 69 | 70 | /** 71 | * 构造方法 72 | * 73 | * @param array $config 配置信息 74 | * @author yangyifan 75 | */ 76 | public function __construct($config) 77 | { 78 | $this->config = $config; 79 | $this->bucket = $this->config['bucket']; 80 | $this->auth = new Auth($this->config['access_key'], $this->config['secret_key']); 81 | $this->token = $this->auth->uploadToken($this->bucket); 82 | 83 | //设置路径前缀 84 | $this->setPathPrefix($this->config['transport'] . '://' . $this->config['domain']); 85 | } 86 | 87 | /** 88 | * 格式化路径 89 | * 90 | * @param $path 91 | * @return string 92 | */ 93 | protected static function normalizerPath($path) 94 | { 95 | $path = ltrim(PathLibrary::normalizerPath($path), '/'); 96 | 97 | return $path == '/' ? '' : $path; 98 | } 99 | 100 | 101 | /** 102 | * 获得七牛空间管理对象 103 | * 104 | * @return BucketManager 105 | * @author yangyifan 106 | */ 107 | protected function getBucketManager() 108 | { 109 | if (!$this->bucketManager) { 110 | $this->bucketManager = new BucketManager($this->auth); 111 | } 112 | return $this->bucketManager; 113 | } 114 | 115 | /** 116 | * 获得七牛上传对象 117 | * 118 | * @return UploadManager 119 | * @author yangyifan 120 | */ 121 | protected function getUploadManager() 122 | { 123 | if (!$this->uploadManager) { 124 | $this->uploadManager = new UploadManager(); 125 | } 126 | return $this->uploadManager; 127 | } 128 | 129 | /** 130 | * 获得 Qiniu 实例 131 | * 132 | * @return UploadManager 133 | */ 134 | public function getInstance() 135 | { 136 | return $this->getUploadManager(); 137 | } 138 | 139 | /** 140 | * 获得二进制流上传对象 141 | * 142 | * @param string $key 上传文件名 143 | * @param resource $inputStream 上传二进制流 144 | * @param Config $config 配置信息 145 | * @param array $params 自定义变量 146 | * @return ResumeUploader 147 | * @author yangyifan 148 | */ 149 | protected function getResumeUpload($key, $inputStream, Config $config, $params = null) 150 | { 151 | return new ResumeUploader( $this->token, $key, $inputStream, $this->getResourceSize($inputStream), $params, $config->get('mimetype'), (new QiniuConfig()) ); 152 | } 153 | 154 | /** 155 | * 获得文件大小 156 | * 157 | * @param $inputStream 158 | * @return int 159 | */ 160 | protected function getResourceSize($inputStream) 161 | { 162 | $size = 0; 163 | 164 | $a = &$inputStream; 165 | 166 | while( !feof($a) ) { 167 | $str = fgets($a); 168 | $size += strlen($str); 169 | } 170 | 171 | fseek($inputStream, 0); 172 | 173 | return $size; 174 | } 175 | 176 | /** 177 | * 判断文件是否存在 178 | * 179 | * @param string $path 180 | * @return bool 181 | * @author yangyifan 182 | */ 183 | public function has($path) 184 | { 185 | $file_stat = $this->getMetadata($path); 186 | return !empty($file_stat) ? true : false; 187 | } 188 | 189 | /** 190 | * 读取文件 191 | * 192 | * @param $file_name 193 | * @author yangyifan 194 | */ 195 | public function read($path) 196 | { 197 | return ['contents' => file_get_contents($this->applyPathPrefix($path)) ]; 198 | } 199 | 200 | /** 201 | * 获得文件流 202 | * 203 | * @param string $path 204 | * @return array 205 | * @author yangyifan 206 | */ 207 | public function readStream($path) 208 | { 209 | return ['stream' => fopen($this->applyPathPrefix($path), 'r')]; 210 | } 211 | 212 | /** 213 | * 写入文件 214 | * 215 | * @param $file_name 216 | * @param $contents 217 | * @param Config $config 218 | * @author yangyifan 219 | */ 220 | public function write($path, $contents, Config $config) 221 | { 222 | list(, $error) = $this->getUploadManager()->put($this->token, static::normalizerPath($path), $contents); 223 | 224 | if ($error) { 225 | return false; 226 | } 227 | return true; 228 | } 229 | 230 | /** 231 | * 写入文件流 232 | * 233 | * @param string $path 234 | * @param resource $resource 235 | * @param array $config 236 | */ 237 | public function writeStream($path, $resource, Config $config) 238 | { 239 | $config->set('mimetype', MimeType::detectByContent(fgets($resource))); 240 | 241 | list(, $error) = $this->getResumeUpload(static::normalizerPath($path), $resource, $config)->upload(); 242 | 243 | return $error ? false : true; 244 | } 245 | 246 | /** 247 | * 更新文件 248 | * 249 | * @param string $path 250 | * @param string $contents 251 | * @param array $config 252 | */ 253 | public function update($path, $contents, Config $config) 254 | { 255 | return $this->write($path, $contents, $config); 256 | } 257 | 258 | /** 259 | * 更新文件流 260 | * 261 | * @param string $path 262 | * @param resource $resource 263 | * @param array $config 264 | */ 265 | public function updateStream($path, $resource, Config $config) 266 | { 267 | return $this->writeStream($path, $resource, $config); 268 | } 269 | 270 | /** 271 | * 列出目录文件 272 | * 273 | * @param string $directory 274 | * @param bool|false $recursive 275 | * @return mixed 276 | * @author yangyifan 277 | */ 278 | public function listContents($directory = '', $recursive = false) 279 | { 280 | list($file_list, $marker, $error) = $this->getBucketManager()->listFiles($this->bucket, static::normalizerPath($directory)); 281 | 282 | if (!$error) { 283 | foreach ($file_list as &$file) { 284 | $file['path'] = $file['key']; 285 | $file['marker'] = $marker;//用于下次请求的标识符 286 | } 287 | return $file_list; 288 | } 289 | return false; 290 | } 291 | 292 | /** 293 | * 获取资源的元信息,但不返回文件内容 294 | * 295 | * @param $path 296 | * @return mixed 297 | * @author yangyifan 298 | */ 299 | public function getMetadata($path) 300 | { 301 | list($info, $error) = $this->getBucketManager()->stat($this->bucket, static::normalizerPath($path)); 302 | 303 | if ($error) { 304 | return false; 305 | } 306 | return $info; 307 | } 308 | 309 | /** 310 | * 获得文件大小 311 | * 312 | * @param string $path 313 | * @return array 314 | * @author yangyifan 315 | */ 316 | public function getSize($path) 317 | { 318 | list($fsize, , , ) = array_values($this->getMetadata($path)); 319 | return $fsize > 0 ? [ 'size' => $fsize ] : ['size' => 0]; 320 | } 321 | 322 | /** 323 | * 获得文件Mime类型 324 | * 325 | * @param string $path 326 | * @return mixed string|null 327 | * @author yangyifan 328 | */ 329 | public function getMimetype($path) 330 | { 331 | list(, , $mimeType,) = array_values($this->getMetadata($path)); 332 | return !empty($mimeType) ? ['mimetype' => $mimeType ] : ['mimetype' => '']; 333 | } 334 | 335 | /** 336 | * 获得文件最后修改时间 337 | * 338 | * @param string $path 339 | * @return mixed 时间戳 340 | * @author yangyifan 341 | */ 342 | public function getTimestamp($path) 343 | { 344 | list(, , , $timestamp) = array_values($this->getMetadata($path)); 345 | return !empty($timestamp) ? ['timestamp' => substr($timestamp, 0, -7) ] : ['timestamp' => 0]; 346 | } 347 | 348 | /** 349 | * 获得文件模式 (未实现) 350 | * 351 | * @param string $path 352 | * @author yangyifan 353 | */ 354 | public function getVisibility($path) 355 | { 356 | return self::VISIBILITY_PUBLIC; 357 | } 358 | 359 | /** 360 | * 重命名文件 361 | * 362 | * @param $oldname 363 | * @param $newname 364 | * @return boolean 365 | * @author yangyifan 366 | */ 367 | public function rename($path, $newpath) 368 | { 369 | return $this->getBucketManager()->rename($this->bucket, static::normalizerPath($path), static::normalizerPath($newpath)) == null 370 | ? true 371 | : false; 372 | } 373 | 374 | /** 375 | * 复制文件 376 | * 377 | * @param $path 378 | * @param $newpath 379 | * @return boolean 380 | * @author yangyifan 381 | */ 382 | public function copy($path, $newpath) 383 | { 384 | return $this->getBucketManager()->copy($this->bucket, static::normalizerPath($path), $this->bucket, static::normalizerPath($newpath)) == null 385 | ? true 386 | : false; 387 | } 388 | 389 | /** 390 | * 删除文件或者文件夹 391 | * 392 | * @param $path 393 | * @author yangyifan 394 | */ 395 | public function delete($path) 396 | { 397 | return $this->getBucketManager()->delete($this->bucket, static::normalizerPath($path)) == null ? true : false; 398 | } 399 | 400 | /** 401 | * 删除文件夹 402 | * 403 | * @param string $path 404 | * @return mixed 405 | * @author yangyifan 406 | */ 407 | public function deleteDir($path) 408 | { 409 | list($file_list, , $error) = $this->getBucketManager()->listFiles($this->bucket, static::normalizerPath($path)); 410 | 411 | if (!$error) { 412 | foreach ( $file_list as $file) { 413 | $this->delete($file['key']); 414 | } 415 | } 416 | return true; 417 | } 418 | 419 | /** 420 | * 创建文件夹(因为七牛没有文件夹的概念,所以此方法没有实现) 421 | * 422 | * @param string $dirname 423 | * @param array $config 424 | * @author yangyifan 425 | */ 426 | public function createDir($dirname, Config $config) 427 | { 428 | return true; 429 | } 430 | 431 | /** 432 | * 设置文件模式 (未实现) 433 | * 434 | * @param string $path 435 | * @param string $visibility 436 | * @return bool 437 | * @author yangyifan 438 | */ 439 | public function setVisibility($path, $visibility) 440 | { 441 | return true; 442 | } 443 | 444 | /** 445 | * 获取当前文件的URL访问路径 446 | * 447 | * @param $file 448 | * @param int $expire_at 449 | * @return mixed 450 | */ 451 | public function getUrl($file, $expire_at = 3600) 452 | { 453 | return $this->applyPathPrefix($file); 454 | } 455 | } -------------------------------------------------------------------------------- /src/Cos/Cos/File.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Cos\Cos; 12 | 13 | trait File 14 | { 15 | /* 16 | * 查询文件信息 17 | * @param string $bucket bucket名称 18 | * @param string $path 文件路径 19 | */ 20 | public function stat($bucket, $path) 21 | { 22 | $path = self::normalizerPath($path); 23 | 24 | return $this->statBase($bucket, $path); 25 | } 26 | 27 | /** 28 | * 上传文件,自动判断文件大小,如果小于20M则使用普通文件上传,大于20M则使用分片上传 29 | * @param string $bucket bucket名称 30 | * @param string $dstPath 上传的文件路径 31 | * @param string $filecontent 文件的内容 32 | * @param string $bizAttr 文件属性 33 | * @param string $slicesize 分片大小(512k,1m,2m,3m),默认:1m 34 | * @param string $insertOnly 同名文件是否覆盖 35 | * @return [type] [description] 36 | */ 37 | public function upload( 38 | $bucket, $dstPath, $filecontent, $mime_type, $bizAttr=null, $sliceSize=null, $insertOnly=true) { 39 | 40 | $dstPath = self::normalizerPath($dstPath, false); 41 | 42 | // 如果是文件句柄,则读取文件内容 43 | if ( is_resource($filecontent) ) { 44 | $content = fgets($filecontent); 45 | } else { 46 | $content = $filecontent; 47 | } 48 | 49 | //文件大于20M则使用分片传输 50 | if (strlen($content) < self::MAX_UNSLICE_FILE_SIZE ) { 51 | return $this->uploadFile($bucket, $dstPath, $content, $mime_type, $bizAttr, $insertOnly); 52 | } else { 53 | throw new \InvalidArgumentException('文件大小大于 20M,请使用分片传输!'); 54 | 55 | //$sliceSize = self::getSliceSize($sliceSize); 56 | //return self::uploadBySlicing($bucket,$dstPath, $content, $mime_type, $bizAttr, $sliceSize, $insertOnly); 57 | } 58 | } 59 | 60 | /** 61 | * 内部方法, 上传文件 62 | * 63 | * @param string $bucket bucket名称 64 | * @param string $srcPath 本地文件路径 65 | * @param string $dstPath 上传的文件路径 66 | * @param string $bizAttr 文件属性 67 | * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 68 | * @return [type] [description] 69 | */ 70 | private function uploadFile($bucket, $dstPath, $filecontent, $mime_type, $bizAttr = null, $insertOnly = null) 71 | { 72 | $dstPath = self::cosUrlEncode($dstPath); 73 | 74 | $expired = time() + self::EXPIRED_SECONDS; 75 | $url = self::generateResUrl($bucket, $dstPath); 76 | $signature = $this->createReusableSignature($expired, $bucket); 77 | $fileSha = sha1($filecontent); 78 | 79 | $data = array( 80 | 'op' => 'upload', 81 | 'sha' => $fileSha, 82 | 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), 83 | ); 84 | 85 | $data['filecontent'] = $filecontent; 86 | 87 | if (isset($insertOnly) && strlen($insertOnly) > 0) { 88 | $data['insertOnly'] = (($insertOnly == 0 || $insertOnly == '0' ) ? 0 : 1); 89 | } 90 | 91 | // 设置文件的 mime 类型 92 | $data['custom_headers']['Content-Type'] = $mime_type; 93 | 94 | $req = [ 95 | 'url' => $url, 96 | 'method' => 'post', 97 | 'timeout' => self::$timeout, 98 | 'data' => $data, 99 | 'header' => [ 100 | 'Authorization: ' . $signature, 101 | ], 102 | ]; 103 | 104 | return self::sendRequest($req); 105 | } 106 | 107 | 108 | /** 109 | * 内部方法,上传文件 110 | * @param string $bucket bucket名称 111 | * @param string $srcPath 本地文件路径 112 | * @param string $dstPath 上传的文件路径 113 | * @param string $bizAttr 文件属性 114 | * @param string $sliceSize 分片大小 115 | * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 116 | * @return [type] [description] 117 | */ 118 | private function uploadBySlicing( 119 | $bucket, $dstFpath, $content, $mime_type, $bizAttr=null, $sliceSize=null, $insertOnly=null) 120 | { 121 | $fileSize = strlen($content); 122 | $dstFpath = self::cosUrlEncode($dstFpath); 123 | $url = self::generateResUrl($bucket, $dstFpath); 124 | $sliceCount = ceil($fileSize / $sliceSize); 125 | // expiration seconds for one slice mutiply by slice count 126 | // will be the expired seconds for whole file 127 | $expiration = time() + (self::EXPIRED_SECONDS * $sliceCount); 128 | if ($expiration >= (time() + 10 * 24 * 60 * 60)) { 129 | $expiration = time() + 10 * 24 * 60 * 60; 130 | } 131 | $signature = $this->createReusableSignature($expiration, $bucket); 132 | 133 | $sliceUploading = new SliceUploading(self::$timeout * 1000, self::MAX_RETRY_TIMES); 134 | for ($tryCount = 0; $tryCount < self::MAX_RETRY_TIMES; ++$tryCount) { 135 | if ($sliceUploading->initUploading( 136 | $signature, 137 | $srcFpath, 138 | $url, 139 | $fileSize, $sliceSize, $bizAttr, $insertOnly)) { 140 | break; 141 | } 142 | 143 | $errorCode = $sliceUploading->getLastErrorCode(); 144 | if ($errorCode === -4019) { 145 | // Delete broken file and retry again on _ERROR_FILE_NOT_FINISH_UPLOAD error. 146 | $this->delFile($bucket, $dstFpath); 147 | continue; 148 | } 149 | 150 | if ($tryCount === self::MAX_RETRY_TIMES - 1) { 151 | return array( 152 | 'code' => $sliceUploading->getLastErrorCode(), 153 | 'message' => $sliceUploading->getLastErrorMessage(), 154 | 'requestId' => $sliceUploading->getRequestId(), 155 | ); 156 | } 157 | } 158 | 159 | if (!$sliceUploading->performUploading()) { 160 | return array( 161 | 'code' => $sliceUploading->getLastErrorCode(), 162 | 'message' => $sliceUploading->getLastErrorMessage(), 163 | 'requestId' => $sliceUploading->getRequestId(), 164 | ); 165 | } 166 | 167 | if (!$sliceUploading->finishUploading()) { 168 | return array( 169 | 'code' => $sliceUploading->getLastErrorCode(), 170 | 'message' => $sliceUploading->getLastErrorMessage(), 171 | 'requestId' => $sliceUploading->getRequestId(), 172 | ); 173 | } 174 | 175 | return array( 176 | 'code' => 0, 177 | 'message' => 'success', 178 | 'requestId' => $sliceUploading->getRequestId(), 179 | 'data' => array( 180 | 'accessUrl' => $sliceUploading->getAccessUrl(), 181 | 'resourcePath' => $sliceUploading->getResourcePath(), 182 | 'sourceUrl' => $sliceUploading->getSourceUrl(), 183 | ), 184 | ); 185 | } 186 | 187 | /* 188 | * 删除文件 189 | * @param string $bucket 190 | * @param string $path 文件路径 191 | */ 192 | public function delFile($bucket, $path) 193 | { 194 | if (empty($bucket) || empty($path)) { 195 | return array( 196 | 'code' => Response::COSAPI_PARAMS_ERROR, 197 | 'message' => 'path is empty' 198 | ); 199 | } 200 | 201 | $path = self::normalizerPath($path); 202 | 203 | return $this->delBase($bucket, $path); 204 | } 205 | 206 | /** 207 | * Move a file. 208 | * @param $bucket bucket name. 209 | * @param $srcFpath source file path. 210 | * @param $dstFpath destination file path. 211 | * @param $overwrite if the destination location is occupied, overwrite it or not? 212 | * @return array|mixed. 213 | */ 214 | public function moveFile($bucket, $srcFpath, $dstFpath, $overwrite = false) 215 | { 216 | $url = self::generateResUrl($bucket, $srcFpath); 217 | $sign = $this->createNonreusableSignature($bucket, $srcFpath); 218 | $data = array( 219 | 'op' => 'move', 220 | 'dest_fileid' => $dstFpath, 221 | 'to_over_write' => $overwrite ? 1 : 0, 222 | ); 223 | $req = array( 224 | 'url' => $url, 225 | 'method' => 'post', 226 | 'timeout' => self::$timeout, 227 | 'data' => $data, 228 | 'header' => array( 229 | 'Authorization: ' . $sign, 230 | ), 231 | ); 232 | 233 | return self::sendRequest($req); 234 | } 235 | 236 | /** 237 | * Copy a file. 238 | * @param $bucket bucket name. 239 | * @param $srcFpath source file path. 240 | * @param $dstFpath destination file path. 241 | * @param $overwrite if the destination location is occupied, overwrite it or not? 242 | * @return array|mixed. 243 | */ 244 | public function copyFile($bucket, $srcFpath, $dstFpath, $overwrite = false) 245 | { 246 | $url = self::generateResUrl($bucket, $srcFpath); 247 | $sign = $this->createNonreusableSignature($bucket, $srcFpath); 248 | $data = array( 249 | 'op' => 'copy', 250 | 'dest_fileid' => $dstFpath, 251 | 'to_over_write' => $overwrite ? 1 : 0, 252 | ); 253 | $req = array( 254 | 'url' => $url, 255 | 'method' => 'post', 256 | 'timeout' => self::$timeout, 257 | 'data' => $data, 258 | 'header' => array( 259 | 'Authorization: ' . $sign, 260 | ), 261 | ); 262 | 263 | return self::sendRequest($req); 264 | } 265 | 266 | /** 267 | * Get slice size. 268 | */ 269 | private static function getSliceSize($sliceSize) { 270 | // Fix slice size to 1MB. 271 | return self::SLICE_SIZE_1M; 272 | } 273 | 274 | /* 275 | * 内部公共方法(更新文件和更新文件夹) 276 | * @param string $bucket bucket名称 277 | * @param string $path 路径 278 | * @param string $bizAttr 文件/目录属性 279 | * @param string $authority: eInvalid/eWRPrivate(私有)/eWPrivateRPublic(公有读写) 280 | * @param array $customer_headers_array 携带的用户自定义头域,包括 281 | * 'Cache-Control' => '*' 282 | * 'Content-Type' => '*' 283 | * 'Content-Disposition' => '*' 284 | * 'Content-Language' => '*' 285 | * 'x-cos-meta-自定义内容' => '*' 286 | */ 287 | public function updateBase( 288 | $bucket, $path, $bizAttr = null, $authority = null, $custom_headers_array = null) 289 | { 290 | $path = self::cosUrlEncode($path); 291 | $url = self::generateResUrl($bucket, $path); 292 | $signature = $this->createNonreusableSignature($bucket, $path); 293 | 294 | $data = array('op' => 'update'); 295 | 296 | if (isset($bizAttr)) { 297 | $data['biz_attr'] = $bizAttr; 298 | } 299 | 300 | if (isset($authority) && strlen($authority) > 0) { 301 | if(self::isAuthorityValid($authority) == false) { 302 | return array( 303 | 'code' => COSAPI_PARAMS_ERROR, 304 | 'message' => 'parameter authority invalid'); 305 | } 306 | 307 | $data['authority'] = $authority; 308 | } 309 | 310 | if (isset($custom_headers_array)) { 311 | $data['custom_headers'] = array(); 312 | self::add_customer_header($data['custom_headers'], $custom_headers_array); 313 | } 314 | 315 | $data = json_encode($data); 316 | 317 | $req = array( 318 | 'url' => $url, 319 | 'method' => 'post', 320 | 'timeout' => self::$timeout, 321 | 'data' => $data, 322 | 'header' => array( 323 | 'Authorization: ' . $signature, 324 | 'Content-Type: application/json', 325 | ), 326 | ); 327 | 328 | return self::sendRequest($req); 329 | } 330 | 331 | /** 332 | * 增加自定义属性到data中 333 | * @param array $data 334 | * @param array $customer_headers_array 335 | * @return [type] void 336 | */ 337 | private static function add_customer_header(&$data, &$customer_headers_array) { 338 | if (count($customer_headers_array) < 1) { 339 | return; 340 | } 341 | foreach($customer_headers_array as $key=>$value) { 342 | if(self::isCustomer_header($key)) { 343 | $data[$key] = $value; 344 | } 345 | } 346 | } 347 | 348 | /** 349 | * 判断authority值是否正确 350 | * @param string $authority 351 | * @return [type] bool 352 | */ 353 | private static function isAuthorityValid($authority) { 354 | if ($authority == 'eInvalid' || $authority == 'eWRPrivate' || $authority == 'eWPrivateRPublic') { 355 | return true; 356 | } 357 | return false; 358 | } 359 | 360 | /** 361 | * 判断是否符合自定义属性 362 | * @param string $key 363 | * @return [type] bool 364 | */ 365 | private static function isCustomer_header($key) { 366 | if ($key == 'Cache-Control' || $key == 'Content-Type' || 367 | $key == 'Content-Disposition' || $key == 'Content-Language' || 368 | $key == 'Content-Encoding' || 369 | substr($key,0,strlen('x-cos-meta-')) == 'x-cos-meta-') { 370 | return true; 371 | } 372 | return false; 373 | } 374 | } -------------------------------------------------------------------------------- /src/Oss/OssAdapter.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | 12 | namespace Yangyifan\Upload\Oss; 13 | 14 | use League\Flysystem\Adapter\AbstractAdapter; 15 | use League\Flysystem\Config; 16 | use Yangyifan\Library\PathLibrary; 17 | use Yangyifan\Upload\Functions\FileFunction; 18 | use Exception; 19 | use OSS\OssClient; 20 | use OSS\Core\OssException; 21 | 22 | class OssAdapter extends AbstractAdapter 23 | { 24 | 25 | const FILE_TYPE_FILE = 'file';//类型是文件 26 | const FILE_TYPE_DIR = 'dir';//类型是文件夹 27 | 28 | /** 29 | * 配置信息 30 | * 31 | * @var 32 | */ 33 | protected $config; 34 | 35 | /** 36 | * oss client 上传对象 37 | * 38 | * @var OssClient 39 | */ 40 | protected $upload; 41 | 42 | /** 43 | * bucket 44 | * 45 | * @var string 46 | */ 47 | protected $bucket; 48 | 49 | /** 50 | * 构造方法 51 | * 52 | * @param array $config 配置信息 53 | * @author yangyifan 54 | */ 55 | public function __construct($config) 56 | { 57 | $this->config = $config; 58 | $this->bucket = $this->config['bucket']; 59 | //设置路径前缀 60 | $this->setPathPrefix($this->config['transport'] . '://' . $this->config['bucket'] . '.' . $this->config['endpoint']); 61 | } 62 | 63 | /** 64 | * 格式化路径 65 | * 66 | * @param $path 67 | * @return string 68 | */ 69 | protected static function normalizerPath($path, $is_dir = false) 70 | { 71 | $path = ltrim(PathLibrary::normalizerPath($path, $is_dir), '/'); 72 | 73 | return $path == '/' ? '' : $path; 74 | } 75 | 76 | /** 77 | * 获得OSS client上传对象 78 | * 79 | * @return \OSS\OssClient 80 | * @author yangyifan 81 | */ 82 | protected function getOss() 83 | { 84 | if (!$this->upload) { 85 | $this->upload = new OssClient( 86 | $this->config['accessKeyId'], 87 | $this->config['accessKeySecret'], 88 | $this->config['endpoint'], 89 | $this->config['isCName'], 90 | $this->config['securityToken'] 91 | ); 92 | 93 | //设置请求超时时间 94 | $this->upload->setTimeout($this->config['timeout']); 95 | 96 | //设置连接超时时间 97 | $this->upload->setConnectTimeout($this->config['connectTimeout']); 98 | } 99 | 100 | return $this->upload; 101 | } 102 | 103 | /** 104 | * 获得 Oss 实例 105 | * 106 | * @return OssClient 107 | */ 108 | public function getInstance() 109 | { 110 | return $this->getOss(); 111 | } 112 | 113 | /** 114 | * 判断文件是否存在 115 | * 116 | * @param string $path 117 | * @return bool 118 | * @author yangyifan 119 | */ 120 | public function has($path) 121 | { 122 | try { 123 | return $this->getOss()->doesObjectExist($this->bucket, $path) != false ? true : false; 124 | }catch (OssException $e){ 125 | 126 | } 127 | return false; 128 | } 129 | 130 | /** 131 | * 读取文件 132 | * 133 | * @param $file_name 134 | * @author yangyifan 135 | */ 136 | public function read($path) 137 | { 138 | try { 139 | return ['contents' => $this->getOss()->getObject($this->bucket, static::normalizerPath($path)) ]; 140 | }catch (OssException $e){ 141 | 142 | } 143 | return false; 144 | 145 | } 146 | 147 | /** 148 | * 获得文件流 149 | * 150 | * @param string $path 151 | * @return array|bool 152 | * @author yangyifan 153 | */ 154 | public function readStream($path) 155 | { 156 | try { 157 | //获得一个临时文件 158 | $tmpfname = FileFunction::getTmpFile(); 159 | 160 | file_put_contents($tmpfname, $this->read($path)['contents'] ); 161 | 162 | $handle = fopen($tmpfname, 'r'); 163 | 164 | //删除临时文件 165 | FileFunction::deleteTmpFile($tmpfname); 166 | 167 | return ['stream' => $handle]; 168 | }catch (OssException $e){ 169 | 170 | } 171 | 172 | return false; 173 | } 174 | 175 | /** 176 | * 写入文件 177 | * 178 | * @param $file_name 179 | * @param $contents 180 | * @author yangyifan 181 | */ 182 | public function write($path, $contents, Config $config) 183 | { 184 | try { 185 | $this->getOss()->putObject($this->bucket, $path, $contents, $option = []); 186 | 187 | return true; 188 | }catch (OssException $e){ 189 | 190 | } 191 | return false; 192 | } 193 | 194 | /** 195 | * 写入文件流 196 | * 197 | * @param string $path 198 | * @param resource $resource 199 | * @param array $config 200 | */ 201 | public function writeStream($path, $resource, Config $config) 202 | { 203 | try{ 204 | //获得一个临时文件 205 | $tmpfname = FileFunction::getTmpFile(); 206 | 207 | file_put_contents($tmpfname, $resource); 208 | 209 | $this->getOss()->uploadFile($this->bucket, $path, $tmpfname, $option = []); 210 | 211 | //删除临时文件 212 | FileFunction::deleteTmpFile($tmpfname); 213 | 214 | return true; 215 | } 216 | catch (OssException $e){ 217 | 218 | } 219 | return false; 220 | } 221 | 222 | /** 223 | * 更新文件 224 | * 225 | * @param string $path 226 | * @param string $contents 227 | * @param array $config 228 | */ 229 | public function update($path, $contents, Config $config) 230 | { 231 | return $this->write($path, $contents, $config); 232 | } 233 | 234 | /** 235 | * 更新文件流 236 | * 237 | * @param string $path 238 | * @param resource $resource 239 | * @param array $config 240 | */ 241 | public function updateStream($path, $resource, Config $config) 242 | { 243 | return $this->writeStream($path, $resource, $config); 244 | } 245 | 246 | /** 247 | * 列出目录文件 248 | * 249 | * @param string $directory 250 | * @param bool|false $recursive 251 | * @return array 252 | * @author yangyifan 253 | */ 254 | public function listContents($directory = '', $recursive = false) 255 | { 256 | try{ 257 | $directory = static::normalizerPath($directory, true); 258 | 259 | $options = [ 260 | 'delimiter' => '/' , 261 | 'prefix' => $directory, 262 | 'max-keys' => $this->config['max_keys'], 263 | 'marker' => '', 264 | ]; 265 | 266 | $result_obj = $this->getOss()->listObjects($this->bucket, $options); 267 | 268 | $file_list = $result_obj->getObjectList();//文件列表 269 | $dir_list = $result_obj->getPrefixList();//文件夹列表 270 | $data = []; 271 | 272 | if (is_array($dir_list) && count($dir_list) > 0 ) { 273 | foreach ($dir_list as $key => $dir) { 274 | $data[] = [ 275 | 'path' => $dir->getPrefix(), 276 | 'prefix' => $options['prefix'], 277 | 'marker' => $options['marker'], 278 | 'file_type' => self::FILE_TYPE_DIR 279 | ]; 280 | } 281 | } 282 | 283 | if (is_array($file_list) && count($file_list) > 0 ) { 284 | foreach ($file_list as $key => $file) { 285 | if ($key == 0 ) { 286 | $data[] = [ 287 | 'path' => $file->getKey(), 288 | 'prefix' => $options['prefix'], 289 | 'marker' => $options['marker'], 290 | 'file_type' => self::FILE_TYPE_DIR 291 | ]; 292 | } else { 293 | $data[] = [ 294 | 'path' => $file->getKey(), 295 | 'last_modified' => $file->getLastModified(), 296 | 'e_tag' => $file->getETag(), 297 | 'file_size' => $file->getSize(), 298 | 'prefix' => $options['prefix'], 299 | 'marker' => $options['marker'], 300 | 'file_type' => self::FILE_TYPE_FILE, 301 | ]; 302 | } 303 | } 304 | } 305 | 306 | return $data; 307 | }catch (Exception $e){ 308 | 309 | } 310 | return []; 311 | } 312 | 313 | /** 314 | * 获取资源的元信息,但不返回文件内容 315 | * 316 | * @param $path 317 | * @return array|bool 318 | * @author yangyifan 319 | */ 320 | public function getMetadata($path) 321 | { 322 | try { 323 | $file_info = $this->getOss()->getObjectMeta($this->bucket, $path); 324 | if ( !empty($file_info) ) { 325 | return $file_info; 326 | } 327 | }catch (OssException $e) { 328 | 329 | } 330 | return false; 331 | } 332 | 333 | /** 334 | * 获得文件大小 335 | * 336 | * @param string $path 337 | * @return array 338 | * @author yangyifan 339 | */ 340 | public function getSize($path) 341 | { 342 | $file_info = $this->getMetadata($path); 343 | return $file_info != false && $file_info['content-length'] > 0 ? [ 'size' => $file_info['content-length'] ] : ['size' => 0]; 344 | } 345 | 346 | /** 347 | * 获得文件Mime类型 348 | * 349 | * @param string $path 350 | * @return mixed string|null 351 | * @author yangyifan 352 | */ 353 | public function getMimetype($path) 354 | { 355 | $file_info = $this->getMetadata($path); 356 | return $file_info != false && !empty($file_info['content-type']) ? [ 'mimetype' => $file_info['content-type'] ] : false; 357 | } 358 | 359 | /** 360 | * 获得文件最后修改时间 361 | * 362 | * @param string $path 363 | * @return array 时间戳 364 | * @author yangyifan 365 | */ 366 | public function getTimestamp($path) 367 | { 368 | $file_info = $this->getMetadata($path); 369 | return $file_info != false && !empty($file_info['last-modified']) 370 | ? ['timestamp' => strtotime($file_info['last-modified']) ] 371 | : ['timestamp' => 0 ]; 372 | } 373 | 374 | /** 375 | * 获得文件模式 (未实现) 376 | * 377 | * @param string $path 378 | * @author yangyifan 379 | */ 380 | public function getVisibility($path) 381 | { 382 | return self::VISIBILITY_PUBLIC; 383 | } 384 | 385 | /** 386 | * 重命名文件 387 | * 388 | * @param $oldname 389 | * @param $newname 390 | * @return boolean 391 | * @author yangyifan 392 | */ 393 | public function rename($path, $newpath) 394 | { 395 | try { 396 | /** 397 | * 如果是一个资源,请保持最后不是以“/”结尾! 398 | * 399 | */ 400 | $path = static::normalizerPath($path); 401 | 402 | $this->getOss()->copyObject($this->bucket, $path, $this->bucket, static::normalizerPath($newpath), []); 403 | $this->delete($path); 404 | return true; 405 | }catch (OssException $e){ 406 | 407 | } 408 | return false; 409 | } 410 | 411 | /** 412 | * 复制文件 413 | * 414 | * @param $path 415 | * @param $newpath 416 | * @return boolean 417 | * @author yangyifan 418 | */ 419 | public function copy($path, $newpath) 420 | { 421 | try { 422 | $this->getOss()->copyObject($this->bucket, $path, $this->bucket, static::normalizerPath($newpath), []); 423 | return true; 424 | }catch (OssException $e){ 425 | 426 | } 427 | return false; 428 | } 429 | 430 | /** 431 | * 删除文件或者文件夹 432 | * 433 | * @param $path 434 | * @author yangyifan 435 | */ 436 | public function delete($path) 437 | { 438 | try{ 439 | $this->getOss()->deleteObject($this->bucket, $path); 440 | return true; 441 | }catch (OssException $e){ 442 | 443 | } 444 | return false; 445 | } 446 | 447 | /** 448 | * 删除文件夹 449 | * 450 | * @param string $path 451 | * @return mixed 452 | * @author yangyifan 453 | */ 454 | public function deleteDir($path) 455 | { 456 | try{ 457 | //递归去删除全部文件 458 | $this->recursiveDelete($path); 459 | 460 | return true; 461 | }catch (OssException $e){ 462 | 463 | } 464 | return false; 465 | } 466 | 467 | /** 468 | * 递归删除全部文件 469 | * 470 | * @param $path 471 | * @author yangyifan 472 | */ 473 | protected function recursiveDelete($path) 474 | { 475 | $file_list = $this->listContents($path); 476 | 477 | // 如果当前文件夹文件不为空,则直接去删除文件夹 478 | if ( is_array($file_list) && count($file_list) > 0 ) { 479 | foreach ($file_list as $file) { 480 | if ($file['path'] == $path) { 481 | continue; 482 | } 483 | if ($file['file_type'] == self::FILE_TYPE_FILE) { 484 | $this->delete($file['path']); 485 | } else { 486 | $this->recursiveDelete($file['path']); 487 | } 488 | } 489 | } 490 | 491 | $this->getOss()->deleteObject($this->bucket, $path); 492 | } 493 | 494 | /** 495 | * 创建文件夹 496 | * 497 | * @param string $dirname 498 | * @param array $config 499 | * @author yangyifan 500 | */ 501 | public function createDir($dirname, Config $config) 502 | { 503 | try{ 504 | $this->getOss()->createObjectDir($this->bucket, static::normalizerPath($dirname, true)); 505 | return true; 506 | }catch (OssException $e){ 507 | 508 | } 509 | return false; 510 | } 511 | 512 | /** 513 | * 设置文件模式 (未实现) 514 | * 515 | * @param string $path 516 | * @param string $visibility 517 | * @return bool 518 | * @author yangyifan 519 | */ 520 | public function setVisibility($path, $visibility) 521 | { 522 | return true; 523 | } 524 | 525 | /** 526 | * 获取当前文件的URL访问路径 527 | * @param string $file 文件名 528 | * @param integer $expire_at 有效期,单位:秒 529 | * @return string 530 | * @author qsnh <616896861@qq.com> 531 | */ 532 | public function getUrl($file, $expire_at = 3600) 533 | { 534 | try { 535 | $accessUrl = $this->getOss()->signUrl($this->bucket, $file, $expire_at); 536 | } catch (OssException $e) { 537 | return false; 538 | } 539 | 540 | return $accessUrl; 541 | } 542 | 543 | } -------------------------------------------------------------------------------- /src/Cos/CosAdapter.php: -------------------------------------------------------------------------------- 1 | 9 | // +---------------------------------------------------------------------- 10 | 11 | namespace Yangyifan\Upload\Cos; 12 | 13 | use League\Flysystem\Adapter\AbstractAdapter; 14 | use League\Flysystem\Config; 15 | use InvalidArgumentException; 16 | use Yangyifan\Config\Config as YangyifanConfig; 17 | use League\Flysystem\Util\MimeType; 18 | use Yangyifan\Library\PathLibrary; 19 | use Yangyifan\Upload\Cos\Cos\Cos; 20 | use Yangyifan\Upload\Cos\Cos\Response; 21 | 22 | class CosAdapter extends AbstractAdapter 23 | { 24 | /** 25 | * 业务维护的自定义属性 26 | */ 27 | const FILE_TYPE = 'file'; 28 | const DIR_TYPE = 'dir'; 29 | 30 | /** 31 | * bucket 32 | * 33 | * @var 34 | */ 35 | protected $bucket; 36 | 37 | /** 38 | * 配置信息 39 | * 40 | * @var YangyifanConfig 41 | */ 42 | protected $config; 43 | 44 | /** 45 | * cos 对象 46 | * 47 | * @var Cos 48 | */ 49 | protected $cos; 50 | 51 | /** 52 | * 获取 Bucket 53 | * 54 | * @author @author yangyifan 55 | * @return mixed 56 | */ 57 | public function getBucket() 58 | { 59 | return $this->bucket; 60 | } 61 | 62 | /** 63 | * 设置 Bucket 64 | * 65 | * @param mixed $bucket 66 | * @author @author yangyifan 67 | * @return CosAdapter 68 | */ 69 | public function setBucket($bucket) 70 | { 71 | if ( empty($bucket) ) { 72 | throw new InvalidArgumentException('bucket 不能为空!'); 73 | } 74 | 75 | $this->bucket = $bucket; 76 | 77 | return $this; 78 | } 79 | 80 | /** 81 | * 获取 Cos 对象 82 | * 83 | * @author @author yangyifan 84 | * @return Cos 85 | */ 86 | public function getCos() 87 | { 88 | return $this->cos; 89 | } 90 | 91 | /** 92 | * 设置 Cos 对象 93 | * 94 | * @param Cos $cos 95 | * @author @author yangyifan 96 | * @return CosAdapter 97 | */ 98 | public function setCos($cos) 99 | { 100 | $this->cos = $cos; 101 | 102 | return $this; 103 | } 104 | 105 | /** 106 | * 构造方法 107 | * 108 | * @param array $config 配置信息 109 | * @author yangyifan 110 | */ 111 | public function __construct($config) 112 | { 113 | // 设置区域 114 | Cos::setRegion($config['region']); 115 | // 设置超时时间 116 | Cos::setTimeout($config['timeout']); 117 | 118 | $this->setCos(new Cos($config))->setBucket($config['bucket']); 119 | 120 | //设置路径前缀 121 | $this->setPathPrefix($config['transport'] . '://' . $config['domain']); 122 | 123 | include __DIR__ . '/cos-php-sdk-v4/qcloudcos/libcurl_helper.php'; 124 | } 125 | 126 | /** 127 | * 获得 Cos 实例 128 | * 129 | * @return Cos 130 | */ 131 | public function getInstance() 132 | { 133 | return $this->getCos(); 134 | } 135 | 136 | /** 137 | * 判断文件是否存在 138 | * 139 | * @param string $path 140 | * @return bool 141 | * @author yangyifan 142 | */ 143 | public function has($path) 144 | { 145 | return $this->getMetadata($path) != false ; 146 | } 147 | 148 | /** 149 | * 读取文件 150 | * 151 | * @param $file_name 152 | * @author yangyifan 153 | */ 154 | public function read($path) 155 | { 156 | return ['contents' => file_get_contents($this->applyPathPrefix($path)) ]; 157 | } 158 | 159 | /** 160 | * 获得文件流 161 | * 162 | * @param string $path 163 | * @return array 164 | * @author yangyifan 165 | */ 166 | public function readStream($path) 167 | { 168 | return ['stream' => fopen($this->applyPathPrefix($path), 'r')]; 169 | } 170 | 171 | /** 172 | * 写入文件 173 | * 174 | * @param $file_name 175 | * @param $contents 176 | * @param Config $config 177 | * @author yangyifan 178 | */ 179 | public function write($path, $contents, Config $config) 180 | { 181 | $pathInfo = pathinfo($path); 182 | $path = PathLibrary::normalizerPath($path); 183 | 184 | // 先递归创建目录 185 | if ( !empty($pathInfo['dirname']) ) { 186 | $this->createDir($pathInfo['dirname'], new Config()); 187 | } 188 | 189 | // 设置文件的 mime_type 190 | $mimetype = MimeType::detectByContent($contents); 191 | 192 | $status = Response::parseResponse( 193 | $this->getCos()->upload($this->getBucket(), $path, $contents, $mimetype, self::FILE_TYPE) 194 | ); 195 | 196 | if ( $status ) { 197 | 198 | // 更新文件的 mime 类型 199 | $info = $this->getMetadata($path); 200 | 201 | return Response::parseResponse( 202 | $this->getCos()->updateBase( 203 | $this->getBucket(), 204 | $path, 205 | $info['biz_attr'], 206 | $info['authority'], 207 | ['Content-Type' => $mimetype] 208 | ) 209 | ) != false; 210 | } 211 | } 212 | 213 | /** 214 | * 写入文件流 215 | * 216 | * @param string $path 217 | * @param resource $resource 218 | * @param array $config 219 | */ 220 | public function writeStream($path, $resource, Config $config) 221 | { 222 | $pathInfo = pathinfo($path); 223 | $path = PathLibrary::normalizerPath($path); 224 | 225 | // 先递归创建目录 226 | if ( !empty($pathInfo['dirname']) ) { 227 | $this->createDir($pathInfo['dirname'], new Config()); 228 | } 229 | 230 | // 设置文件的 mime_type 231 | $mimetype = MimeType::detectByContent(fgets($resource)); 232 | 233 | $status = Response::parseResponse( 234 | $this->getCos()->upload($this->getBucket(), $path, $resource, $mimetype, self::FILE_TYPE) 235 | ); 236 | 237 | if ( $status ) { 238 | // 更新文件的 mime 类型 239 | $info = $this->getMetadata($path); 240 | 241 | return Response::parseResponse( 242 | $this->getCos()->updateBase( 243 | $this->getBucket(), 244 | $path, 245 | $info['biz_attr'], 246 | $info['authority'], 247 | ['Content-Type' => $mimetype] 248 | ) 249 | ) != false; 250 | } 251 | 252 | return false ; 253 | } 254 | 255 | /** 256 | * 更新文件 257 | * 258 | * @param string $path 259 | * @param string $contents 260 | * @param array $config 261 | */ 262 | public function update($path, $contents, Config $config) 263 | { 264 | return $this->write($path, $contents, $config); 265 | } 266 | 267 | /** 268 | * 更新文件流 269 | * 270 | * @param string $path 271 | * @param resource $resource 272 | * @param array $config 273 | */ 274 | public function updateStream($path, $resource, Config $config) 275 | { 276 | return $this->writeStream($path, $resource, $config); 277 | } 278 | 279 | /** 280 | * 列出目录文件 281 | * 282 | * @param string $directory 283 | * @param bool|false $recursive 284 | * @return mixed 285 | * @author yangyifan 286 | */ 287 | public function listContents($directory = '', $recursive = false) 288 | { 289 | $directory = PathLibrary::normalizerPath($directory); 290 | $list = $this->getCos()->listFolder($this->getBucket(), $directory); 291 | $list = json_decode($list, true); 292 | 293 | 294 | if ($list && $list['code'] == Response::COSAPI_SUCCESS) { 295 | 296 | $file_list = $list['data']['infos']; 297 | 298 | foreach ($file_list as &$file) { 299 | $file['path'] = $directory . $file['name']; 300 | } 301 | 302 | return $file_list; 303 | } 304 | 305 | return false; 306 | } 307 | 308 | /** 309 | * 获取资源的元信息,但不返回文件内容 310 | * 311 | * @param $path 312 | * @return mixed 313 | * @author yangyifan 314 | */ 315 | public function getMetadata($path) 316 | { 317 | $data = Response::parseResponse( 318 | $this->getCos()->stat($this->getBucket(), $path) 319 | ); 320 | 321 | return $data['data']; 322 | } 323 | 324 | /** 325 | * 获得文件大小 326 | * 327 | * @param string $path 328 | * @return array 329 | * @author yangyifan 330 | */ 331 | public function getSize($path) 332 | { 333 | $stat = $this->getMetadata($path); 334 | 335 | if ( $stat ) { 336 | return ['size' => $stat['filesize']]; 337 | } 338 | 339 | return ['size' => 0]; 340 | } 341 | 342 | /** 343 | * 获得文件Mime类型 344 | * 345 | * @param string $path 346 | * @return mixed string|null 347 | * @author yangyifan 348 | */ 349 | public function getMimetype($path) 350 | { 351 | $stat = $this->getMetadata($path); 352 | 353 | if ( $stat && !empty($stat['custom_headers']) && !empty($stat['custom_headers']['Content-Type'])) { 354 | return ['mimetype' => $stat['custom_headers']['Content-Type']]; 355 | } 356 | 357 | return ['mimetype' => '']; 358 | } 359 | 360 | /** 361 | * 获得文件最后修改时间 362 | * 363 | * @param string $path 364 | * @return mixed 时间戳 365 | * @author yangyifan 366 | */ 367 | public function getTimestamp($path) 368 | { 369 | $stat = $this->getMetadata($path); 370 | 371 | if ( $stat ) { 372 | return ['timestamp' => $stat['ctime']]; 373 | } 374 | 375 | return ['timestamp' => 0]; 376 | } 377 | 378 | /** 379 | * 获得文件模式 (未实现) 380 | * 381 | * @param string $path 382 | * @author yangyifan 383 | */ 384 | public function getVisibility($path) 385 | { 386 | return self::VISIBILITY_PUBLIC; 387 | } 388 | 389 | /** 390 | * 重命名文件 391 | * 392 | * @param string $oldname 393 | * @param string $newname 394 | * @return boolean 395 | * @author yangyifan 396 | */ 397 | public function rename($oldname, $newname) 398 | { 399 | $pathInfo = pathinfo($newname); 400 | $oldname = PathLibrary::normalizerPath($oldname); 401 | $newname = PathLibrary::normalizerPath($newname); 402 | $oldMetadata = $this->getMetadata($oldname); 403 | 404 | // 先递归创建目录 405 | if ( !empty($pathInfo['dirname']) ) { 406 | $this->createDir($pathInfo['dirname'], new Config()); 407 | } 408 | 409 | $status = Response::parseResponse( 410 | $this->getCos()->moveFile( 411 | $this->getBucket(), 412 | $oldname, 413 | $newname, 414 | self::FILE_TYPE 415 | ) 416 | ) != false; 417 | 418 | if ( $status ) { 419 | 420 | //更新 自定义属性字段 421 | return Response::parseResponse( 422 | $this->getCos()->updateBase($this->getBucket(), $newname, self::FILE_TYPE, $oldMetadata['authority'], $oldMetadata['custom_headers']) 423 | ) != false; 424 | } 425 | 426 | return false; 427 | } 428 | 429 | /** 430 | * 复制文件 431 | * 432 | * @param string $oldname 433 | * @param string $newname 434 | * @return boolean 435 | * @author yangyifan 436 | */ 437 | public function copy($oldname, $newname) 438 | { 439 | $pathInfo = pathinfo($newname); 440 | $oldname = PathLibrary::normalizerPath($oldname); 441 | $newname = PathLibrary::normalizerPath($newname); 442 | $oldMetadata = $this->getMetadata($oldname); 443 | 444 | // 先递归创建目录 445 | if ( !empty($pathInfo['dirname']) ) { 446 | $this->createDir($pathInfo['dirname'], new Config()); 447 | } 448 | 449 | $status = Response::parseResponse( 450 | $this->getCos()->copyFile( 451 | $this->getBucket(), 452 | $oldname, 453 | $newname, 454 | self::FILE_TYPE 455 | ) 456 | ); 457 | 458 | if ( $status ) { 459 | //更新 自定义属性字段 460 | return Response::parseResponse( 461 | $this->getCos()->updateBase($this->getBucket(), $newname, self::FILE_TYPE, $oldMetadata['authority'], $oldMetadata['custom_headers']) 462 | ) != false; 463 | } 464 | 465 | return false; 466 | } 467 | 468 | /** 469 | * 删除文件 470 | * 471 | * @param $path 472 | * @author yangyifan 473 | */ 474 | public function delete($path) 475 | { 476 | return Response::parseResponse( 477 | $this->getCos()->delFile($this->getBucket(), PathLibrary::normalizerPath($path)) 478 | ) != false ; 479 | } 480 | 481 | /** 482 | * 删除文件夹 483 | * 484 | * @param string $path 485 | * @return bool 486 | * @author yangyifan 487 | */ 488 | public function deleteDir($path) 489 | { 490 | $path = PathLibrary::normalizerPath($path, true); 491 | $stat = $this->getMetadata($path); 492 | 493 | if ( $stat) { 494 | 495 | // 递归删除文件夹里面的文件 496 | $files = $this->listContents($path); 497 | 498 | if ( $files ) { 499 | foreach ( $files as $file ) { 500 | $info = $this->getMetadata($file['path']); 501 | if ( isset($info['biz_attr']) && $info['biz_attr'] == self::FILE_TYPE ) { 502 | $this->delete($file['path']); 503 | } else { 504 | $this->deleteDir($file['path']); 505 | } 506 | } 507 | 508 | unset($file); 509 | unset($info); 510 | } 511 | 512 | return Response::parseResponse( 513 | $this->getCos()->delFolder($this->getBucket(), $path) 514 | ) != false; 515 | } 516 | 517 | return false; 518 | } 519 | 520 | /** 521 | * 创建文件夹 522 | * 523 | * @param string $dirname 524 | * @param array $config 525 | * @author yangyifan 526 | */ 527 | public function createDir($dirname, Config $config) 528 | { 529 | $path = PathLibrary::normalizerPath($dirname, true); 530 | $pathArr = explode(DIRECTORY_SEPARATOR, $path); 531 | $pathArr = array_filter($pathArr, function($value){ 532 | return !empty($value); 533 | }); 534 | 535 | if ( $pathArr ) { 536 | 537 | $currentPath = ''; 538 | 539 | foreach ( $pathArr as $value ) { 540 | 541 | $currentPath .= DIRECTORY_SEPARATOR . $value; 542 | 543 | // 如果已经存在,则跳过 544 | if ( $this->has( PathLibrary::normalizerPath($currentPath, true) ) ) { 545 | continue; 546 | } 547 | 548 | $status = Response::parseResponse( 549 | $this->getCos()->createFolder($this->getBucket(), PathLibrary::normalizerPath($currentPath, true), self::DIR_TYPE) 550 | ); 551 | 552 | if ( $status == false ) { 553 | return false; 554 | } 555 | } 556 | 557 | unset($value); 558 | return true; 559 | } 560 | 561 | return false; 562 | } 563 | 564 | /** 565 | * 设置文件模式 (未实现) 566 | * 567 | * @param string $path 568 | * @param string $visibility 569 | * @return bool 570 | * @author yangyifan 571 | */ 572 | public function setVisibility($path, $visibility) 573 | { 574 | return true; 575 | } 576 | 577 | /** 578 | * 获取当前文件的URL访问路径 579 | * 580 | * @param $file 581 | * @param int $expire_at 582 | * @return mixed 583 | */ 584 | public function getUrl($file, $expire_at = 3600) 585 | { 586 | return $this->applyPathPrefix($file); 587 | } 588 | } -------------------------------------------------------------------------------- /src/Upyun/UpYun.php: -------------------------------------------------------------------------------- 1 | code}]: {$this->message}\n"; 12 | } 13 | }/*}}}*/ 14 | 15 | class UpYunAuthorizationException extends UpYunException {/*{{{*/ 16 | public function __construct($message, $code = 0, Exception $previous = null) { 17 | parent::__construct($message, 401, $previous); 18 | } 19 | }/*}}}*/ 20 | 21 | class UpYunForbiddenException extends UpYunException {/*{{{*/ 22 | public function __construct($message, $code = 0, Exception $previous = null) { 23 | parent::__construct($message, 403, $previous); 24 | } 25 | }/*}}}*/ 26 | 27 | class UpYunNotFoundException extends UpYunException {/*{{{*/ 28 | public function __construct($message, $code = 0, Exception $previous = null) { 29 | parent::__construct($message, 404, $previous); 30 | } 31 | }/*}}}*/ 32 | 33 | class UpYunNotAcceptableException extends UpYunException {/*{{{*/ 34 | public function __construct($message, $code = 0, Exception $previous = null) { 35 | parent::__construct($message, 406, $previous); 36 | } 37 | }/*}}}*/ 38 | 39 | class UpYunServiceUnavailable extends UpYunException {/*{{{*/ 40 | public function __construct($message, $code = 0, Exception $previous = null) { 41 | parent::__construct($message, 503, $previous); 42 | } 43 | }/*}}}*/ 44 | 45 | class UpYun { 46 | const VERSION = '2.0'; 47 | 48 | /*{{{*/ 49 | const ED_AUTO = 'v0.api.upyun.com'; 50 | const ED_TELECOM = 'v1.api.upyun.com'; 51 | const ED_CNC = 'v2.api.upyun.com'; 52 | const ED_CTT = 'v3.api.upyun.com'; 53 | 54 | const CONTENT_TYPE = 'Content-Type'; 55 | const CONTENT_MD5 = 'Content-MD5'; 56 | const CONTENT_SECRET = 'Content-Secret'; 57 | 58 | // 缩略图 59 | const X_GMKERL_THUMBNAIL = 'x-gmkerl-thumbnail'; 60 | const X_GMKERL_TYPE = 'x-gmkerl-type'; 61 | const X_GMKERL_VALUE = 'x-gmkerl-value'; 62 | const X_GMKERL_QUALITY = 'x­gmkerl-quality'; 63 | const X_GMKERL_UNSHARP = 'x­gmkerl-unsharp'; 64 | /*}}}*/ 65 | 66 | private $_bucketname; 67 | private $_username; 68 | private $_password; 69 | private $_timeout = 30; 70 | 71 | /** 72 | * @deprecated 73 | */ 74 | private $_content_md5 = NULL; 75 | 76 | /** 77 | * @deprecated 78 | */ 79 | private $_file_secret = NULL; 80 | 81 | /** 82 | * @deprecated 83 | */ 84 | private $_file_infos= NULL; 85 | 86 | protected $endpoint; 87 | 88 | /** 89 | * @var string: UPYUN 请求唯一id, 出现错误时, 可以将该id报告给 UPYUN,进行调试 90 | */ 91 | private $x_request_id; 92 | 93 | /** 94 | * 初始化 UpYun 存储接口 95 | * @param $bucketname 空间名称 96 | * @param $username 操作员名称 97 | * @param $password 密码 98 | * 99 | * @return object 100 | */ 101 | public function __construct($bucketname, $username, $password, $endpoint = NULL, $timeout = 30) {/*{{{*/ 102 | $this->_bucketname = $bucketname; 103 | $this->_username = $username; 104 | $this->_password = md5($password); 105 | $this->_timeout = $timeout; 106 | 107 | $this->endpoint = is_null($endpoint) ? self::ED_AUTO : $endpoint; 108 | }/*}}}*/ 109 | 110 | /** 111 | * 获取当前SDK版本号 112 | */ 113 | public function version() { 114 | return self::VERSION; 115 | } 116 | 117 | /** 118 | * 创建目录 119 | * @param $path 路径 120 | * @param $auto_mkdir 是否自动创建父级目录,最多10层次 121 | * 122 | * @return void 123 | */ 124 | public function makeDir($path, $auto_mkdir = false) {/*{{{*/ 125 | $headers = array('Folder' => 'true'); 126 | if ($auto_mkdir) $headers['Mkdir'] = 'true'; 127 | return $this->_do_request('PUT', $path, $headers); 128 | }/*}}}*/ 129 | 130 | /** 131 | * 删除目录和文件 132 | * @param string $path 路径 133 | * 134 | * @return boolean 135 | */ 136 | public function delete($path) {/*{{{*/ 137 | return $this->_do_request('DELETE', $path); 138 | }/*}}}*/ 139 | 140 | 141 | /** 142 | * 上传文件 143 | * @param string $path 存储路径 144 | * @param mixed $file 需要上传的文件,可以是文件流或者文件内容 145 | * @param boolean $auto_mkdir 自动创建目录 146 | * @param array $opts 可选参数 147 | */ 148 | public function writeFile($path, $file, $auto_mkdir = False, $opts = NULL) {/*{{{*/ 149 | if (is_null($opts)) $opts = array(); 150 | if (!is_null($this->_content_md5) || !is_null($this->_file_secret)) { 151 | //if (!is_null($this->_content_md5)) array_push($opts, self::CONTENT_MD5 . ": {$this->_content_md5}"); 152 | //if (!is_null($this->_file_secret)) array_push($opts, self::CONTENT_SECRET . ": {$this->_file_secret}"); 153 | if (!is_null($this->_content_md5)) $opts[self::CONTENT_MD5] = $this->_content_md5; 154 | if (!is_null($this->_file_secret)) $opts[self::CONTENT_SECRET] = $this->_file_secret; 155 | } 156 | 157 | // 如果设置了缩略版本或者缩略图类型,则添加默认压缩质量和锐化参数 158 | //if (isset($opts[self::X_GMKERL_THUMBNAIL]) || isset($opts[self::X_GMKERL_TYPE])) { 159 | // if (!isset($opts[self::X_GMKERL_QUALITY])) $opts[self::X_GMKERL_QUALITY] = 95; 160 | // if (!isset($opts[self::X_GMKERL_UNSHARP])) $opts[self::X_GMKERL_UNSHARP] = 'true'; 161 | //} 162 | 163 | if ($auto_mkdir === True) $opts['Mkdir'] = 'true'; 164 | $this->_file_infos = $this->_do_request('PUT', $path, $opts, $file); 165 | 166 | return $this->_file_infos; 167 | }/*}}}*/ 168 | 169 | /** 170 | * 下载文件 171 | * @param string $path 文件路径 172 | * @param mixed $file_handle 173 | * 174 | * @return mixed 175 | */ 176 | public function readFile($path, $file_handle = NULL) {/*{{{*/ 177 | return $this->_do_request('GET', $path, NULL, NULL, $file_handle); 178 | }/*}}}*/ 179 | 180 | /** 181 | * 获取目录文件列表 182 | * 183 | * @param string $path 查询路径 184 | * 185 | * @return mixed 186 | */ 187 | public function getList($path = '/') {/*{{{*/ 188 | 189 | $rsp = $this->_do_request('GET', $path); 190 | 191 | $list = array(); 192 | if ($rsp) { 193 | $rsp = explode("\n", $rsp); 194 | foreach($rsp as $item) { 195 | @list($name, $type, $size, $time) = explode("\t", trim($item)); 196 | if (!empty($time)) { 197 | $type = $type == 'N' ? 'file' : 'folder'; 198 | } 199 | 200 | $item = array( 201 | 'name' => $name, 202 | 'type' => $type, 203 | 'size' => intval($size), 204 | 'time' => intval($time), 205 | ); 206 | array_push($list, $item); 207 | } 208 | } 209 | 210 | return $list; 211 | }/*}}}*/ 212 | 213 | /** 214 | * @deprecated 215 | * @param string $path 目录路径 216 | * @return mixed 217 | */ 218 | public function getFolderUsage($path = '/') {/*{{{*/ 219 | $rsp = $this->_do_request('GET', '/?usage'); 220 | return floatval($rsp); 221 | }/*}}}*/ 222 | 223 | /** 224 | * 获取文件、目录信息 225 | * 226 | * @param string $path 路径 227 | * 228 | * @return mixed 229 | */ 230 | public function getFileInfo($path) {/*{{{*/ 231 | $path = '/' . $path; 232 | $rsp = $this->_do_request('HEAD', $path); 233 | 234 | return $rsp; 235 | }/*}}}*/ 236 | 237 | /** 238 | * 连接签名方法 239 | * @param $method 请求方式 {GET, POST, PUT, DELETE} 240 | * return 签名字符串 241 | */ 242 | private function sign($method, $uri, $date, $length){/*{{{*/ 243 | //$uri = urlencode($uri); 244 | $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->_password}"; 245 | return 'UpYun '.$this->_username.':'.md5($sign); 246 | }/*}}}*/ 247 | 248 | /** 249 | * HTTP REQUEST 封装 250 | * @param string $method HTTP REQUEST方法,包括PUT、POST、GET、OPTIONS、DELETE 251 | * @param string $path 除Bucketname之外的请求路径,包括get参数 252 | * @param array $headers 请求需要的特殊HTTP HEADERS 253 | * @param array $body 需要POST发送的数据 254 | * 255 | * @return mixed 256 | */ 257 | protected function _do_request($method, $path, $headers = NULL, $body= NULL, $file_handle= NULL) {/*{{{*/ 258 | $uri = "/{$this->_bucketname}{$path}"; 259 | $ch = curl_init("http://{$this->endpoint}{$uri}"); 260 | 261 | $_headers = array('Expect:'); 262 | if (!is_null($headers) && is_array($headers)){ 263 | foreach($headers as $k => $v) { 264 | array_push($_headers, "{$k}: {$v}"); 265 | } 266 | } 267 | 268 | $length = 0; 269 | $date = gmdate('D, d M Y H:i:s \G\M\T'); 270 | 271 | if (!is_null($body)) { 272 | if(is_resource($body)){ 273 | fseek($body, 0, SEEK_END); 274 | $length = ftell($body); 275 | fseek($body, 0); 276 | 277 | array_push($_headers, "Content-Length: {$length}"); 278 | curl_setopt($ch, CURLOPT_INFILE, $body); 279 | curl_setopt($ch, CURLOPT_INFILESIZE, $length); 280 | } else { 281 | $length = @strlen($body); 282 | array_push($_headers, "Content-Length: {$length}"); 283 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 284 | } 285 | } else { 286 | array_push($_headers, "Content-Length: {$length}"); 287 | } 288 | 289 | array_push($_headers, "Authorization: {$this->sign($method, $uri, $date, $length)}"); 290 | array_push($_headers, "Date: {$date}"); 291 | 292 | curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers); 293 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout); 294 | curl_setopt($ch, CURLOPT_HEADER, 1); 295 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 296 | //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 297 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0); 298 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); 299 | 300 | if ($method == 'PUT' || $method == 'POST') { 301 | curl_setopt($ch, CURLOPT_POST, 1); 302 | } else { 303 | curl_setopt($ch, CURLOPT_POST, 0); 304 | } 305 | 306 | if ($method == 'GET' && is_resource($file_handle)) { 307 | curl_setopt($ch, CURLOPT_HEADER, 0); 308 | curl_setopt($ch, CURLOPT_FILE, $file_handle); 309 | } 310 | 311 | if ($method == 'HEAD') { 312 | curl_setopt($ch, CURLOPT_NOBODY, true); 313 | } 314 | 315 | $response = curl_exec($ch); 316 | $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); 317 | 318 | 319 | if ($http_code == 0) throw new UpYunException('Connection Failed', $http_code); 320 | 321 | curl_close($ch); 322 | 323 | $header_string = ''; 324 | $body = ''; 325 | 326 | if ($method == 'GET' && is_resource($file_handle)) { 327 | $header_string = ''; 328 | $body = $response; 329 | } else { 330 | list($header_string, $body) = explode("\r\n\r\n", $response, 2); 331 | } 332 | $this->setXRequestId($header_string); 333 | if ($http_code == 200) { 334 | if ($method == 'GET' && is_null($file_handle)) { 335 | return $body; 336 | } else { 337 | $data = $this->_getHeadersData($header_string); 338 | return count($data) > 0 ? $data : true; 339 | } 340 | } else { 341 | $message = $this->_getErrorMessage($header_string); 342 | if (is_null($message) && $method == 'GET' && is_resource($file_handle)) { 343 | $message = 'File Not Found'; 344 | } 345 | switch($http_code) { 346 | case 401: 347 | throw new UpYunAuthorizationException($message); 348 | break; 349 | case 403: 350 | throw new UpYunForbiddenException($message); 351 | break; 352 | case 404: 353 | throw new UpYunNotFoundException($message); 354 | break; 355 | case 406: 356 | throw new UpYunNotAcceptableException($message); 357 | break; 358 | case 503: 359 | throw new UpYunServiceUnavailable($message); 360 | break; 361 | default: 362 | throw new UpYunException($message, $http_code); 363 | } 364 | } 365 | }/*}}}*/ 366 | 367 | /** 368 | * 处理HTTP HEADERS中返回的自定义数据 369 | * 370 | * @param string $text header字符串 371 | * 372 | * @return array 373 | */ 374 | private function _getHeadersData($text) {/*{{{*/ 375 | $headers = explode("\r\n", $text); 376 | $items = array(); 377 | foreach($headers as $header) { 378 | $header = trim($header); 379 | if(stripos($header, 'x-upyun') !== False){ 380 | list($k, $v) = explode(':', $header); 381 | $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v); 382 | } 383 | } 384 | return $items; 385 | }/*}}}*/ 386 | 387 | /** 388 | * 获取返回的错误信息 389 | * 390 | * @param string $header_string 391 | * 392 | * @return mixed 393 | */ 394 | private function _getErrorMessage($header_string) { 395 | list($status, $stash) = explode("\r\n", $header_string, 2); 396 | list($v, $code, $message) = explode(" ", $status, 3); 397 | return $message . " X-Request-Id: " . $this->getXRequestId(); 398 | } 399 | 400 | private function setXRequestId($header_string) { 401 | preg_match('~^X-Request-Id: ([0-9a-zA-Z]{32})~ism', $header_string, $result); 402 | $this->x_request_id = isset($result[1]) ? $result[1] : ''; 403 | } 404 | 405 | public function getXRequestId() { 406 | return $this->x_request_id; 407 | } 408 | 409 | /** 410 | * 删除目录 411 | * @deprecated 412 | * @param $path 路径 413 | * 414 | * @return void 415 | */ 416 | public function rmDir($path) {/*{{{*/ 417 | $this->_do_request('DELETE', '/'.$path); 418 | }/*}}}*/ 419 | 420 | /** 421 | * 删除文件 422 | * 423 | * @deprecated 424 | * @param string $path 要删除的文件路径 425 | * 426 | * @return boolean 427 | */ 428 | public function deleteFile($path) {/*{{{*/ 429 | $rsp = $this->_do_request('DELETE', '/'.$path); 430 | }/*}}}*/ 431 | 432 | /** 433 | * 获取目录文件列表 434 | * @deprecated 435 | * 436 | * @param string $path 要获取列表的目录 437 | * 438 | * @return array 439 | */ 440 | public function readDir($path) {/*{{{*/ 441 | return $this->getList($path); 442 | }/*}}}*/ 443 | 444 | /** 445 | * 获取空间使用情况 446 | * 447 | * @deprecated 推荐直接使用 getFolderUsage('/')来获取 448 | * @return mixed 449 | */ 450 | public function getBucketUsage() {/*{{{*/ 451 | return $this->getFolderUsage('/'); 452 | }/*}}}*/ 453 | 454 | /** 455 | * 获取文件信息 456 | * 457 | * #deprecated 458 | * @param $file 文件路径(包含文件名) 459 | * return array('type'=> file | folder, 'size'=> file size, 'date'=> unix time) 或 null 460 | */ 461 | //public function getFileInfo($file){/*{{{*/ 462 | // $result = $this->head($file); 463 | // if(is_null($r))return null; 464 | // return array('type'=> $this->tmp_infos['x-upyun-file-type'], 'size'=> @intval($this->tmp_infos['x-upyun-file-size']), 'date'=> @intval($this->tmp_infos['x-upyun-file-date'])); 465 | //}/*}}}*/ 466 | 467 | /** 468 | * 切换 API 接口的域名 469 | * 470 | * @deprecated 471 | * @param $domain {默然 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动} 472 | * return null; 473 | */ 474 | public function setApiDomain($domain){/*{{{*/ 475 | $this->endpoint = $domain; 476 | }/*}}}*/ 477 | 478 | /** 479 | * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误) 480 | * 481 | * @deprecated 482 | * @param $str (文件 MD5 校验码) 483 | * return null; 484 | */ 485 | public function setContentMD5($str){/*{{{*/ 486 | $this->_content_md5 = $str; 487 | }/*}}}*/ 488 | 489 | /** 490 | * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问) 491 | * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac 492 | * 493 | * @deprecated 494 | * @param $str (文件 MD5 校验码) 495 | * return null; 496 | */ 497 | public function setFileSecret($str){/*{{{*/ 498 | $this->_file_secret = $str; 499 | }/*}}}*/ 500 | 501 | /** 502 | * @deprecated 503 | * 获取上传文件后的信息(仅图片空间有返回数据) 504 | * @param $key 信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file-type) 505 | * return value or NULL 506 | */ 507 | public function getWritedFileInfo($key){/*{{{*/ 508 | if(!isset($this->_file_infos))return NULL; 509 | return $this->_file_infos[$key]; 510 | }/*}}}*/ 511 | } 512 | -------------------------------------------------------------------------------- /src/Cos/cos-php-sdk-v4/qcloudcos/cosapi.php: -------------------------------------------------------------------------------- 1 | COSAPI_PARAMS_ERROR, 56 | 'message' => 'file ' . $srcPath .' not exists', 57 | 'data' => array() 58 | ); 59 | } 60 | 61 | $dstPath = self::normalizerPath($dstPath, false); 62 | 63 | //文件大于20M则使用分片传输 64 | if (filesize($srcPath) < self::MAX_UNSLICE_FILE_SIZE ) { 65 | return self::uploadFile($bucket, $srcPath, $dstPath, $bizAttr, $insertOnly); 66 | } else { 67 | $sliceSize = self::getSliceSize($sliceSize); 68 | return self::uploadBySlicing($bucket, $srcPath, $dstPath, $bizAttr, $sliceSize, $insertOnly); 69 | } 70 | } 71 | 72 | /* 73 | * 创建目录 74 | * @param string $bucket bucket名称 75 | * @param string $folder 目录路径 76 | * @param string $bizAttr 目录属性 77 | */ 78 | public static function createFolder($bucket, $folder, $bizAttr = null) { 79 | if (!self::isValidPath($folder)) { 80 | return array( 81 | 'code' => COSAPI_PARAMS_ERROR, 82 | 'message' => 'folder ' . $path . ' is not a valid folder name', 83 | 'data' => array() 84 | ); 85 | } 86 | 87 | $folder = self::normalizerPath($folder, True); 88 | $folder = self::cosUrlEncode($folder); 89 | $expired = time() + self::EXPIRED_SECONDS; 90 | $url = self::generateResUrl($bucket, $folder); 91 | $signature = Auth::createReusableSignature($expired, $bucket); 92 | 93 | $data = array( 94 | 'op' => 'create', 95 | 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), 96 | ); 97 | 98 | $data = json_encode($data); 99 | 100 | $req = array( 101 | 'url' => $url, 102 | 'method' => 'post', 103 | 'timeout' => self::$timeout, 104 | 'data' => $data, 105 | 'header' => array( 106 | 'Authorization: ' . $signature, 107 | 'Content-Type: application/json', 108 | ), 109 | ); 110 | 111 | return self::sendRequest($req); 112 | } 113 | 114 | /* 115 | * 目录列表 116 | * @param string $bucket bucket名称 117 | * @param string $path 目录路径,sdk会补齐末尾的 '/' 118 | * @param int $num 拉取的总数 119 | * @param string $pattern eListBoth,ListDirOnly,eListFileOnly 默认both 120 | * @param int $order 默认正序(=0), 填1为反序, 121 | * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 122 | */ 123 | public static function listFolder( 124 | $bucket, $folder, $num = 20, 125 | $pattern = 'eListBoth', $order = 0, 126 | $context = null) { 127 | $folder = self::normalizerPath($folder, True); 128 | 129 | return self::listBase($bucket, $folder, $num, $pattern, $order, $context); 130 | } 131 | 132 | /* 133 | * 目录列表(前缀搜索) 134 | * @param string $bucket bucket名称 135 | * @param string $prefix 列出含此前缀的所有文件 136 | * @param int $num 拉取的总数 137 | * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly 138 | * @param int $order 默认正序(=0), 填1为反序, 139 | * @param string $offset 透传字段,用于翻页,前端不需理解,需要往前/往后翻页则透传回来 140 | */ 141 | public static function prefixSearch( 142 | $bucket, $prefix, $num = 20, 143 | $pattern = 'eListBoth', $order = 0, 144 | $context = null) { 145 | $path = self::normalizerPath($prefix); 146 | 147 | return self::listBase($bucket, $prefix, $num, $pattern, $order, $context); 148 | } 149 | 150 | /* 151 | * 目录更新 152 | * @param string $bucket bucket名称 153 | * @param string $folder 文件夹路径,SDK会补齐末尾的 '/' 154 | * @param string $bizAttr 目录属性 155 | */ 156 | public static function updateFolder($bucket, $folder, $bizAttr = null) { 157 | $folder = self::normalizerPath($folder, True); 158 | 159 | return self::updateBase($bucket, $folder, $bizAttr); 160 | } 161 | 162 | /* 163 | * 查询目录信息 164 | * @param string $bucket bucket名称 165 | * @param string $folder 目录路径 166 | */ 167 | public static function statFolder($bucket, $folder) { 168 | $folder = self::normalizerPath($folder, True); 169 | 170 | return self::statBase($bucket, $folder); 171 | } 172 | 173 | /* 174 | * 删除目录 175 | * @param string $bucket bucket名称 176 | * @param string $folder 目录路径 177 | * 注意不能删除bucket下根目录/ 178 | */ 179 | public static function delFolder($bucket, $folder) { 180 | if (empty($bucket) || empty($folder)) { 181 | return array( 182 | 'code' => COSAPI_PARAMS_ERROR, 183 | 'message' => 'bucket or path is empty'); 184 | } 185 | 186 | $folder = self::normalizerPath($folder, True); 187 | 188 | return self::delBase($bucket, $folder); 189 | } 190 | 191 | /* 192 | * 更新文件 193 | * @param string $bucket bucket名称 194 | * @param string $path 文件路径 195 | * @param string $authority: eInvalid(继承Bucket的读写权限)/eWRPrivate(私有读写)/eWPrivateRPublic(公有读私有写) 196 | * @param array $customer_headers_array 携带的用户自定义头域,包括 197 | * 'Cache-Control' => '*' 198 | * 'Content-Type' => '*' 199 | * 'Content-Disposition' => '*' 200 | * 'Content-Language' => '*' 201 | * 'x-cos-meta-自定义内容' => '*' 202 | */ 203 | public static function update($bucket, $path, 204 | $bizAttr = null, $authority=null,$customer_headers_array=null) { 205 | $path = self::normalizerPath($path); 206 | 207 | return self::updateBase($bucket, $path, $bizAttr, $authority, $customer_headers_array); 208 | } 209 | 210 | /* 211 | * 查询文件信息 212 | * @param string $bucket bucket名称 213 | * @param string $path 文件路径 214 | */ 215 | public static function stat($bucket, $path) { 216 | $path = self::normalizerPath($path); 217 | 218 | return self::statBase($bucket, $path); 219 | } 220 | 221 | /* 222 | * 删除文件 223 | * @param string $bucket 224 | * @param string $path 文件路径 225 | */ 226 | public static function delFile($bucket, $path) { 227 | if (empty($bucket) || empty($path)) { 228 | return array( 229 | 'code' => COSAPI_PARAMS_ERROR, 230 | 'message' => 'path is empty'); 231 | } 232 | 233 | $path = self::normalizerPath($path); 234 | 235 | return self::delBase($bucket, $path); 236 | } 237 | 238 | /** 239 | * 内部方法, 上传文件 240 | * @param string $bucket bucket名称 241 | * @param string $srcPath 本地文件路径 242 | * @param string $dstPath 上传的文件路径 243 | * @param string $bizAttr 文件属性 244 | * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 245 | * @return [type] [description] 246 | */ 247 | private static function uploadFile($bucket, $srcPath, $dstPath, $bizAttr = null, $insertOnly = null) { 248 | $srcPath = realpath($srcPath); 249 | $dstPath = self::cosUrlEncode($dstPath); 250 | 251 | if (filesize($srcPath) >= self::MAX_UNSLICE_FILE_SIZE ) { 252 | return array( 253 | 'code' => COSAPI_PARAMS_ERROR, 254 | 'message' => 'file '.$srcPath.' larger then 20M, please use uploadBySlicing interface', 255 | 'data' => array() 256 | ); 257 | } 258 | 259 | $expired = time() + self::EXPIRED_SECONDS; 260 | $url = self::generateResUrl($bucket, $dstPath); 261 | $signature = Auth::createReusableSignature($expired, $bucket); 262 | $fileSha = hash_file('sha1', $srcPath); 263 | 264 | $data = array( 265 | 'op' => 'upload', 266 | 'sha' => $fileSha, 267 | 'biz_attr' => (isset($bizAttr) ? $bizAttr : ''), 268 | ); 269 | 270 | $data['filecontent'] = file_get_contents($srcPath); 271 | 272 | if (isset($insertOnly) && strlen($insertOnly) > 0) { 273 | $data['insertOnly'] = (($insertOnly == 0 || $insertOnly == '0' ) ? 0 : 1); 274 | } 275 | 276 | $req = array( 277 | 'url' => $url, 278 | 'method' => 'post', 279 | 'timeout' => self::$timeout, 280 | 'data' => $data, 281 | 'header' => array( 282 | 'Authorization: ' . $signature, 283 | ), 284 | ); 285 | 286 | return self::sendRequest($req); 287 | } 288 | 289 | /** 290 | * 内部方法,上传文件 291 | * @param string $bucket bucket名称 292 | * @param string $srcPath 本地文件路径 293 | * @param string $dstPath 上传的文件路径 294 | * @param string $bizAttr 文件属性 295 | * @param string $sliceSize 分片大小 296 | * @param int $insertOnly 是否覆盖同名文件:0 覆盖,1:不覆盖 297 | * @return [type] [description] 298 | */ 299 | private static function uploadBySlicing( 300 | $bucket, $srcFpath, $dstFpath, $bizAttr=null, $sliceSize=null, $insertOnly=null) { 301 | $srcFpath = realpath($srcFpath); 302 | $fileSize = filesize($srcFpath); 303 | $dstFpath = self::cosUrlEncode($dstFpath); 304 | $url = self::generateResUrl($bucket, $dstFpath); 305 | $sliceCount = ceil($fileSize / $sliceSize); 306 | // expiration seconds for one slice mutiply by slice count 307 | // will be the expired seconds for whole file 308 | $expiration = time() + (self::EXPIRED_SECONDS * $sliceCount); 309 | if ($expiration >= (time() + 10 * 24 * 60 * 60)) { 310 | $expiration = time() + 10 * 24 * 60 * 60; 311 | } 312 | $signature = Auth::createReusableSignature($expiration, $bucket); 313 | 314 | $sliceUploading = new SliceUploading(self::$timeout * 1000, self::MAX_RETRY_TIMES); 315 | for ($tryCount = 0; $tryCount < self::MAX_RETRY_TIMES; ++$tryCount) { 316 | if ($sliceUploading->initUploading( 317 | $signature, 318 | $srcFpath, 319 | $url, 320 | $fileSize, $sliceSize, $bizAttr, $insertOnly)) { 321 | break; 322 | } 323 | 324 | $errorCode = $sliceUploading->getLastErrorCode(); 325 | if ($errorCode === -4019) { 326 | // Delete broken file and retry again on _ERROR_FILE_NOT_FINISH_UPLOAD error. 327 | Cosapi::delFile($bucket, $dstFpath); 328 | continue; 329 | } 330 | 331 | if ($tryCount === self::MAX_RETRY_TIMES - 1) { 332 | return array( 333 | 'code' => $sliceUploading->getLastErrorCode(), 334 | 'message' => $sliceUploading->getLastErrorMessage(), 335 | 'requestId' => $sliceUploading->getRequestId(), 336 | ); 337 | } 338 | } 339 | 340 | if (!$sliceUploading->performUploading()) { 341 | return array( 342 | 'code' => $sliceUploading->getLastErrorCode(), 343 | 'message' => $sliceUploading->getLastErrorMessage(), 344 | 'requestId' => $sliceUploading->getRequestId(), 345 | ); 346 | } 347 | 348 | if (!$sliceUploading->finishUploading()) { 349 | return array( 350 | 'code' => $sliceUploading->getLastErrorCode(), 351 | 'message' => $sliceUploading->getLastErrorMessage(), 352 | 'requestId' => $sliceUploading->getRequestId(), 353 | ); 354 | } 355 | 356 | return array( 357 | 'code' => 0, 358 | 'message' => 'success', 359 | 'requestId' => $sliceUploading->getRequestId(), 360 | 'data' => array( 361 | 'accessUrl' => $sliceUploading->getAccessUrl(), 362 | 'resourcePath' => $sliceUploading->getResourcePath(), 363 | 'sourceUrl' => $sliceUploading->getSourceUrl(), 364 | ), 365 | ); 366 | } 367 | 368 | /* 369 | * 内部公共函数 370 | * @param string $bucket bucket名称 371 | * @param string $path 文件夹路径 372 | * @param int $num 拉取的总数 373 | * @param string $pattern eListBoth(默认),ListDirOnly,eListFileOnly 374 | * @param int $order 默认正序(=0), 填1为反序, 375 | * @param string $context 在翻页查询时候用到 376 | */ 377 | private static function listBase( 378 | $bucket, $path, $num = 20, $pattern = 'eListBoth', $order = 0, $context = null) { 379 | $path = self::cosUrlEncode($path); 380 | $expired = time() + self::EXPIRED_SECONDS; 381 | $url = self::generateResUrl($bucket, $path); 382 | $signature = Auth::createReusableSignature($expired, $bucket); 383 | 384 | $data = array( 385 | 'op' => 'list', 386 | ); 387 | 388 | if (self::isPatternValid($pattern) == false) { 389 | return array( 390 | 'code' => COSAPI_PARAMS_ERROR, 391 | 'message' => 'parameter pattern invalid', 392 | ); 393 | } 394 | $data['pattern'] = $pattern; 395 | 396 | if ($order != 0 && $order != 1) { 397 | return array( 398 | 'code' => COSAPI_PARAMS_ERROR, 399 | 'message' => 'parameter order invalid', 400 | ); 401 | } 402 | $data['order'] = $order; 403 | 404 | if ($num < 0 || $num > 199) { 405 | return array( 406 | 'code' => COSAPI_PARAMS_ERROR, 407 | 'message' => 'parameter num invalid, num need less then 200', 408 | ); 409 | } 410 | $data['num'] = $num; 411 | 412 | if (isset($context)) { 413 | $data['context'] = $context; 414 | } 415 | 416 | $url = $url . '?' . http_build_query($data); 417 | 418 | $req = array( 419 | 'url' => $url, 420 | 'method' => 'get', 421 | 'timeout' => self::$timeout, 422 | 'header' => array( 423 | 'Authorization: ' . $signature, 424 | ), 425 | ); 426 | 427 | return self::sendRequest($req); 428 | } 429 | 430 | /* 431 | * 内部公共方法(更新文件和更新文件夹) 432 | * @param string $bucket bucket名称 433 | * @param string $path 路径 434 | * @param string $bizAttr 文件/目录属性 435 | * @param string $authority: eInvalid/eWRPrivate(私有)/eWPrivateRPublic(公有读写) 436 | * @param array $customer_headers_array 携带的用户自定义头域,包括 437 | * 'Cache-Control' => '*' 438 | * 'Content-Type' => '*' 439 | * 'Content-Disposition' => '*' 440 | * 'Content-Language' => '*' 441 | * 'x-cos-meta-自定义内容' => '*' 442 | */ 443 | private static function updateBase( 444 | $bucket, $path, $bizAttr = null, $authority = null, $custom_headers_array = null) { 445 | $path = self::cosUrlEncode($path); 446 | $expired = time() + self::EXPIRED_SECONDS; 447 | $url = self::generateResUrl($bucket, $path); 448 | $signature = Auth::createNonreusableSignature($bucket, $path); 449 | 450 | $data = array('op' => 'update'); 451 | 452 | if (isset($bizAttr)) { 453 | $data['biz_attr'] = $bizAttr; 454 | } 455 | 456 | if (isset($authority) && strlen($authority) > 0) { 457 | if(self::isAuthorityValid($authority) == false) { 458 | return array( 459 | 'code' => COSAPI_PARAMS_ERROR, 460 | 'message' => 'parameter authority invalid'); 461 | } 462 | 463 | $data['authority'] = $authority; 464 | } 465 | 466 | if (isset($custom_headers_array)) { 467 | $data['custom_headers'] = array(); 468 | self::add_customer_header($data['custom_headers'], $custom_headers_array); 469 | } 470 | 471 | $data = json_encode($data); 472 | 473 | $req = array( 474 | 'url' => $url, 475 | 'method' => 'post', 476 | 'timeout' => self::$timeout, 477 | 'data' => $data, 478 | 'header' => array( 479 | 'Authorization: ' . $signature, 480 | 'Content-Type: application/json', 481 | ), 482 | ); 483 | 484 | return self::sendRequest($req); 485 | } 486 | 487 | /* 488 | * 内部方法 489 | * @param string $bucket bucket名称 490 | * @param string $path 文件/目录路径 491 | */ 492 | private static function statBase($bucket, $path) { 493 | $path = self::cosUrlEncode($path); 494 | $expired = time() + self::EXPIRED_SECONDS; 495 | $url = self::generateResUrl($bucket, $path); 496 | $signature = Auth::createReusableSignature($expired, $bucket); 497 | 498 | $data = array('op' => 'stat'); 499 | 500 | $url = $url . '?' . http_build_query($data); 501 | 502 | $req = array( 503 | 'url' => $url, 504 | 'method' => 'get', 505 | 'timeout' => self::$timeout, 506 | 'header' => array( 507 | 'Authorization: ' . $signature, 508 | ), 509 | ); 510 | 511 | return self::sendRequest($req); 512 | } 513 | 514 | /* 515 | * 内部私有方法 516 | * @param string $bucket bucket名称 517 | * @param string $path 文件/目录路径路径 518 | */ 519 | private static function delBase($bucket, $path) { 520 | if ($path == "/") { 521 | return array( 522 | 'code' => COSAPI_PARAMS_ERROR, 523 | 'message' => 'can not delete bucket using api! go to ' . 524 | 'http://console.qcloud.com/cos to operate bucket'); 525 | } 526 | 527 | $path = self::cosUrlEncode($path); 528 | $expired = time() + self::EXPIRED_SECONDS; 529 | $url = self::generateResUrl($bucket, $path); 530 | $signature = Auth::createNonreusableSignature($bucket, $path); 531 | 532 | $data = array('op' => 'delete'); 533 | 534 | $data = json_encode($data); 535 | 536 | $req = array( 537 | 'url' => $url, 538 | 'method' => 'post', 539 | 'timeout' => self::$timeout, 540 | 'data' => $data, 541 | 'header' => array( 542 | 'Authorization: ' . $signature, 543 | 'Content-Type: application/json', 544 | ), 545 | ); 546 | 547 | return self::sendRequest($req); 548 | } 549 | 550 | /* 551 | * 内部公共方法, 路径编码 552 | * @param string $path 待编码路径 553 | */ 554 | private static function cosUrlEncode($path) { 555 | return str_replace('%2F', '/', rawurlencode($path)); 556 | } 557 | 558 | /* 559 | * 内部公共方法, 构造URL 560 | * @param string $bucket 561 | * @param string $dstPath 562 | */ 563 | private static function generateResUrl($bucket, $dstPath) { 564 | $endPoint = Conf::API_COSAPI_END_POINT; 565 | $endPoint = str_replace('region', self::$region, $endPoint); 566 | 567 | return $endPoint . Conf::APP_ID . '/' . $bucket . $dstPath; 568 | } 569 | 570 | /* 571 | * 内部公共方法, 发送消息 572 | * @param string $req 573 | */ 574 | private static function sendRequest($req) { 575 | $rsp = HttpClient::sendRequest($req); 576 | if ($rsp === false) { 577 | return array( 578 | 'code' => COSAPI_NETWORK_ERROR, 579 | 'message' => 'network error', 580 | ); 581 | } 582 | 583 | $info = HttpClient::info(); 584 | $ret = json_decode($rsp, true); 585 | 586 | if ($ret === NULL) { 587 | return array( 588 | 'code' => COSAPI_NETWORK_ERROR, 589 | 'message' => $rsp, 590 | 'data' => array() 591 | ); 592 | } 593 | 594 | return $ret; 595 | } 596 | 597 | /** 598 | * Get slice size. 599 | */ 600 | private static function getSliceSize($sliceSize) { 601 | // Fix slice size to 1MB. 602 | return self::SLICE_SIZE_1M; 603 | } 604 | 605 | /* 606 | * 内部方法, 规整文件路径 607 | * @param string $path 文件路径 608 | * @param string $isfolder 是否为文件夹 609 | */ 610 | private static function normalizerPath($path, $isfolder = False) { 611 | if (preg_match('/^\//', $path) == 0) { 612 | $path = '/' . $path; 613 | } 614 | 615 | if ($isfolder == True) { 616 | if (preg_match('/\/$/', $path) == 0) { 617 | $path = $path . '/'; 618 | } 619 | } 620 | 621 | // Remove unnecessary slashes. 622 | $path = preg_replace('#/+#', '/', $path); 623 | 624 | return $path; 625 | } 626 | 627 | /** 628 | * 判断authority值是否正确 629 | * @param string $authority 630 | * @return [type] bool 631 | */ 632 | private static function isAuthorityValid($authority) { 633 | if ($authority == 'eInvalid' || $authority == 'eWRPrivate' || $authority == 'eWPrivateRPublic') { 634 | return true; 635 | } 636 | return false; 637 | } 638 | 639 | /** 640 | * 判断pattern值是否正确 641 | * @param string $authority 642 | * @return [type] bool 643 | */ 644 | private static function isPatternValid($pattern) { 645 | if ($pattern == 'eListBoth' || $pattern == 'eListDirOnly' || $pattern == 'eListFileOnly') { 646 | return true; 647 | } 648 | return false; 649 | } 650 | 651 | /** 652 | * 判断是否符合自定义属性 653 | * @param string $key 654 | * @return [type] bool 655 | */ 656 | private static function isCustomer_header($key) { 657 | if ($key == 'Cache-Control' || $key == 'Content-Type' || 658 | $key == 'Content-Disposition' || $key == 'Content-Language' || 659 | $key == 'Content-Encoding' || 660 | substr($key,0,strlen('x-cos-meta-')) == 'x-cos-meta-') { 661 | return true; 662 | } 663 | return false; 664 | } 665 | 666 | /** 667 | * 增加自定义属性到data中 668 | * @param array $data 669 | * @param array $customer_headers_array 670 | * @return [type] void 671 | */ 672 | private static function add_customer_header(&$data, &$customer_headers_array) { 673 | if (count($customer_headers_array) < 1) { 674 | return; 675 | } 676 | foreach($customer_headers_array as $key=>$value) { 677 | if(self::isCustomer_header($key)) { 678 | $data[$key] = $value; 679 | } 680 | } 681 | } 682 | 683 | // Check |$path| is a valid file path. 684 | // Return true on success, otherwise return false. 685 | private static function isValidPath($path) { 686 | if (strpos($path, '?') !== false) { 687 | return false; 688 | } 689 | if (strpos($path, '*') !== false) { 690 | return false; 691 | } 692 | if (strpos($path, ':') !== false) { 693 | return false; 694 | } 695 | if (strpos($path, '|') !== false) { 696 | return false; 697 | } 698 | if (strpos($path, '\\') !== false) { 699 | return false; 700 | } 701 | if (strpos($path, '<') !== false) { 702 | return false; 703 | } 704 | if (strpos($path, '>') !== false) { 705 | return false; 706 | } 707 | if (strpos($path, '"') !== false) { 708 | return false; 709 | } 710 | 711 | return true; 712 | } 713 | 714 | /** 715 | * Copy a file. 716 | * @param $bucket bucket name. 717 | * @param $srcFpath source file path. 718 | * @param $dstFpath destination file path. 719 | * @param $overwrite if the destination location is occupied, overwrite it or not? 720 | * @return array|mixed. 721 | */ 722 | public static function copyFile($bucket, $srcFpath, $dstFpath, $overwrite = false) { 723 | $url = self::generateResUrl($bucket, $srcFpath); 724 | $sign = Auth::createNonreusableSignature($bucket, $srcFpath); 725 | $data = array( 726 | 'op' => 'copy', 727 | 'dest_fileid' => $dstFpath, 728 | 'to_over_write' => $overwrite ? 1 : 0, 729 | ); 730 | $req = array( 731 | 'url' => $url, 732 | 'method' => 'post', 733 | 'timeout' => self::$timeout, 734 | 'data' => $data, 735 | 'header' => array( 736 | 'Authorization: ' . $sign, 737 | ), 738 | ); 739 | 740 | return self::sendRequest($req); 741 | } 742 | 743 | /** 744 | * Move a file. 745 | * @param $bucket bucket name. 746 | * @param $srcFpath source file path. 747 | * @param $dstFpath destination file path. 748 | * @param $overwrite if the destination location is occupied, overwrite it or not? 749 | * @return array|mixed. 750 | */ 751 | public static function moveFile($bucket, $srcFpath, $dstFpath, $overwrite = false) { 752 | $url = self::generateResUrl($bucket, $srcFpath); 753 | $sign = Auth::createNonreusableSignature($bucket, $srcFpath); 754 | $data = array( 755 | 'op' => 'move', 756 | 'dest_fileid' => $dstFpath, 757 | 'to_over_write' => $overwrite ? 1 : 0, 758 | ); 759 | $req = array( 760 | 'url' => $url, 761 | 'method' => 'post', 762 | 'timeout' => self::$timeout, 763 | 'data' => $data, 764 | 'header' => array( 765 | 'Authorization: ' . $sign, 766 | ), 767 | ); 768 | 769 | return self::sendRequest($req); 770 | } 771 | } 772 | --------------------------------------------------------------------------------