├── 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 = 'xgmkerl-quality';
63 | const X_GMKERL_UNSHARP = 'xgmkerl-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 |
--------------------------------------------------------------------------------