wcq1 8 éve
commit
dc13a52116
88 módosított fájl, 5612 hozzáadás és 0 törlés
  1. 12 0
      .gitignore
  2. 35 0
      .scrutinizer.yml
  3. 23 0
      .travis.yml
  4. 92 0
      CHANGELOG.md
  5. 30 0
      CONTRIBUTING.md
  6. 22 0
      LICENSE
  7. 75 0
      README.md
  8. 14 0
      autoload.php
  9. 26 0
      composer.json
  10. 10 0
      examples/README.md
  11. 40 0
      examples/cdn_get_bandwidth.php
  12. 34 0
      examples/cdn_get_flux.php
  13. 29 0
      examples/cdn_get_log_list.php
  14. 52 0
      examples/cdn_refresh_urls_dirs.php
  15. 19 0
      examples/cdn_timestamp_antileech.php
  16. 74 0
      examples/image_url_builder.php
  17. 19 0
      examples/persistent_fop_init.php
  18. 12 0
      examples/persistent_fop_status.php
  19. 43 0
      examples/pfop_mkzip.php
  20. 46 0
      examples/pfop_vframe.php
  21. 47 0
      examples/pfop_video_avthumb.php
  22. 52 0
      examples/pfop_watermark.php
  23. BIN
      examples/php-logo.png
  24. 29 0
      examples/prefop.php
  25. 11 0
      examples/qetag.php
  26. 28 0
      examples/rs_batch_change_mime.php
  27. 34 0
      examples/rs_batch_change_type.php
  28. 36 0
      examples/rs_batch_copy.php
  29. 28 0
      examples/rs_batch_delete.php
  30. 34 0
      examples/rs_batch_delete_after_days.php
  31. 36 0
      examples/rs_batch_move.php
  32. 28 0
      examples/rs_batch_stat.php
  33. 19 0
      examples/rs_bucket_domains.php
  34. 19 0
      examples/rs_buckets.php
  35. 20 0
      examples/rs_change_mime.php
  36. 20 0
      examples/rs_change_type.php
  37. 22 0
      examples/rs_copy.php
  38. 17 0
      examples/rs_delete.php
  39. 20 0
      examples/rs_delete_after_days.php
  40. 17 0
      examples/rs_download_urls.php
  41. 34 0
      examples/rs_fetch.php
  42. 22 0
      examples/rs_move.php
  43. 17 0
      examples/rs_prefetch.php
  44. 19 0
      examples/rs_stat.php
  45. 46 0
      examples/rsf_list_bucket.php
  46. 38 0
      examples/rsf_list_files.php
  47. 28 0
      examples/saveas.php
  48. 31 0
      examples/upload_and_callback.php
  49. 38 0
      examples/upload_and_pfop.php
  50. 18 0
      examples/upload_mgr_init.php
  51. 85 0
      examples/upload_multi_demos.php
  52. 37 0
      examples/upload_simple_file.php
  53. 68 0
      examples/upload_tokens.php
  54. 32 0
      examples/upload_verify_callback.php
  55. 19 0
      phpunit.xml.dist
  56. 187 0
      src/Qiniu/Auth.php
  57. 190 0
      src/Qiniu/Cdn/CdnManager.php
  58. 134 0
      src/Qiniu/Config.php
  59. 76 0
      src/Qiniu/Etag.php
  60. 150 0
      src/Qiniu/Http/Client.php
  61. 35 0
      src/Qiniu/Http/Error.php
  62. 18 0
      src/Qiniu/Http/Request.php
  63. 176 0
      src/Qiniu/Http/Response.php
  64. 282 0
      src/Qiniu/Processing/ImageUrlBuilder.php
  65. 60 0
      src/Qiniu/Processing/Operation.php
  66. 94 0
      src/Qiniu/Processing/PersistentFop.php
  67. 475 0
      src/Qiniu/Storage/BucketManager.php
  68. 139 0
      src/Qiniu/Storage/FormUploader.php
  69. 169 0
      src/Qiniu/Storage/ResumeUploader.php
  70. 144 0
      src/Qiniu/Storage/UploadManager.php
  71. 171 0
      src/Qiniu/Zone.php
  72. 264 0
      src/Qiniu/functions.php
  73. 4 0
      test-env.sh
  74. 71 0
      tests/Qiniu/Tests/AuthTest.php
  75. 14 0
      tests/Qiniu/Tests/Base64Test.php
  76. 227 0
      tests/Qiniu/Tests/BucketTest.php
  77. 50 0
      tests/Qiniu/Tests/CdnManagerTest.php
  78. 21 0
      tests/Qiniu/Tests/Crc32Test.php
  79. 25 0
      tests/Qiniu/Tests/DownloadTest.php
  80. 52 0
      tests/Qiniu/Tests/EtagTest.php
  81. 37 0
      tests/Qiniu/Tests/FopTest.php
  82. 59 0
      tests/Qiniu/Tests/FormUpTest.php
  83. 43 0
      tests/Qiniu/Tests/HttpTest.php
  84. 261 0
      tests/Qiniu/Tests/ImageUrlBuilderTest.php
  85. 66 0
      tests/Qiniu/Tests/PfopTest.php
  86. 60 0
      tests/Qiniu/Tests/ResumeUpTest.php
  87. 59 0
      tests/Qiniu/Tests/ZoneTest.php
  88. 43 0
      tests/bootstrap.php

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+*.phar
+*.zip
+build/artifacts
+phpunit.xml
+phpunit.functional.xml
+.DS_Store
+.swp
+.build
+composer.lock
+vendor
+src/package.xml
+.idea/

+ 35 - 0
.scrutinizer.yml

@@ -0,0 +1,35 @@
+filter:
+    excluded_paths: [tests/*]
+checks:
+    php:
+        code_rating: true
+        remove_extra_empty_lines: true
+        remove_php_closing_tag: true
+        remove_trailing_whitespace: true
+        fix_use_statements:
+            remove_unused: true
+            preserve_multiple: false
+            preserve_blanklines: true
+            order_alphabetically: true
+        fix_php_opening_tag: true
+        fix_linefeed: true
+        fix_line_ending: true
+        fix_identation_4spaces: true
+        fix_doc_comments: true
+tools:
+    external_code_coverage:
+        timeout: 1200
+        runs: 3
+    php_analyzer: true
+    php_code_coverage: false
+    php_code_sniffer:
+        config:
+            standard: PSR2
+        filter:
+            paths: ['src']
+    php_loc:
+        enabled: true
+        excluded_dirs: [vendor, tests]
+    php_cpd:
+        enabled: true
+        excluded_dirs: [vendor, tests]

+ 23 - 0
.travis.yml

@@ -0,0 +1,23 @@
+sudo: false
+language: php
+
+php:
+  - 5.4
+  - 5.5
+  - 5.6
+  - 7.0
+
+before_script:
+  - export QINIU_TEST_ENV="travis"
+  - travis_retry composer self-update
+  - travis_retry composer install --no-interaction --prefer-source --dev
+
+script:
+  - ./vendor/bin/phpcs --standard=PSR2 src
+  - ./vendor/bin/phpcs --standard=PSR2 examples
+  - ./vendor/bin/phpcs --standard=PSR2 tests
+  - ./vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover tests/Qiniu/Tests/
+
+after_script:
+  - wget https://scrutinizer-ci.com/ocular.phar
+  - php ocular.phar code-coverage:upload --format=php-clover coverage.clover

+ 92 - 0
CHANGELOG.md

@@ -0,0 +1,92 @@
+# Changelog
+
+## 7.2.3 (2018-01-20)
+### 增加
+* 新加坡机房
+### 修正
+* 获取域名的入口域名
+* http回复头部兼容大小写
+
+## 7.2.2 (2017-11-06)
+### 增加
+* Qiniu算法的鉴权方法
+
+## 7.1.4 (2017-06-21)
+### 增加
+* cdn 文件/目录 刷新
+* cdn 获取 流量/带宽 
+* cdn 获取域名的访问日志列表
+* cdn 对资源链接进行时间戳防盗链签名
+
+## 7.1.3 (2016-11-18)
+### 增加
+* move, copy操作增加force参数
+
+## 7.1.2 (2016-11-12)
+### 修正
+* 明确抛出获取各区域域名失败时的报错
+
+## 7.1.1 (2016-11-02)
+### 修正
+* 多区域配置文件存储目录从home修改到tmp目录
+
+
+## 7.1.0 (2016-10-22)
+### 增加
+* 多存储区域的支持
+
+## 7.0.8 (2016-07-19)
+### 增加
+* demo
+* https url 支持
+* deleteAfterDays 策略
+* 添加图片处理链接统一拼接方法 by @SherlockRen
+
+## 7.0.7 (2016-01-12)
+### 修正
+* PersistentFop参数pipeline和notify_url失效
+* resume 模式 close file inputstream
+
+## 7.0.6 (2015-12-05)
+### 修正
+* php7.0 Json 对空字符串解析单元测试报错
+* 开启安全模式或者设置可操作目录树时,设置CURLOPT_FOLLOWLOCATION报错, by @twocabbages
+* fetch 支持不指定key, by @sinkcup
+
+## 7.0.5 (2015-10-29)
+### 增加
+* 增加上传策略最小文件大小限制 fsizeMin
+* 增加常见examples
+
+## 7.0.4 (2015-07-23)
+### 修正
+* 一些地方的严格比较检查
+* resumeupload 备用地址失效
+
+## 7.0.3 (2015-07-10)
+### 修改
+* 多zone 支持
+
+## 7.0.2 (2015-04-18)
+### 修改
+* fetch 接口返回内容调整
+* pfop 接口调整
+
+###修正
+* exception 类调用
+
+## 7.0.1 (2015-03-27)
+### 增加
+* 增加代码注释
+
+## 7.0.0 (2015-02-03)
+
+### 增加
+* 简化上传接口
+* 自动选择断点续上传还是直传
+* 重构代码,接口和内部结构更清晰
+* 改变mime
+* 代码覆盖度报告
+* policy改为array, 便于灵活增加,并加入过期字段检查
+* 文件列表支持目录形式
+* 利用元编程方式支持 fop 和 pfop

+ 30 - 0
CONTRIBUTING.md

@@ -0,0 +1,30 @@
+# 贡献代码指南
+
+我们非常欢迎大家来贡献代码,我们会向贡献者致以最诚挚的敬意。
+
+一般可以通过在Github上提交[Pull Request](https://github.com/qiniu/php-sdk)来贡献代码。
+
+## Pull Request要求
+
+- **[PSR-2 编码风格标准](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** 。要通过项目中的code sniffer检查。
+
+- **代码格式** 提交前 请按 ./vendor/bin/phpcbf --standard=PSR2  进行格式化。
+
+- **必须添加测试!** - 如果没有测试(单元测试、集成测试都可以),那么提交的补丁是不会通过的。
+
+- **记得更新文档** - 保证`README.md`以及其他相关文档及时更新,和代码的变更保持一致性。
+
+- **考虑我们的发布周期** - 我们的版本号会服从[SemVer v2.0.0](http://semver.org/),我们绝对不会随意变更对外的API。
+
+- **创建feature分支** - 最好不要从你的master分支提交 pull request。
+
+- **一个feature提交一个pull请求** - 如果你的代码变更了多个操作,那就提交多个pull请求吧。
+
+- **清晰的commit历史** - 保证你的pull请求的每次commit操作都是有意义的。如果你开发中需要执行多次的即时commit操作,那么请把它们放到一起再提交pull请求。
+
+## 运行测试
+
+``` bash
+./vendor/bin/phpunit tests/Qiniu/Tests/
+
+```

+ 22 - 0
LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Qiniu, Ltd.<sdk@qiniu.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 75 - 0
README.md

@@ -0,0 +1,75 @@
+# Qiniu Resource Storage SDK for PHP
+[![doxygen.io](http://doxygen.io/github.com/qiniu/php-sdk/?status.svg)](http://doxygen.io/github.com/qiniu/php-sdk/)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
+[![Build Status](https://travis-ci.org/qiniu/php-sdk.svg)](https://travis-ci.org/qiniu/php-sdk)
+[![Latest Stable Version](https://img.shields.io/packagist/v/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk)
+[![Total Downloads](https://img.shields.io/packagist/dt/qiniu/php-sdk.svg)](https://packagist.org/packages/qiniu/php-sdk)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[![Code Coverage](https://scrutinizer-ci.com/g/qiniu/php-sdk/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/qiniu/php-sdk/?branch=master)
+[![Join Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/qiniu/php-sdk?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![@qiniu on weibo](http://img.shields.io/badge/weibo-%40qiniutek-blue.svg)](http://weibo.com/qiniutek)
+
+## 安装
+
+* 通过composer,这是推荐的方式,可以使用composer.json 声明依赖,或者运行下面的命令。SDK 包已经放到这里 [`qiniu/php-sdk`][install-packagist] 。
+```bash
+$ composer require qiniu/php-sdk
+```
+* 直接下载安装,SDK 没有依赖其他第三方库,但需要参照 composer的autoloader,增加一个自己的autoloader程序。
+
+## 运行环境
+
+| Qiniu SDK版本 | PHP 版本 |
+|:--------------------:|:---------------------------:|
+|          7.x         |  cURL extension,   5.3 - 5.6,7.0 |
+|          6.x         |  cURL extension,   5.2 - 5.6 |
+
+## 使用方法
+
+### 上传
+```php
+use Qiniu\Storage\UploadManager;
+use Qiniu\Auth;
+...
+    $upManager = new UploadManager();
+    $auth = new Auth($accessKey, $secretKey);
+    $token = $auth->uploadToken($bucketName);
+    list($ret, $error) = $upManager->put($token, 'formput', 'hello world');
+...
+```
+
+## 测试
+
+``` bash
+$ ./vendor/bin/phpunit tests/Qiniu/Tests/
+```
+
+## 常见问题
+
+- $error保留了请求响应的信息,失败情况下ret 为none, 将$error可以打印出来,提交给我们。
+- API 的使用 demo 可以参考 [单元测试](https://github.com/qiniu/php-sdk/blob/master/tests)。
+
+## 代码贡献
+
+详情参考[代码提交指南](https://github.com/qiniu/php-sdk/blob/master/CONTRIBUTING.md)。
+
+## 贡献记录
+
+- [所有贡献者](https://github.com/qiniu/php-sdk/contributors)
+
+## 联系我们
+
+- 如果需要帮助,请提交工单(在portal右侧点击咨询和建议提交工单,或者直接向 support@qiniu.com 发送邮件)
+- 如果有什么问题,可以到问答社区提问,[问答社区](http://qiniu.segmentfault.com/)
+- 更详细的文档,见[官方文档站](http://developer.qiniu.com/)
+- 如果发现了bug, 欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果有功能需求,欢迎提交 [issue](https://github.com/qiniu/php-sdk/issues)
+- 如果要提交代码,欢迎提交 pull request
+- 欢迎关注我们的[微信](http://www.qiniu.com/#weixin) [微博](http://weibo.com/qiniutek),及时获取动态信息。
+
+## 代码许可
+
+The MIT License (MIT).详情见 [License文件](https://github.com/qiniu/php-sdk/blob/master/LICENSE).
+
+[packagist]: http://packagist.org
+[install-packagist]: https://packagist.org/packages/qiniu/php-sdk

+ 14 - 0
autoload.php

@@ -0,0 +1,14 @@
+<?php
+
+function classLoader($class)
+{
+    $path = str_replace('\\', DIRECTORY_SEPARATOR, $class);
+    $file = __DIR__ . '/src/' . $path . '.php';
+
+    if (file_exists($file)) {
+        require_once $file;
+    }
+}
+spl_autoload_register('classLoader');
+
+require_once  __DIR__ . '/src/Qiniu/functions.php';

+ 26 - 0
composer.json

@@ -0,0 +1,26 @@
+{
+    "name": "qiniu/php-sdk",
+    "type": "library",
+    "description": "Qiniu Resource (Cloud) Storage SDK for PHP",
+    "keywords": ["qiniu", "storage", "sdk", "cloud"],
+    "homepage": "http://developer.qiniu.com/",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Qiniu",
+            "email": "sdk@qiniu.com",
+            "homepage": "http://www.qiniu.com"
+        }
+    ],
+    "require": {
+        "php": ">=5.3.3"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "~4.0",
+        "squizlabs/php_codesniffer": "~2.3"
+    },
+    "autoload": {
+        "psr-4": {"Qiniu\\": "src/Qiniu"},
+        "files": ["src/Qiniu/functions.php"]
+    }
+}

+ 10 - 0
examples/README.md

@@ -0,0 +1,10 @@
+# examples
+
+这些 examples 旨在帮助你快速了解使用七牛的sdk。这些demo都是可以直接运行的, 但是在运行之前需要填上您自己的参数。
+
+比如:
+
+* `$bucket`  需要填上您想操作的 [bucket名字](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket)。
+* `$accessKey` 和 `$secretKey` 可以在我们的[管理后台](https://portal.qiniu.com/setting/key)找到。
+* 在进行`视频转码`, `压缩文件`等异步操作时 需要使用到的队列名称也可以在我们[管理后台](https://portal.qiniu.com/mps/pipeline)新建。
+

+ 40 - 0
examples/cdn_get_bandwidth.php

@@ -0,0 +1,40 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+//获取流量和带宽数据
+//参考文档:http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+
+$domains = array(
+    "javasdk.qiniudn.com",
+    "phpsdk.qiniudn.com"
+);
+
+$startDate = "2017-08-20";
+$endDate = "2017-08-21";
+
+//5min or hour or day
+$granularity = "day";
+
+//获取带宽数据
+list($bandwidthData, $getBandwidthErr) = $cdnManager->getBandwidthData(
+    $domains,
+    $startDate,
+    $endDate,
+    $granularity
+);
+
+if ($getBandwidthErr != null) {
+    var_dump($getBandwidthErr);
+} else {
+    echo "get bandwidth data success\n";
+    print_r($bandwidthData);
+}

+ 34 - 0
examples/cdn_get_flux.php

@@ -0,0 +1,34 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+//获取流量和带宽数据
+//参考文档:http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+
+$domains = array(
+    "javasdk.qiniudn.com",
+    "phpsdk.qiniudn.com"
+);
+
+$startDate = "2017-08-20";
+$endDate = "2017-08-21";
+
+//5min or hour or day
+$granularity = "day";
+
+//获取流量数据
+list($fluxData, $getFluxErr) = $cdnManager->getFluxData($domains, $startDate, $endDate, $granularity);
+if ($getFluxErr != null) {
+    var_dump($getFluxErr);
+} else {
+    echo "get flux data success\n";
+    print_r($fluxData);
+}

+ 29 - 0
examples/cdn_get_log_list.php

@@ -0,0 +1,29 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+$cdnManager = new CdnManager($auth);
+
+$domains = array(
+    "javasdk.qiniudn.com",
+    "phpsdk.qiniudn.com"
+);
+
+$logDate = '2017-08-20';
+
+//获取日志下载链接
+//参考文档:http://developer.qiniu.com/article/fusion/api/log.html
+
+list($logListData, $getLogErr) = $cdnManager->getCdnLogList($domains, $logDate);
+if ($getLogErr != null) {
+    var_dump($getLogErr);
+} else {
+    echo "get cdn log list success\n";
+    print_r($logListData);
+}

+ 52 - 0
examples/cdn_refresh_urls_dirs.php

@@ -0,0 +1,52 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+$auth = new Qiniu\Auth($accessKey, $secretKey);
+
+//待刷新的文件列表和目录,文件列表最多一次100个,目录最多一次10个
+//参考文档:http://developer.qiniu.com/article/fusion/api/refresh.html
+$urls = array(
+    "http://phpsdk.qiniudn.com/qiniu.jpg",
+    "http://phpsdk.qiniudn.com/qiniu2.jpg",
+);
+
+//刷新目录需要联系七牛技术支持开通账户权限
+$dirs = array(
+    "http://phpsdk.qiniudn.com/test/"
+);
+
+$cdnManager = new CdnManager($auth);
+
+// 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+// 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+list($refreshResult, $refreshErr) = $cdnManager->refreshUrlsAndDirs($urls, $dirs);
+if ($refreshErr != null) {
+    var_dump($refreshErr);
+} else {
+    echo "refresh request sent\n";
+    print_r($refreshResult);
+}
+
+//如果只有刷新链接或者目录的需求,可以分布使用
+
+list($refreshResult, $refreshErr) = $cdnManager->refreshUrls($urls);
+if ($refreshErr != null) {
+    var_dump($refreshErr);
+} else {
+    echo "refresh request sent\n";
+    print_r($refreshResult);
+}
+
+list($refreshResult, $refreshErr) = $cdnManager->refreshDirs($dirs);
+if ($refreshErr != null) {
+    var_dump($refreshErr);
+} else {
+    echo "refresh request sent\n";
+    print_r($refreshResult);
+}

+ 19 - 0
examples/cdn_timestamp_antileech.php

@@ -0,0 +1,19 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Cdn\CdnManager;
+
+//创建时间戳防盗链
+//时间戳防盗链密钥,后台获取
+$encryptKey = 'your_domain_timestamp_antileech_encryptkey';
+
+//带访问协议的域名
+$url1 = 'http://phpsdk.qiniuts.com/24.jpg?avinfo';
+$url2 = 'http://phpsdk.qiniuts.com/24.jpg';
+
+//有效期时间(单位秒)
+$durationInSeconds = 3600;
+
+$signedUrl = CdnManager::createTimestampAntiLeechUrl($url1, $encryptKey, $durationInSeconds);
+print($signedUrl);

+ 74 - 0
examples/image_url_builder.php

@@ -0,0 +1,74 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+// 引入图片处理类
+use Qiniu\Processing\ImageUrlBuilder;
+
+$imageUrlBuilder = new ImageUrlBuilder();
+
+// 要处理图片
+$url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+$url2 = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg?watermark/1/gravity/SouthEast/dx/0/dy/0/image/'
+    . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB';
+$waterImage = 'http://developer.qiniu.com/resource/logo-2.jpg';
+
+/**
+ * 缩略图链接拼接
+ *
+ * @param  string $url 图片链接
+ * @param  int $mode 缩略模式
+ * @param  int $width 宽度
+ * @param  int $height 长度
+ * @param  string $format 输出类型 [可选]
+ * @param  int $quality 图片质量 [可选]
+ * @param  int $interlace 是否支持渐进显示 [可选]
+ * @param  int $ignoreError 忽略结果 [可选]
+ * @return string
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$thumbLink = $imageUrlBuilder->thumbnail($url, 1, 100, 100);
+
+// 函数方式调用 也可拼接多个操作参数 图片+水印
+$thumbLink2 = \Qiniu\thumbnail($url2, 1, 100, 100);
+var_dump($thumbLink, $thumbLink2);
+
+/**
+ * 图片水印
+ *
+ * @param  string $url 图片链接
+ * @param  string $image 水印图片链接
+ * @param  numeric $dissolve 透明度 [可选]
+ * @param  string $gravity 水印位置 [可选]
+ * @param  numeric $dx 横轴边距 [可选]
+ * @param  numeric $dy 纵轴边距 [可选]
+ * @param  numeric $watermarkScale 自适应原图的短边比例 [可选]
+ * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$waterLink = $imageUrlBuilder->waterImg($url, $waterImage);
+// 函数调用方法
+//$waterLink = \Qiniu\waterImg($url, $waterImage);
+var_dump($waterLink);
+
+/**
+ * 文字水印
+ *
+ * @param  string $url 图片链接
+ * @param  string $text 文字
+ * @param  string $font 文字字体
+ * @param  string $fontSize 文字字号
+ * @param  string $fontColor 文字颜色 [可选]
+ * @param  numeric $dissolve 透明度 [可选]
+ * @param  string $gravity 水印位置 [可选]
+ * @param  numeric $dx 横轴边距 [可选]
+ * @param  numeric $dy 纵轴边距 [可选]
+ * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+ * @return string
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+$textLink = $imageUrlBuilder->waterText($url, '你瞅啥', '微软雅黑', 300);
+// 函数调用方法
+// $textLink = \Qiniu\waterText($url, '你瞅啥', '微软雅黑', 300);
+var_dump($textLink);

+ 19 - 0
examples/persistent_fop_init.php

@@ -0,0 +1,19 @@
+<?php
+
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+// 要转码的文件所在的空间。
+$bucket = 'Bucket_Name';
+
+// 转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'pipeline_name';
+
+// 初始化
+$pfop = new PersistentFop($auth, $bucket, $pipeline);

+ 12 - 0
examples/persistent_fop_status.php

@@ -0,0 +1,12 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Processing\PersistentFop;
+
+// 触发持久化处理后返回的 Id
+$persistentId = 'z0.564d5f977823de48a85ece59';
+
+// 通过persistentId查询该 触发持久化处理的状态
+$status = PersistentFop::status($persistentId);
+
+var_dump($status);

+ 43 - 0
examples/pfop_mkzip.php

@@ -0,0 +1,43 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+// 去我们的portal 后台来获取AK, SK
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$key = 'qiniu.png';
+
+$auth = new Auth($accessKey, $secretKey);
+// 异步任务的队列, 去后台新建: https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+$pfop = new PersistentFop($auth, null);
+
+// 进行zip压缩的url
+$url1 = 'http://phpsdk.qiniudn.com/php-logo.png';
+$url2 = 'http://phpsdk.qiniudn.com/1.png';
+
+//压缩后的key
+$zipKey = 'test.zip';
+
+$fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1);
+$fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2);
+$fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey");
+
+$notify_url = null;
+$force = false;
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notify_url, $force);
+
+echo "\n====> pfop mkzip result: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    echo "PersistentFop Id: $id\n";
+
+    $res = "http://api.qiniu.com/status/get/prefop?id=$id";
+    echo "Processing result: $res";
+}

+ 46 - 0
examples/pfop_vframe.php

@@ -0,0 +1,46 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作 
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$force = false;
+
+$config = new \Qiniu\Config();
+$config->useHTTPS = true;
+$pfop = new PersistentFop($auth, $config);
+
+//要进行视频截图操作
+$fops = "vframe/jpg/offset/1/w/480/h/360/rotate/90|saveas/" .
+    \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_480x360.jpg");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 47 - 0
examples/pfop_video_avthumb.php

@@ -0,0 +1,47 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+$force = false;
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$config = new \Qiniu\Config();
+//$config->useHTTPS=true;
+
+$pfop = new PersistentFop($auth, $config);
+
+//要进行转码的转码操作。 http://developer.qiniu.com/docs/v6/api/reference/fop/av/avthumb.html
+$fops = "avthumb/mp4/s/640x360/vb/1.4m|saveas/" . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_640x360.mp4");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 52 - 0
examples/pfop_watermark.php

@@ -0,0 +1,52 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+//对已经上传到七牛的视频发起异步转码操作 
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//要转码的文件所在的空间和文件名。
+$key = 'qiniu.mp4';
+
+//转码是使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+//转码完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$force = false;
+
+$config = new \Qiniu\Config();
+//$config->useHTTPS=true;
+$pfop = new PersistentFop($auth, $config);
+
+//需要添加水印的图片UrlSafeBase64
+//可以参考http://developer.qiniu.com/code/v6/api/dora-api/av/video-watermark.html
+$base64URL = Qiniu\base64_urlSafeEncode('http://devtools.qiniu.com/qiniu.png');
+
+//水印参数
+$fops = "avthumb/mp4/s/640x360/vb/1.4m/image/" . $base64URL . "|saveas/"
+    . \Qiniu\base64_urlSafeEncode($bucket . ":qiniu_wm.mp4");
+
+list($id, $err) = $pfop->execute($bucket, $key, $fops, $pipeline, $notifyUrl, $force);
+echo "\n====> pfop avthumb result: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    echo "PersistentFop Id: $id\n";
+}
+
+//查询转码的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

BIN
examples/php-logo.png


+ 29 - 0
examples/prefop.php

@@ -0,0 +1,29 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+//要持久化处理的文件所在的空间和文件名。
+$bucket = 'Bucket_Name';
+
+//持久化处理使用的队列名称。 https://portal.qiniu.com/mps/pipeline
+$pipeline = 'pipeline_name';
+
+//持久化处理完成后通知到你的业务服务器。
+$notifyUrl = 'http://375dec79.ngrok.com/notify.php';
+$pfop = new PersistentFop($auth, $bucket, $pipeline, $notifyUrl);
+
+$id = "z2.5955c739e3d0041bf80c9baa";
+//查询持久化处理的进度和状态
+list($ret, $err) = $pfop->status($id);
+echo "\n====> pfop avthumb status: \n";
+if ($err != null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 11 - 0
examples/qetag.php

@@ -0,0 +1,11 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+use Qiniu\Etag;
+
+$localFile = "/Users/jemy/Documents/qiniu.mp4";
+list($etag, $err) = Etag::sum($localFile);
+if ($err == null) {
+    echo "Etag: $etag";
+} else {
+    var_dump($err);
+}

+ 28 - 0
examples/rs_batch_change_mime.php

@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keyMimePairs = array(
+    'qiniu.mp4' => 'video/x-mp4',
+    'qiniu.png' => 'image/x-png',
+    'qiniu.jpg' => 'image/x-jpg'
+);
+
+$ops = $bucketManager->buildBatchChangeMime($bucket, $keyMimePairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 34 - 0
examples/rs_batch_change_type.php

@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$keyTypePairs = array();
+//type=0表示普通存储,type=1表示低频存储
+foreach ($keys as $key) {
+    $keyTypePairs[$key] = 1;
+}
+
+$ops = $bucketManager->buildBatchChangeType($bucket, $keyTypePairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 36 - 0
examples/rs_batch_copy.php

@@ -0,0 +1,36 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$keyPairs = array();
+foreach ($keys as $key) {
+    $keyPairs[$key] = $key . "_copy";
+}
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+
+$ops = $bucketManager->buildBatchCopy($srcBucket, $keyPairs, $destBucket, true);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 28 - 0
examples/rs_batch_delete.php

@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$ops = $bucketManager->buildBatchDelete($bucket, $keys);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 34 - 0
examples/rs_batch_delete_after_days.php

@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$keyDayPairs = array();
+//day=0表示永久存储
+foreach ($keys as $key) {
+    $keyDayPairs[$key] = 0;
+}
+
+$ops = $bucketManager->buildBatchDeleteAfterDays($bucket, $keyDayPairs);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 36 - 0
examples/rs_batch_move.php

@@ -0,0 +1,36 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$keyPairs = array();
+foreach ($keys as $key) {
+    $keyPairs[$key . "_copy"] = $key . "_move";
+}
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+
+$ops = $bucketManager->buildBatchMove($srcBucket, $keyPairs, $destBucket, true);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 28 - 0
examples/rs_batch_stat.php

@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+//每次最多不能超过1000个
+$keys = array(
+    'qiniu.mp4',
+    'qiniu.png',
+    'qiniu.jpg'
+);
+
+$ops = $bucketManager->buildBatchStat($bucket, $keys);
+list($ret, $err) = $bucketManager->batch($ops);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($ret);
+}

+ 19 - 0
examples/rs_bucket_domains.php

@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($domains, $err) = $bucketManager->domains($bucket);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($domains);
+}

+ 19 - 0
examples/rs_buckets.php

@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($buckets, $err) = $bucketManager->buckets(true);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($buckets);
+}

+ 20 - 0
examples/rs_change_mime.php

@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = 'qiniu.mp4';
+$newMime = 'video/x-mp4';
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$err = $bucketManager->changeMime($bucket, $key, $newMime);
+if ($err) {
+    print_r($err);
+}

+ 20 - 0
examples/rs_change_type.php

@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$fileType = 1;//0 表示普通存储,1表示低频存储
+
+$err = $bucketManager->changeType($bucket, $key, $fileType);
+if ($err) {
+    print_r($err);
+}

+ 22 - 0
examples/rs_copy.php

@@ -0,0 +1,22 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+$srcKey = $key;
+$destKey = $key . "_copy";
+$err = $bucketManager->copy($srcBucket, $srcKey, $destBucket, $destKey, true);
+if ($err) {
+    print_r($err);
+}

+ 17 - 0
examples/rs_delete.php

@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4_copy";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+$err = $bucketManager->delete($bucket, $key);
+if ($err) {
+    print_r($err);
+}

+ 20 - 0
examples/rs_delete_after_days.php

@@ -0,0 +1,20 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = 'qiniu.mp4';
+$days = 10;
+
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$err = $bucketManager->deleteAfterDays($bucket, $key, $days);
+if ($err) {
+    print_r($err);
+}

+ 17 - 0
examples/rs_download_urls.php

@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+
+// 构建Auth对象
+$auth = new Auth($accessKey, $secretKey);
+
+// 私有空间中的外链 http://<domain>/<file_key>
+$baseUrl = 'http://if-pri.qiniudn.com/qiniu.png?imageView2/1/h/500';
+// 对链接进行签名
+$signedUrl = $auth->privateDownloadUrl($baseUrl);
+
+echo $signedUrl;

+ 34 - 0
examples/rs_fetch.php

@@ -0,0 +1,34 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+$url = 'http://devtools.qiniu.com/qiniu.png';
+$key = time() . '.png';
+
+// 指定抓取的文件保存名称
+list($ret, $err) = $bucketManager->fetch($url, $bucket, $key);
+echo "=====> fetch $url to bucket: $bucket  key: $key\n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    print_r($ret);
+}
+
+// 不指定key时,以文件内容的hash作为文件名
+$key = null;
+list($ret, $err) = $bucketManager->fetch($url, $bucket, $key);
+echo "=====> fetch $url to bucket: $bucket  key: $(etag)\n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    print_r($ret);
+}

+ 22 - 0
examples/rs_move.php

@@ -0,0 +1,22 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+
+$srcBucket = $bucket;
+$destBucket = $bucket;
+$srcKey = $key . "_copy";
+$destKey = $key . "_move";
+$err = $bucketManager->move($srcBucket, $srcKey, $destBucket, $destKey, true);
+if ($err) {
+    print_r($err);
+}

+ 17 - 0
examples/rs_prefetch.php

@@ -0,0 +1,17 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+$err = $bucketManager->prefetch($bucket, $key);
+if ($err) {
+    print_r($err);
+}

+ 19 - 0
examples/rs_stat.php

@@ -0,0 +1,19 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$key = "qiniu.mp4";
+$auth = new Auth($accessKey, $secretKey);
+$config = new \Qiniu\Config();
+$bucketManager = new \Qiniu\Storage\BucketManager($auth, $config);
+list($fileInfo, $err) = $bucketManager->stat($bucket, $key);
+if ($err) {
+    print_r($err);
+} else {
+    print_r($fileInfo);
+}

+ 46 - 0
examples/rsf_list_bucket.php

@@ -0,0 +1,46 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+// http://developer.qiniu.com/docs/v6/api/reference/rs/list.html#list-description
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+
+// 要列取文件的公共前缀
+$prefix = '';
+
+// 上次列举返回的位置标记,作为本次列举的起点信息。
+$marker = '';
+
+// 本次列举的条目数
+$limit = 200;
+
+$delimiter = '/';
+
+// 列举文件
+do {
+    list($ret, $err) = $bucketManager->listFiles($bucket, $prefix, $marker, $limit, $delimiter);
+    if ($err !== null) {
+        echo "\n====> list file err: \n";
+        var_dump($err);
+    } else {
+        $marker = null;
+        if (array_key_exists('marker', $ret)) {
+            $marker = $ret['marker'];
+        }
+        echo "Marker: $marker\n";
+        echo "\nList Items====>\n";
+        //var_dump($ret['items']);
+        print('items count:' . count($ret['items']) . "\n");
+        if (array_key_exists('commonPrefixes', $ret)) {
+            print_r($ret['commonPrefixes']);
+        }
+    }
+} while (!empty($marker));

+ 38 - 0
examples/rsf_list_files.php

@@ -0,0 +1,38 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\BucketManager;
+
+// http://developer.qiniu.com/docs/v6/api/reference/rs/list.html#list-description
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+$bucketManager = new BucketManager($auth);
+
+
+// 要列取文件的公共前缀
+$prefix = '';
+
+// 上次列举返回的位置标记,作为本次列举的起点信息。
+$marker = '';
+
+// 本次列举的条目数
+$limit = 100;
+
+$delimiter = '/';
+
+// 列举文件
+list($ret, $err) = $bucketManager->listFiles($bucket, $prefix, $marker, $limit, $delimiter);
+if ($err !== null) {
+    echo "\n====> list file err: \n";
+    var_dump($err);
+} else {
+    if (array_key_exists('marker', $ret)) {
+        echo "Marker:" . $ret["marker"] . "\n";
+    }
+    echo "\nList Iterms====>\n";
+    //var_dump($ret['items']);
+}

+ 28 - 0
examples/saveas.php

@@ -0,0 +1,28 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Processing\PersistentFop;
+
+// 后台来获取AK, SK
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+
+//生成EncodedEntryURI的值
+$entry = '<bucket>:<Key>';//<Key>为生成缩略图的文件名
+//生成的值
+$encodedEntryURI = \Qiniu\base64_urlSafeEncode($entry);
+
+//使用SecretKey对新的下载URL进行HMAC1-SHA1签名
+$newurl = "78re52.com1.z0.glb.clouddn.com/resource/Ship.jpg?imageView2/2/w/200/h/200|saveas/" . $encodedEntryURI;
+
+$sign = hash_hmac("sha1", $newurl, $secretKey, true);
+
+//对签名进行URL安全的Base64编码
+$encodedSign = \Qiniu\base64_urlSafeEncode($sign);
+//最终得到的完整下载URL
+$finalURL = "http://" . $newurl . "/sign/" . $accessKey . ":" . $encodedSign;
+
+$callbackBody = file_get_contents("$finalURL");
+
+echo $callbackBody;

+ 31 - 0
examples/upload_and_callback.php

@@ -0,0 +1,31 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+
+// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器.
+// 可参考文档: http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$policy = array(
+    'callbackUrl' => 'http://your.domain.com/upload_verify_callback.php',
+    'callbackBody' => 'filename=$(fname)&filesize=$(fsize)'
+);
+$uptoken = $auth->uploadToken($bucket, null, 3600, $policy);
+
+//上传文件的本地路径
+$filePath = './php-logo.png';
+
+$uploadMgr = new UploadManager();
+
+list($ret, $err) = $uploadMgr->putFile($uptoken, null, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 38 - 0
examples/upload_and_pfop.php

@@ -0,0 +1,38 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$auth = new Auth($accessKey, $secretKey);
+
+
+// 在七牛保存的文件名
+$key = 'php-logo.png';
+$uploadMgr = new UploadManager();
+
+$pfop = "imageMogr2/rotate/90|saveas/" . \Qiniu\base64_urlSafeEncode($bucket . ":php-logo-rotate.png");
+
+//转码完成后通知到你的业务服务器。(公网可以访问,并相应200 OK)
+$notifyUrl = 'http://notify.fake.com';
+
+//独立的转码队列:https://portal.qiniu.com/mps/pipeline
+$pipeline = 'sdktest';
+
+$policy = array(
+    'persistentOps' => $pfop,
+    'persistentNotifyUrl' => $notifyUrl,
+    'persistentPipeline' => $pipeline
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 18 - 0
examples/upload_mgr_init.php

@@ -0,0 +1,18 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = 'Access_Key';
+$secretKey = 'Secret_Key';
+$auth = new Auth($accessKey, $secretKey);
+
+// 空间名  http://developer.qiniu.com/docs/v6/api/overview/concepts.html#bucket
+$bucket = 'Bucket_Name';
+
+// 生成上传Token
+$token = $auth->uploadToken($bucket);
+
+// 构建 UploadManager 对象
+$uploadMgr = new UploadManager();

+ 85 - 0
examples/upload_multi_demos.php

@@ -0,0 +1,85 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+use Qiniu\Storage\UploadManager;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+$pipeline = 'sdktest';
+
+$auth = new Auth($accessKey, $secretKey);
+$token = $auth->uploadToken($bucket);
+$uploadMgr = new UploadManager();
+
+//----------------------------------------upload demo1 ----------------------------------------
+// 上传字符串到七牛
+list($ret, $err) = $uploadMgr->put($token, null, 'content string');
+echo "\n====> put result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}
+
+
+//----------------------------------------upload demo2 ----------------------------------------
+// 上传文件到七牛
+$filePath = './php-logo.png';
+$key = 'php-logo.png';
+list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}
+
+
+//----------------------------------------upload demo3 ----------------------------------------
+// 上传文件到七牛后, 七牛将文件名和文件大小回调给业务服务器.
+// 可参考文档: http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$policy = array(
+    'callbackUrl' => 'http://172.30.251.210/upload_verify_callback.php',
+    'callbackBody' => 'filename=$(fname)&filesize=$(fsize)'
+//  'callbackBodyType' => 'application/json',                       
+//  'callbackBody' => '{"filename":$(fname), "filesize": $(fsize)}'  //设置application/json格式回调
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+
+
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}
+
+
+//----------------------------------------upload demo4 ----------------------------------------
+//上传视频,上传完成后进行m3u8的转码, 并给视频打水印
+$wmImg = Qiniu\base64_urlSafeEncode('http://devtools.qiniudn.com/qiniu.png');
+$pfop = "avthumb/m3u8/wmImage/$wmImg";
+
+//转码完成后回调到业务服务器。(公网可以访问,并相应200 OK)
+$notifyUrl = 'http://notify.fake.com';
+
+//独立的转码队列:https://portal.qiniu.com/mps/pipeline
+
+
+$policy = array(
+    'persistentOps' => $pfop,
+    'persistentNotifyUrl' => $notifyUrl,
+    'persistentPipeline' => $pipeline
+);
+$token = $auth->uploadToken($bucket, null, 3600, $policy);
+print($token);
+list($ret, $err) = $uploadMgr->putFile($token, null, $key);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 37 - 0
examples/upload_simple_file.php

@@ -0,0 +1,37 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+// 引入鉴权类
+use Qiniu\Auth;
+
+// 引入上传类
+use Qiniu\Storage\UploadManager;
+
+// 需要填写你的 Access Key 和 Secret Key
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+// 构建鉴权对象
+$auth = new Auth($accessKey, $secretKey);
+
+// 生成上传 Token
+$token = $auth->uploadToken($bucket);
+
+// 要上传文件的本地路径
+$filePath = './php-logo.png';
+
+// 上传到七牛后保存的文件名
+$key = 'my-php-logo.png';
+
+// 初始化 UploadManager 对象并进行文件的上传。
+$uploadMgr = new UploadManager();
+
+// 调用 UploadManager 的 putFile 方法进行文件的上传。
+list($ret, $err) = $uploadMgr->putFile($token, $key, $filePath);
+echo "\n====> putFile result: \n";
+if ($err !== null) {
+    var_dump($err);
+} else {
+    var_dump($ret);
+}

+ 68 - 0
examples/upload_tokens.php

@@ -0,0 +1,68 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use \Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+// 初始化Auth状态
+$auth = new Auth($accessKey, $secretKey);
+
+// 简单上传凭证
+$expires = 3600;
+
+$policy = null;
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+// 自定义凭证有效期(示例2小时)
+$expires = 7200;
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+// 覆盖上传凭证
+$expires = 3600;
+$keyToOverwrite = 'qiniu.mp4';
+$upToken = $auth->uploadToken($bucket, $keyToOverwrite, $expires, $policy, true);
+print($upToken . "\n");
+
+//自定义上传回复(非callback模式)凭证
+$returnBody = '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}';
+$policy = array(
+    'returnBody' => $returnBody
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+//带回调业务服务器的凭证(application/json)
+$policy = array(
+    'callbackUrl' => 'http://api.example.com/qiniu/upload/callback',
+    'callbackBody' => '{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}',
+    'callbackBodyType' => 'application/json'
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+
+//带回调业务服务器的凭证(application/x-www-form-urlencoded)
+$policy = array(
+    'callbackUrl' => 'http://api.example.com/qiniu/upload/callback',
+    'callbackBody' => 'key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)'
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");
+
+//带数据处理的凭证
+$saveMp4Entry = \Qiniu\base64_urlSafeEncode($bucket . ":avthumb_test_target.mp4");
+$saveJpgEntry = \Qiniu\base64_urlSafeEncode($bucket . ":vframe_test_target.jpg");
+$avthumbMp4Fop = "avthumb/mp4|saveas/" . $saveMp4Entry;
+$vframeJpgFop = "vframe/jpg/offset/1|saveas/" . $saveJpgEntry;
+$policy = array(
+    'persistentOps' => $avthumbMp4Fop . ";" . $vframeJpgFop,
+    'persistentPipeline' => "video-pipe",
+    'persistentNotifyUrl' => "http://api.example.com/qiniu/pfop/notify",
+);
+$upToken = $auth->uploadToken($bucket, null, $expires, $policy, true);
+print($upToken . "\n");

+ 32 - 0
examples/upload_verify_callback.php

@@ -0,0 +1,32 @@
+<?php
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$bucket = getenv('QINIU_TEST_BUCKET');
+
+$auth = new Auth($accessKey, $secretKey);
+
+//获取回调的body信息
+$callbackBody = file_get_contents('php://input');
+
+//回调的contentType
+$contentType = 'application/x-www-form-urlencoded';
+
+//回调的签名信息,可以验证该回调是否来自七牛
+$authorization = $_SERVER['HTTP_AUTHORIZATION'];
+
+//七牛回调的url,具体可以参考:http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+$url = 'http://172.30.251.210/upload_verify_callback.php';
+
+$isQiniuCallback = $auth->verifyCallback($contentType, $authorization, $url, $callbackBody);
+
+if ($isQiniuCallback) {
+    $resp = array('ret' => 'success');
+} else {
+    $resp = array('ret' => 'failed');
+}
+
+echo json_encode($resp);

+ 19 - 0
phpunit.xml.dist

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit backupGlobals="false"
+         backupStaticAttributes="false"
+         bootstrap="./tests/bootstrap.php"
+         colors="true"
+         convertErrorsToExceptions="true"
+         convertNoticesToExceptions="true"
+         convertWarningsToExceptions="true"
+         processIsolation="false"
+         stopOnFailure="false"
+         syntaxCheck="true"
+>
+    <testsuites>
+        <testsuite name="Qiniu SDK Tests">
+            <directory>tests</directory>
+        </testsuite>
+    </testsuites>
+
+</phpunit>

+ 187 - 0
src/Qiniu/Auth.php

@@ -0,0 +1,187 @@
+<?php
+namespace Qiniu;
+
+use Qiniu\Zone;
+
+final class Auth
+{
+    private $accessKey;
+    private $secretKey;
+
+    public function __construct($accessKey, $secretKey)
+    {
+        $this->accessKey = $accessKey;
+        $this->secretKey = $secretKey;
+    }
+
+    public function getAccessKey()
+    {
+        return $this->accessKey;
+    }
+
+    public function sign($data)
+    {
+        $hmac = hash_hmac('sha1', $data, $this->secretKey, true);
+        return $this->accessKey . ':' . \Qiniu\base64_urlSafeEncode($hmac);
+    }
+
+    public function signWithData($data)
+    {
+        $encodedData = \Qiniu\base64_urlSafeEncode($data);
+        return $this->sign($encodedData) . ':' . $encodedData;
+    }
+
+    public function signRequest($urlString, $body, $contentType = null)
+    {
+        $url = parse_url($urlString);
+        $data = '';
+        if (array_key_exists('path', $url)) {
+            $data = $url['path'];
+        }
+        if (array_key_exists('query', $url)) {
+            $data .= '?' . $url['query'];
+        }
+        $data .= "\n";
+
+        if ($body !== null && $contentType === 'application/x-www-form-urlencoded') {
+            $data .= $body;
+        }
+        return $this->sign($data);
+    }
+
+    public function verifyCallback($contentType, $originAuthorization, $url, $body)
+    {
+        $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
+        return $originAuthorization === $authorization;
+    }
+
+    public function privateDownloadUrl($baseUrl, $expires = 3600)
+    {
+        $deadline = time() + $expires;
+
+        $pos = strpos($baseUrl, '?');
+        if ($pos !== false) {
+            $baseUrl .= '&e=';
+        } else {
+            $baseUrl .= '?e=';
+        }
+        $baseUrl .= $deadline;
+
+        $token = $this->sign($baseUrl);
+        return "$baseUrl&token=$token";
+    }
+
+    public function uploadToken($bucket, $key = null, $expires = 3600, $policy = null, $strictPolicy = true)
+    {
+        $deadline = time() + $expires;
+        $scope = $bucket;
+        if ($key !== null) {
+            $scope .= ':' . $key;
+        }
+
+        $args = self::copyPolicy($args, $policy, $strictPolicy);
+        $args['scope'] = $scope;
+        $args['deadline'] = $deadline;
+
+        $b = json_encode($args);
+        return $this->signWithData($b);
+    }
+
+    /**
+     *上传策略,参数规格详见
+     *http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html
+     */
+    private static $policyFields = array(
+        'callbackUrl',
+        'callbackBody',
+        'callbackHost',
+        'callbackBodyType',
+        'callbackFetchKey',
+
+        'returnUrl',
+        'returnBody',
+
+        'endUser',
+        'saveKey',
+        'insertOnly',
+
+        'detectMime',
+        'mimeLimit',
+        'fsizeMin',
+        'fsizeLimit',
+
+        'persistentOps',
+        'persistentNotifyUrl',
+        'persistentPipeline',
+
+        'deleteAfterDays',
+        'fileType',
+        'isPrefixalScope',
+    );
+
+    private static function copyPolicy(&$policy, $originPolicy, $strictPolicy)
+    {
+        if ($originPolicy === null) {
+            return array();
+        }
+        foreach ($originPolicy as $key => $value) {
+            if (!$strictPolicy || in_array((string)$key, self::$policyFields, true)) {
+                $policy[$key] = $value;
+            }
+        }
+        return $policy;
+    }
+
+    public function authorization($url, $body = null, $contentType = null)
+    {
+        $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType);
+        return array('Authorization' => $authorization);
+    }
+
+    public function authorizationV2($url, $method, $body = null, $contentType = null)
+    {
+        $urlItems = parse_url($url);
+        $host = $urlItems['host'];
+
+        if (isset($urlItems['port'])) {
+            $port = $urlItems['port'];
+        } else {
+            $port = '';
+        }
+
+        $path = $urlItems['path'];
+        if (isset($urlItems['query'])) {
+            $query = $urlItems['query'];
+        } else {
+            $query = '';
+        }
+
+        //write request uri
+        $toSignStr = $method . ' ' . $path;
+        if (!empty($query)) {
+            $toSignStr .= '?' . $query;
+        }
+
+        //write host and port
+        $toSignStr .= "\nHost: " . $host;
+        if (!empty($port)) {
+            $toSignStr .= ":" . $port;
+        }
+
+        //write content type
+        if (!empty($contentType)) {
+            $toSignStr .= "\nContent-Type: " . $contentType;
+        }
+
+        $toSignStr .= "\n\n";
+
+        //write body
+        if (!empty($body)) {
+            $toSignStr .= $body;
+        }
+
+        $sign = $this->sign($toSignStr);
+        $auth = 'Qiniu ' . $sign;
+        return array('Authorization' => $auth);
+    }
+}

+ 190 - 0
src/Qiniu/Cdn/CdnManager.php

@@ -0,0 +1,190 @@
+<?php
+
+namespace Qiniu\Cdn;
+
+use Qiniu\Auth;
+use Qiniu\Http\Error;
+use Qiniu\Http\Client;
+
+final class CdnManager
+{
+
+    private $auth;
+    private $server;
+
+    public function __construct(Auth $auth)
+    {
+        $this->auth = $auth;
+        $this->server = 'http://fusion.qiniuapi.com';
+    }
+
+    /**
+     * @param array $urls 待刷新的文件链接数组
+     * @return array
+     */
+    public function refreshUrls(array $urls)
+    {
+        return $this->refreshUrlsAndDirs($urls, array());
+    }
+
+    /**
+     * @param array $dirs 待刷新的文件链接数组
+     * @return array
+     * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+     * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+     */
+    public function refreshDirs(array $dirs)
+    {
+        return $this->refreshUrlsAndDirs(array(), $dirs);
+    }
+
+    /**
+     * @param array $urls 待刷新的文件链接数组
+     * @param array $dirs 待刷新的目录链接数组
+     *
+     * @return array 刷新的请求回复和错误,参考 examples/cdn_manager.php 代码
+     * @link http://developer.qiniu.com/article/fusion/api/refresh.html
+     *
+     * 目前客户默认没有目录刷新权限,刷新会有400038报错,参考:https://developer.qiniu.com/fusion/api/1229/cache-refresh
+     * 需要刷新目录请工单联系技术支持 https://support.qiniu.com/tickets/category
+     */
+    public function refreshUrlsAndDirs(array $urls, array  $dirs)
+    {
+        $req = array();
+        if (!empty($urls)) {
+            $req['urls'] = $urls;
+        }
+        if (!empty($dirs)) {
+            $req['dirs'] = $dirs;
+        }
+
+        $url = $this->server . '/v2/tune/refresh';
+        $body = json_encode($req);
+        return $this->post($url, $body);
+    }
+
+    /**
+     * @param array $urls 待预取的文件链接数组
+     *
+     * @return array 预取的请求回复和错误,参考 examples/cdn_manager.php 代码
+     *
+     * @link http://developer.qiniu.com/article/fusion/api/refresh.html
+     */
+    public function prefetchUrls(array $urls)
+    {
+        $req = array(
+            'urls' => $urls,
+        );
+
+        $url = $this->server . '/v2/tune/prefetch';
+        $body = json_encode($req);
+        return $this->post($url, $body);
+    }
+
+    /**
+     * @param array $domains 待获取带宽数据的域名数组
+     * @param string $startDate 开始的日期,格式类似 2017-01-01
+     * @param string $endDate 结束的日期,格式类似 2017-01-01
+     * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
+     *
+     * @return array 带宽数据和错误信息,参考 examples/cdn_manager.php 代码
+     *
+     * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+     */
+    public function getBandwidthData(array $domains, $startDate, $endDate, $granularity)
+    {
+        $req = array();
+        $req['domains'] = implode(';', $domains);
+        $req['startDate'] = $startDate;
+        $req['endDate'] = $endDate;
+        $req['granularity'] = $granularity;
+
+        $url = $this->server . '/v2/tune/bandwidth';
+        $body = json_encode($req);
+        return $this->post($url, $body);
+    }
+
+    /**
+     * @param array $domains 待获取流量数据的域名数组
+     * @param string $startDate 开始的日期,格式类似 2017-01-01
+     * @param string $endDate 结束的日期,格式类似 2017-01-01
+     * @param string $granularity 获取数据的时间间隔,可以是 5min, hour 或者 day
+     *
+     * @return array 流量数据和错误信息,参考 examples/cdn_manager.php 代码
+     *
+     * @link http://developer.qiniu.com/article/fusion/api/traffic-bandwidth.html
+     */
+    public function getFluxData(array $domains, $startDate, $endDate, $granularity)
+    {
+        $req = array();
+        $req['domains'] = implode(';', $domains);
+        $req['startDate'] = $startDate;
+        $req['endDate'] = $endDate;
+        $req['granularity'] = $granularity;
+
+        $url = $this->server . '/v2/tune/flux';
+        $body = json_encode($req);
+        return $this->post($url, $body);
+    }
+
+    /**
+     * @param array $domains 待获取日志下载链接的域名数组
+     * @param string $logDate 获取指定日期的日志下载链接,格式类似 2017-01-01
+     *
+     * @return array 日志下载链接数据和错误信息,参考 examples/cdn_manager.php 代码
+     *
+     * @link http://developer.qiniu.com/article/fusion/api/log.html
+     */
+    public function getCdnLogList(array $domains, $logDate)
+    {
+        $req = array();
+        $req['domains'] = implode(';', $domains);
+        $req['day'] = $logDate;
+
+        $url = $this->server . '/v2/tune/log/list';
+        $body = json_encode($req);
+        return $this->post($url, $body);
+    }
+
+    private function post($url, $body)
+    {
+        $headers = $this->auth->authorization($url, $body, 'application/json');
+        $headers['Content-Type'] = 'application/json';
+        $ret = Client::post($url, $body, $headers);
+        if (!$ret->ok()) {
+            return array(null, new Error($url, $ret));
+        }
+        $r = ($ret->body === null) ? array() : $ret->json();
+        return array($r, null);
+    }
+
+    /**
+     * 构建时间戳防盗链鉴权的访问外链
+     *
+     * @param string $rawUrl 需要签名的资源url
+     * @param string $encryptKey 时间戳防盗链密钥
+     * @param string $durationInSeconds 链接的有效期(以秒为单位)
+     *
+     * @return string 带鉴权信息的资源外链,参考 examples/cdn_timestamp_antileech.php 代码
+     */
+    public static function createTimestampAntiLeechUrl($rawUrl, $encryptKey, $durationInSeconds)
+    {
+
+        $parsedUrl = parse_url($rawUrl);
+
+        $deadline = time() + $durationInSeconds;
+        $expireHex = dechex($deadline);
+        $path = isset($parsedUrl['path']) ? $parsedUrl['path'] : '';
+
+        $strToSign = $encryptKey . $path . $expireHex;
+        $signStr = md5($strToSign);
+
+        if (isset($parsedUrl['query'])) {
+            $signedUrl = $rawUrl . '&sign=' . $signStr . '&t=' . $expireHex;
+        } else {
+            $signedUrl = $rawUrl . '?sign=' . $signStr . '&t=' . $expireHex;
+        }
+
+        return $signedUrl;
+    }
+}

+ 134 - 0
src/Qiniu/Config.php

@@ -0,0 +1,134 @@
+<?php
+namespace Qiniu;
+
+final class Config
+{
+    const SDK_VER = '7.2.3';
+
+    const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改
+
+    const RSF_HOST = 'rsf.qiniu.com';
+    const API_HOST = 'api.qiniu.com';
+    const RS_HOST = 'rs.qiniu.com';      //RS Host
+    const UC_HOST = 'https://api.qiniu.com';              //UC Host
+
+    // Zone 空间对应的机房
+    public $zone;
+    //BOOL 是否使用https域名
+    public $useHTTPS;
+    //BOOL 是否使用CDN加速上传域名
+    public $useCdnDomains;
+    // Zone Cache
+    private $zoneCache;
+
+    // 构造函数
+    public function __construct(Zone $z = null)
+    {
+        $this->zone = $z;
+        $this->useHTTPS = false;
+        $this->useCdnDomains = false;
+        $this->zoneCache = array();
+    }
+
+    public function getUpHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        $host = $zone->srcUpHosts[0];
+        if ($this->useCdnDomains === true) {
+            $host = $zone->cdnUpHosts[0];
+        }
+
+        return $scheme . $host;
+    }
+
+    public function getUpBackupHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        $host = $zone->cdnUpHosts[0];
+        if ($this->useCdnDomains === true) {
+            $host = $zone->srcUpHosts[0];
+        }
+
+        return $scheme . $host;
+    }
+
+    public function getRsHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        return $scheme . $zone->rsHost;
+    }
+
+    public function getRsfHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        return $scheme . $zone->rsfHost;
+    }
+
+    public function getIovipHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        return $scheme . $zone->iovipHost;
+    }
+
+    public function getApiHost($accessKey, $bucket)
+    {
+        $zone = $this->getZone($accessKey, $bucket);
+
+        if ($this->useHTTPS === true) {
+            $scheme = "https://";
+        } else {
+            $scheme = "http://";
+        }
+
+        return $scheme . $zone->apiHost;
+    }
+
+    private function getZone($accessKey, $bucket)
+    {
+        $cacheId = "$accessKey:$bucket";
+
+        if (isset($this->zoneCache[$cacheId])) {
+            $zone = $this->zoneCache[$cacheId];
+        } elseif (isset($this->zone)) {
+            $zone = $this->zone;
+            $this->zoneCache[$cacheId] = $zone;
+        } else {
+            $zone = Zone::queryZone($accessKey, $bucket);
+            $this->zoneCache[$cacheId] = $zone;
+        }
+        return $zone;
+    }
+}

+ 76 - 0
src/Qiniu/Etag.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace Qiniu;
+
+use Qiniu\Config;
+
+final class Etag
+{
+    private static function packArray($v, $a)
+    {
+        return call_user_func_array('pack', array_merge(array($v), (array)$a));
+    }
+
+    private static function blockCount($fsize)
+    {
+        return intval(($fsize + (Config::BLOCK_SIZE - 1)) / Config::BLOCK_SIZE);
+    }
+
+    private static function calcSha1($data)
+    {
+        $sha1Str = sha1($data, true);
+        $err = error_get_last();
+        if ($err !== null) {
+            return array(null, $err);
+        }
+        $byteArray = unpack('C*', $sha1Str);
+        return array($byteArray, null);
+    }
+
+
+    public static function sum($filename)
+    {
+        $fhandler = fopen($filename, 'r');
+        $err = error_get_last();
+        if ($err !== null) {
+            return array(null, $err);
+        }
+
+        $fstat = fstat($fhandler);
+        $fsize = $fstat['size'];
+        if ((int)$fsize === 0) {
+            fclose($fhandler);
+            return array('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', null);
+        }
+        $blockCnt = self::blockCount($fsize);
+        $sha1Buf = array();
+
+        if ($blockCnt <= 1) {
+            array_push($sha1Buf, 0x16);
+            $fdata = fread($fhandler, Config::BLOCK_SIZE);
+            if ($err !== null) {
+                fclose($fhandler);
+                return array(null, $err);
+            }
+            list($sha1Code,) = self::calcSha1($fdata);
+            $sha1Buf = array_merge($sha1Buf, $sha1Code);
+        } else {
+            array_push($sha1Buf, 0x96);
+            $sha1BlockBuf = array();
+            for ($i = 0; $i < $blockCnt; $i++) {
+                $fdata = fread($fhandler, Config::BLOCK_SIZE);
+                list($sha1Code, $err) = self::calcSha1($fdata);
+                if ($err !== null) {
+                    fclose($fhandler);
+                    return array(null, $err);
+                }
+                $sha1BlockBuf = array_merge($sha1BlockBuf, $sha1Code);
+            }
+            $tmpData = self::packArray('C*', $sha1BlockBuf);
+            list($sha1Final,) = self::calcSha1($tmpData);
+            $sha1Buf = array_merge($sha1Buf, $sha1Final);
+        }
+        $etag = \Qiniu\base64_urlSafeEncode(self::packArray('C*', $sha1Buf));
+        return array($etag, null);
+    }
+}

+ 150 - 0
src/Qiniu/Http/Client.php

@@ -0,0 +1,150 @@
+<?php
+namespace Qiniu\Http;
+
+use Qiniu\Config;
+use Qiniu\Http\Request;
+use Qiniu\Http\Response;
+
+final class Client
+{
+    public static function get($url, array $headers = array())
+    {
+        $request = new Request('GET', $url, $headers);
+        return self::sendRequest($request);
+    }
+
+    public static function post($url, $body, array $headers = array())
+    {
+        $request = new Request('POST', $url, $headers, $body);
+        return self::sendRequest($request);
+    }
+
+    public static function multipartPost(
+        $url,
+        $fields,
+        $name,
+        $fileName,
+        $fileBody,
+        $mimeType = null,
+        array $headers = array()
+    ) {
+        $data = array();
+        $mimeBoundary = md5(microtime());
+
+        foreach ($fields as $key => $val) {
+            array_push($data, '--' . $mimeBoundary);
+            array_push($data, "Content-Disposition: form-data; name=\"$key\"");
+            array_push($data, '');
+            array_push($data, $val);
+        }
+
+        array_push($data, '--' . $mimeBoundary);
+        $finalMimeType = empty($mimeType) ? 'application/octet-stream' : $mimeType;
+        $finalFileName = self::escapeQuotes($fileName);
+        array_push($data, "Content-Disposition: form-data; name=\"$name\"; filename=\"$finalFileName\"");
+        array_push($data, "Content-Type: $finalMimeType");
+        array_push($data, '');
+        array_push($data, $fileBody);
+
+        array_push($data, '--' . $mimeBoundary . '--');
+        array_push($data, '');
+
+        $body = implode("\r\n", $data);
+        $contentType = 'multipart/form-data; boundary=' . $mimeBoundary;
+        $headers['Content-Type'] = $contentType;
+        $request = new Request('POST', $url, $headers, $body);
+        return self::sendRequest($request);
+    }
+
+    private static function userAgent()
+    {
+        $sdkInfo = "QiniuPHP/" . Config::SDK_VER;
+
+        $systemInfo = php_uname("s");
+        $machineInfo = php_uname("m");
+
+        $envInfo = "($systemInfo/$machineInfo)";
+
+        $phpVer = phpversion();
+
+        $ua = "$sdkInfo $envInfo PHP/$phpVer";
+        return $ua;
+    }
+
+    public static function sendRequest($request)
+    {
+        $t1 = microtime(true);
+        $ch = curl_init();
+        $options = array(
+            CURLOPT_USERAGENT => self::userAgent(),
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_SSL_VERIFYPEER => false,
+            CURLOPT_SSL_VERIFYHOST => false,
+            CURLOPT_HEADER => true,
+            CURLOPT_NOBODY => false,
+            CURLOPT_CUSTOMREQUEST => $request->method,
+            CURLOPT_URL => $request->url,
+        );
+
+        // Handle open_basedir & safe mode
+        if (!ini_get('safe_mode') && !ini_get('open_basedir')) {
+            $options[CURLOPT_FOLLOWLOCATION] = true;
+        }
+
+        if (!empty($request->headers)) {
+            $headers = array();
+            foreach ($request->headers as $key => $val) {
+                array_push($headers, "$key: $val");
+            }
+            $options[CURLOPT_HTTPHEADER] = $headers;
+        }
+        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));
+
+        if (!empty($request->body)) {
+            $options[CURLOPT_POSTFIELDS] = $request->body;
+        }
+        curl_setopt_array($ch, $options);
+        $result = curl_exec($ch);
+        $t2 = microtime(true);
+        $duration = round($t2 - $t1, 3);
+        $ret = curl_errno($ch);
+        if ($ret !== 0) {
+            $r = new Response(-1, $duration, array(), null, curl_error($ch));
+            curl_close($ch);
+            return $r;
+        }
+        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+        $headers = self::parseHeaders(substr($result, 0, $header_size));
+        $body = substr($result, $header_size);
+        curl_close($ch);
+        return new Response($code, $duration, $headers, $body, null);
+    }
+
+    private static function parseHeaders($raw)
+    {
+        $headers = array();
+        $headerLines = explode("\r\n", $raw);
+        foreach ($headerLines as $line) {
+            $headerLine = trim($line);
+            $kv = explode(':', $headerLine);
+            if (count($kv) > 1) {
+                $kv[0] =self::ucwordsHyphen($kv[0]);
+                $headers[$kv[0]] = trim($kv[1]);
+            }
+        }
+        return $headers;
+    }
+
+    private static function escapeQuotes($str)
+    {
+        $find = array("\\", "\"");
+        $replace = array("\\\\", "\\\"");
+        return str_replace($find, $replace, $str);
+    }
+    
+    private static function ucwordsHyphen($str)
+    {
+        return str_replace('- ', '-', ucwords(str_replace('-', '- ', $str)));
+    }
+}

+ 35 - 0
src/Qiniu/Http/Error.php

@@ -0,0 +1,35 @@
+<?php
+namespace Qiniu\Http;
+
+/**
+ * 七牛业务请求逻辑错误封装类,主要用来解析API请求返回如下的内容:
+ * <pre>
+ *     {"error" : "detailed error message"}
+ * </pre>
+ */
+final class Error
+{
+    private $url;
+    private $response;
+
+    public function __construct($url, $response)
+    {
+        $this->url = $url;
+        $this->response = $response;
+    }
+
+    public function code()
+    {
+        return $this->response->statusCode;
+    }
+
+    public function getResponse()
+    {
+        return $this->response;
+    }
+
+    public function message()
+    {
+        return $this->response->error;
+    }
+}

+ 18 - 0
src/Qiniu/Http/Request.php

@@ -0,0 +1,18 @@
+<?php
+namespace Qiniu\Http;
+
+final class Request
+{
+    public $url;
+    public $headers;
+    public $body;
+    public $method;
+
+    public function __construct($method, $url, array $headers = array(), $body = null)
+    {
+        $this->method = strtoupper($method);
+        $this->url = $url;
+        $this->headers = $headers;
+        $this->body = $body;
+    }
+}

+ 176 - 0
src/Qiniu/Http/Response.php

@@ -0,0 +1,176 @@
+<?php
+
+namespace Qiniu\Http;
+
+/**
+ * HTTP response Object
+ */
+final class Response
+{
+    public $statusCode;
+    public $headers;
+    public $body;
+    public $error;
+    private $jsonData;
+    public $duration;
+
+    /** @var array Mapping of status codes to reason phrases */
+    private static $statusTexts = array(
+        100 => 'Continue',
+        101 => 'Switching Protocols',
+        102 => 'Processing',
+        200 => 'OK',
+        201 => 'Created',
+        202 => 'Accepted',
+        203 => 'Non-Authoritative Information',
+        204 => 'No Content',
+        205 => 'Reset Content',
+        206 => 'Partial Content',
+        207 => 'Multi-Status',
+        208 => 'Already Reported',
+        226 => 'IM Used',
+        300 => 'Multiple Choices',
+        301 => 'Moved Permanently',
+        302 => 'Found',
+        303 => 'See Other',
+        304 => 'Not Modified',
+        305 => 'Use Proxy',
+        307 => 'Temporary Redirect',
+        308 => 'Permanent Redirect',
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        402 => 'Payment Required',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        406 => 'Not Acceptable',
+        407 => 'Proxy Authentication Required',
+        408 => 'Request Timeout',
+        409 => 'Conflict',
+        410 => 'Gone',
+        411 => 'Length Required',
+        412 => 'Precondition Failed',
+        413 => 'Request Entity Too Large',
+        414 => 'Request-URI Too Long',
+        415 => 'Unsupported Media Type',
+        416 => 'Requested Range Not Satisfiable',
+        417 => 'Expectation Failed',
+        422 => 'Unprocessable Entity',
+        423 => 'Locked',
+        424 => 'Failed Dependency',
+        425 => 'Reserved for WebDAV advanced collections expired proposal',
+        426 => 'Upgrade required',
+        428 => 'Precondition Required',
+        429 => 'Too Many Requests',
+        431 => 'Request Header Fields Too Large',
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Timeout',
+        505 => 'HTTP Version Not Supported',
+        506 => 'Variant Also Negotiates (Experimental)',
+        507 => 'Insufficient Storage',
+        508 => 'Loop Detected',
+        510 => 'Not Extended',
+        511 => 'Network Authentication Required',
+    );
+
+    /**
+     * @param int $code 状态码
+     * @param double $duration 请求时长
+     * @param array $headers 响应头部
+     * @param string $body 响应内容
+     * @param string $error 错误描述
+     */
+    public function __construct($code, $duration, array $headers = array(), $body = null, $error = null)
+    {
+        $this->statusCode = $code;
+        $this->duration = $duration;
+        $this->headers = $headers;
+        $this->body = $body;
+        $this->error = $error;
+        $this->jsonData = null;
+        if ($error !== null) {
+            return;
+        }
+
+        if ($body === null) {
+            if ($code >= 400) {
+                $this->error = self::$statusTexts[$code];
+            }
+            return;
+        }
+        if (self::isJson($headers)) {
+            try {
+                $jsonData = self::bodyJson($body);
+                if ($code >= 400) {
+                    $this->error = $body;
+                    if ($jsonData['error'] !== null) {
+                        $this->error = $jsonData['error'];
+                    }
+                }
+                $this->jsonData = $jsonData;
+            } catch (\InvalidArgumentException $e) {
+                $this->error = $body;
+                if ($code >= 200 && $code < 300) {
+                    $this->error = $e->getMessage();
+                }
+            }
+        } elseif ($code >= 400) {
+            $this->error = $body;
+        }
+        return;
+    }
+
+    public function json()
+    {
+        return $this->jsonData;
+    }
+
+    private static function bodyJson($body)
+    {
+        return \Qiniu\json_decode((string) $body, true, 512);
+    }
+
+    public function xVia()
+    {
+        $via = $this->headers['X-Via'];
+        if ($via === null) {
+            $via = $this->headers['X-Px'];
+        }
+        if ($via === null) {
+            $via = $this->headers['Fw-Via'];
+        }
+        return $via;
+    }
+
+    public function xLog()
+    {
+        return $this->headers['X-Log'];
+    }
+
+    public function xReqId()
+    {
+        return $this->headers['X-Reqid'];
+    }
+
+    public function ok()
+    {
+        return $this->statusCode >= 200 && $this->statusCode < 300 && $this->error === null;
+    }
+
+    public function needRetry()
+    {
+        $code = $this->statusCode;
+        if ($code < 0 || ($code / 100 === 5 and $code !== 579) || $code === 996) {
+            return true;
+        }
+    }
+
+    private static function isJson($headers)
+    {
+        return array_key_exists('Content-Type', $headers) &&
+        strpos($headers['Content-Type'], 'application/json') === 0;
+    }
+}

+ 282 - 0
src/Qiniu/Processing/ImageUrlBuilder.php

@@ -0,0 +1,282 @@
+<?php
+namespace Qiniu\Processing;
+
+use Qiniu;
+
+/**
+ * 主要涉及图片链接拼接
+ *
+ * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+ */
+final class ImageUrlBuilder
+{
+    /**
+     * mode合法范围值
+     *
+     * @var array
+     */
+    protected $modeArr = array(0, 1, 2, 3, 4, 5);
+
+    /**
+     * format合法值
+     *
+     * @var array
+     */
+    protected $formatArr = array('psd', 'jpeg', 'png', 'gif', 'webp', 'tiff', 'bmp');
+
+    /**
+     * 水印图片位置合法值
+     *
+     * @var array
+     */
+    protected $gravityArr = array('NorthWest', 'North', 'NorthEast',
+        'West', 'Center', 'East', 'SouthWest', 'South', 'SouthEast');
+
+    /**
+     * 缩略图链接拼接
+     *
+     * @param  string $url 图片链接
+     * @param  int $mode 缩略模式
+     * @param  int $width 宽度
+     * @param  int $height 长度
+     * @param  string $format 输出类型
+     * @param  int $quality 图片质量
+     * @param  int $interlace 是否支持渐进显示
+     * @param  int $ignoreError 忽略结果
+     * @return string
+     * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function thumbnail(
+        $url,
+        $mode,
+        $width,
+        $height,
+        $format = null,
+        $interlace = null,
+        $quality = null,
+        $ignoreError = 1
+    ) {
+
+        // url合法效验
+        if (!$this->isUrl($url)) {
+            return $url;
+        }
+
+        // 参数合法性效验
+        if (!in_array(intval($mode), $this->modeArr, true)) {
+            return $url;
+        }
+
+        if (!$width || !$height) {
+            return $url;
+        }
+
+        $thumbStr = 'imageView2/' . $mode . '/w/' . $width . '/h/' . $height . '/';
+
+        // 拼接输出格式
+        if (!is_null($format)
+            && in_array($format, $this->formatArr)
+        ) {
+            $thumbStr .= 'format/' . $format . '/';
+        }
+
+        // 拼接渐进显示
+        if (!is_null($interlace)
+            && in_array(intval($interlace), array(0, 1), true)
+        ) {
+            $thumbStr .= 'interlace/' . $interlace . '/';
+        }
+
+        // 拼接图片质量
+        if (!is_null($quality)
+            && intval($quality) >= 0
+            && intval($quality) <= 100
+        ) {
+            $thumbStr .= 'q/' . $quality . '/';
+        }
+
+        $thumbStr .= 'ignore-error/' . $ignoreError . '/';
+
+        // 如果有query_string用|线分割实现多参数
+        return $url . ($this->hasQuery($url) ? '|' : '?') . $thumbStr;
+    }
+
+    /**
+     * 图片水印
+     *
+     * @param  string $url 图片链接
+     * @param  string $image 水印图片链接
+     * @param  numeric $dissolve 透明度
+     * @param  string $gravity 水印位置
+     * @param  numeric $dx 横轴边距
+     * @param  numeric $dy 纵轴边距
+     * @param  numeric $watermarkScale 自适应原图的短边比例
+     * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function waterImg(
+        $url,
+        $image,
+        $dissolve = 100,
+        $gravity = 'SouthEast',
+        $dx = null,
+        $dy = null,
+        $watermarkScale = null
+    ) {
+        // url合法效验
+        if (!$this->isUrl($url)) {
+            return $url;
+        }
+
+        $waterStr = 'watermark/1/image/' . \Qiniu\base64_urlSafeEncode($image) . '/';
+
+        // 拼接水印透明度
+        if (is_numeric($dissolve)
+            && $dissolve <= 100
+        ) {
+            $waterStr .= 'dissolve/' . $dissolve . '/';
+        }
+
+        // 拼接水印位置
+        if (in_array($gravity, $this->gravityArr, true)) {
+            $waterStr .= 'gravity/' . $gravity . '/';
+        }
+
+        // 拼接横轴边距
+        if (!is_null($dx)
+            && is_numeric($dx)
+        ) {
+            $waterStr .= 'dx/' . $dx . '/';
+        }
+
+        // 拼接纵轴边距
+        if (!is_null($dy)
+            && is_numeric($dy)
+        ) {
+            $waterStr .= 'dy/' . $dy . '/';
+        }
+
+        // 拼接自适应原图的短边比例
+        if (!is_null($watermarkScale)
+            && is_numeric($watermarkScale)
+            && $watermarkScale > 0
+            && $watermarkScale < 1
+        ) {
+            $waterStr .= 'ws/' . $watermarkScale . '/';
+        }
+
+        // 如果有query_string用|线分割实现多参数
+        return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
+    }
+
+    /**
+     * 文字水印
+     *
+     * @param  string $url 图片链接
+     * @param  string $text 文字
+     * @param  string $font 文字字体
+     * @param  string $fontSize 文字字号
+     * @param  string $fontColor 文字颜色
+     * @param  numeric $dissolve 透明度
+     * @param  string $gravity 水印位置
+     * @param  numeric $dx 横轴边距
+     * @param  numeric $dy 纵轴边距
+     * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function waterText(
+        $url,
+        $text,
+        $font = '黑体',
+        $fontSize = 0,
+        $fontColor = null,
+        $dissolve = 100,
+        $gravity = 'SouthEast',
+        $dx = null,
+        $dy = null
+    ) {
+        // url合法效验
+        if (!$this->isUrl($url)) {
+            return $url;
+        }
+
+        $waterStr = 'watermark/2/text/'
+            . \Qiniu\base64_urlSafeEncode($text) . '/font/'
+            . \Qiniu\base64_urlSafeEncode($font) . '/';
+
+        // 拼接文字大小
+        if (is_int($fontSize)) {
+            $waterStr .= 'fontsize/' . $fontSize . '/';
+        }
+
+        // 拼接文字颜色
+        if (!is_null($fontColor)
+            && $fontColor
+        ) {
+            $waterStr .= 'fill/' . \Qiniu\base64_urlSafeEncode($fontColor) . '/';
+        }
+
+        // 拼接水印透明度
+        if (is_numeric($dissolve)
+            && $dissolve <= 100
+        ) {
+            $waterStr .= 'dissolve/' . $dissolve . '/';
+        }
+
+        // 拼接水印位置
+        if (in_array($gravity, $this->gravityArr, true)) {
+            $waterStr .= 'gravity/' . $gravity . '/';
+        }
+
+        // 拼接横轴边距
+        if (!is_null($dx)
+            && is_numeric($dx)
+        ) {
+            $waterStr .= 'dx/' . $dx . '/';
+        }
+
+        // 拼接纵轴边距
+        if (!is_null($dy)
+            && is_numeric($dy)
+        ) {
+            $waterStr .= 'dy/' . $dy . '/';
+        }
+
+        // 如果有query_string用|线分割实现多参数
+        return $url . ($this->hasQuery($url) ? '|' : '?') . $waterStr;
+    }
+
+    /**
+     * 效验url合法性
+     *
+     * @param  string $url url链接
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    protected function isUrl($url)
+    {
+        $urlArr = parse_url($url);
+
+        return $urlArr['scheme']
+        && in_array($urlArr['scheme'], array('http', 'https'))
+        && $urlArr['host']
+        && $urlArr['path'];
+    }
+
+    /**
+     * 检测是否有query
+     *
+     * @param  string $url url链接
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    protected function hasQuery($url)
+    {
+        $urlArr = parse_url($url);
+
+        return !empty($urlArr['query']);
+    }
+}

+ 60 - 0
src/Qiniu/Processing/Operation.php

@@ -0,0 +1,60 @@
+<?php
+
+namespace Qiniu\Processing;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+final class Operation
+{
+
+    private $auth;
+    private $token_expire;
+    private $domain;
+
+    public function __construct($domain, $auth = null, $token_expire = 3600)
+    {
+        $this->auth = $auth;
+        $this->domain = $domain;
+        $this->token_expire = $token_expire;
+    }
+
+
+    /**
+     * 对资源文件进行处理
+     *
+     * @param $key   待处理的资源文件名
+     * @param $fops   string|array  fop操作,多次fop操作以array的形式传入。
+     *                eg. imageView2/1/w/200/h/200, imageMogr2/thumbnail/!75px
+     *
+     * @return array 文件处理后的结果及错误。
+     *
+     * @link http://developer.qiniu.com/docs/v6/api/reference/fop/
+     */
+    public function execute($key, $fops)
+    {
+        $url = $this->buildUrl($key, $fops);
+        $resp = Client::get($url);
+        if (!$resp->ok()) {
+            return array(null, new Error($url, $resp));
+        }
+        if ($resp->json() !== null) {
+            return array($resp->json(), null);
+        }
+        return array($resp->body, null);
+    }
+
+    public function buildUrl($key, $fops, $protocol = 'http')
+    {
+        if (is_array($fops)) {
+            $fops = implode('|', $fops);
+        }
+
+        $url = $protocol . "://$this->domain/$key?$fops";
+        if ($this->auth !== null) {
+            $url = $this->auth->privateDownloadUrl($url, $this->token_expire);
+        }
+
+        return $url;
+    }
+}

+ 94 - 0
src/Qiniu/Processing/PersistentFop.php

@@ -0,0 +1,94 @@
+<?php
+namespace Qiniu\Processing;
+
+use Qiniu\Config;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+use Qiniu\Processing\Operation;
+
+/**
+ * 持久化处理类,该类用于主动触发异步持久化操作.
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/fop/pfop/pfop.html
+ */
+final class PersistentFop
+{
+    /**
+     * @var 账号管理密钥对,Auth对象
+     */
+    private $auth;
+
+    /*
+     * @var 配置对象,Config 对象
+     * */
+    private $config;
+
+
+    public function __construct($auth, $config = null)
+    {
+        $this->auth = $auth;
+        if ($config == null) {
+            $this->config = new Config();
+        } else {
+            $this->config = $config;
+        }
+    }
+
+    /**
+     * 对资源文件进行异步持久化处理
+     * @param $bucket     资源所在空间
+     * @param $key        待处理的源文件
+     * @param $fops       string|array  待处理的pfop操作,多个pfop操作以array的形式传入。
+     *                    eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360
+     * @param $pipeline   资源处理队列
+     * @param $notify_url 处理结果通知地址
+     * @param $force      是否强制执行一次新的指令
+     *
+     *
+     * @return array 返回持久化处理的persistentId, 和返回的错误。
+     *
+     * @link http://developer.qiniu.com/docs/v6/api/reference/fop/
+     */
+    public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = null, $force = false)
+    {
+        if (is_array($fops)) {
+            $fops = implode(';', $fops);
+        }
+        $params = array('bucket' => $bucket, 'key' => $key, 'fops' => $fops);
+        \Qiniu\setWithoutEmpty($params, 'pipeline', $pipeline);
+        \Qiniu\setWithoutEmpty($params, 'notifyURL', $notify_url);
+        if ($force) {
+            $params['force'] = 1;
+        }
+        $data = http_build_query($params);
+        $scheme = "http://";
+        if ($this->config->useHTTPS === true) {
+            $scheme = "https://";
+        }
+        $url = $scheme . Config::API_HOST . '/pfop/';
+        $headers = $this->auth->authorization($url, $data, 'application/x-www-form-urlencoded');
+        $headers['Content-Type'] = 'application/x-www-form-urlencoded';
+        $response = Client::post($url, $data, $headers);
+        if (!$response->ok()) {
+            return array(null, new Error($url, $response));
+        }
+        $r = $response->json();
+        $id = $r['persistentId'];
+        return array($id, null);
+    }
+
+    public function status($id)
+    {
+        $scheme = "http://";
+
+        if ($this->config->useHTTPS === true) {
+            $scheme = "https://";
+        }
+        $url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id";
+        $response = Client::get($url);
+        if (!$response->ok()) {
+            return array(null, new Error($url, $response));
+        }
+        return array($response->json(), null);
+    }
+}

+ 475 - 0
src/Qiniu/Storage/BucketManager.php

@@ -0,0 +1,475 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Auth;
+use Qiniu\Config;
+use Qiniu\Zone;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+/**
+ * 主要涉及了空间资源管理及批量操作接口的实现,具体的接口规格可以参考
+ *
+ * @link https://developer.qiniu.com/kodo/api/1274/rs
+ */
+final class BucketManager
+{
+    private $auth;
+    private $config;
+
+    public function __construct(Auth $auth, Config $config = null)
+    {
+        $this->auth = $auth;
+        if ($config == null) {
+            $this->config = new Config();
+        } else {
+            $this->config = $config;
+        }
+    }
+
+    /**
+     * 获取指定账号下所有的空间名。
+     *
+     * @return string[] 包含所有空间名
+     */
+    public function buckets($shared = true)
+    {
+        $includeShared = "false";
+        if ($shared === true) {
+            $includeShared = "true";
+        }
+        return $this->rsGet('/buckets?shared=' . $includeShared);
+    }
+
+    /**
+     * 获取指定空间绑定的所有的域名
+     *
+     * @return string[] 包含所有空间域名
+     */
+    public function domains($bucket)
+    {
+        return $this->apiGet('/v6/domain/list?tbl=' . $bucket);
+    }
+
+    /**
+     * 获取空间绑定的域名列表
+     * @return string[] 包含空间绑定的所有域名
+     */
+
+    /**
+     * 列取空间的文件列表
+     *
+     * @param $bucket     空间名
+     * @param $prefix     列举前缀
+     * @param $marker     列举标识符
+     * @param $limit      单次列举个数限制
+     * @param $delimiter  指定目录分隔符
+     *
+     * @return array    包含文件信息的数组,类似:[
+     *                                              {
+     *                                                 "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>",
+     *                                                  "fsize" => "<file size>",
+     *                                                  "putTime" => "<file modify time>"
+     *                                              },
+     *                                              ...
+     *                                            ]
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/list.html
+     */
+    public function listFiles($bucket, $prefix = null, $marker = null, $limit = 1000, $delimiter = null)
+    {
+        $query = array('bucket' => $bucket);
+        \Qiniu\setWithoutEmpty($query, 'prefix', $prefix);
+        \Qiniu\setWithoutEmpty($query, 'marker', $marker);
+        \Qiniu\setWithoutEmpty($query, 'limit', $limit);
+        \Qiniu\setWithoutEmpty($query, 'delimiter', $delimiter);
+        $url = $this->getRsfHost() . '/list?' . http_build_query($query);
+        return $this->get($url);
+    }
+
+    /**
+     * 获取资源的元信息,但不返回文件内容
+     *
+     * @param $bucket     待获取信息资源所在的空间
+     * @param $key        待获取资源的文件名
+     *
+     * @return array    包含文件信息的数组,类似:
+     *                                              [
+     *                                                  "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>",
+     *                                                  "fsize" => <file size>,
+     *                                                  "putTime" => "<file modify time>"
+     *                                                  "fileType" => <file type>
+     *                                              ]
+     *
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/stat.html
+     */
+    public function stat($bucket, $key)
+    {
+        $path = '/stat/' . \Qiniu\entry($bucket, $key);
+        return $this->rsGet($path);
+    }
+
+    /**
+     * 删除指定资源
+     *
+     * @param $bucket     待删除资源所在的空间
+     * @param $key        待删除资源的文件名
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/delete.html
+     */
+    public function delete($bucket, $key)
+    {
+        $path = '/delete/' . \Qiniu\entry($bucket, $key);
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+
+    /**
+     * 给资源进行重命名,本质为move操作。
+     *
+     * @param $bucket     待操作资源所在空间
+     * @param $oldname    待操作资源文件名
+     * @param $newname    目标资源文件名
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     */
+    public function rename($bucket, $oldname, $newname)
+    {
+        return $this->move($bucket, $oldname, $bucket, $newname);
+    }
+
+    /**
+     * 给资源进行重命名,本质为move操作。
+     *
+     * @param $from_bucket     待操作资源所在空间
+     * @param $from_key        待操作资源文件名
+     * @param $to_bucket       目标资源空间名
+     * @param $to_key          目标资源文件名
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/copy.html
+     */
+    public function copy($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
+    {
+        $from = \Qiniu\entry($from_bucket, $from_key);
+        $to = \Qiniu\entry($to_bucket, $to_key);
+        $path = '/copy/' . $from . '/' . $to;
+        if ($force === true) {
+            $path .= '/force/true';
+        }
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+    /**
+     * 将资源从一个空间到另一个空间
+     *
+     * @param $from_bucket     待操作资源所在空间
+     * @param $from_key        待操作资源文件名
+     * @param $to_bucket       目标资源空间名
+     * @param $to_key          目标资源文件名
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/move.html
+     */
+    public function move($from_bucket, $from_key, $to_bucket, $to_key, $force = false)
+    {
+        $from = \Qiniu\entry($from_bucket, $from_key);
+        $to = \Qiniu\entry($to_bucket, $to_key);
+        $path = '/move/' . $from . '/' . $to;
+        if ($force) {
+            $path .= '/force/true';
+        }
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+    /**
+     * 主动修改指定资源的文件类型
+     *
+     * @param $bucket     待操作资源所在空间
+     * @param $key        待操作资源文件名
+     * @param $mime       待操作文件目标mimeType
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/chgm.html
+     */
+    public function changeMime($bucket, $key, $mime)
+    {
+        $resource = \Qiniu\entry($bucket, $key);
+        $encode_mime = \Qiniu\base64_urlSafeEncode($mime);
+        $path = '/chgm/' . $resource . '/mime/' . $encode_mime;
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+
+    /**
+     * 修改指定资源的存储类型
+     *
+     * @param $bucket     待操作资源所在空间
+     * @param $key        待操作资源文件名
+     * @param $fileType       待操作文件目标文件类型
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  https://developer.qiniu.com/kodo/api/3710/modify-the-file-type
+     */
+    public function changeType($bucket, $key, $fileType)
+    {
+        $resource = \Qiniu\entry($bucket, $key);
+        $path = '/chtype/' . $resource . '/type/' . $fileType;
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+
+    /**
+     * 从指定URL抓取资源,并将该资源存储到指定空间中
+     *
+     * @param $url        指定的URL
+     * @param $bucket     目标资源空间
+     * @param $key        目标资源文件名
+     *
+     * @return array    包含已拉取的文件信息。
+     *                         成功时:  [
+     *                                          [
+     *                                              "hash" => "<Hash string>",
+     *                                              "key" => "<Key string>"
+     *                                          ],
+     *                                          null
+     *                                  ]
+     *
+     *                         失败时:  [
+     *                                          null,
+     *                                         Qiniu/Http/Error
+     *                                  ]
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/fetch.html
+     */
+    public function fetch($url, $bucket, $key = null)
+    {
+
+        $resource = \Qiniu\base64_urlSafeEncode($url);
+        $to = \Qiniu\entry($bucket, $key);
+        $path = '/fetch/' . $resource . '/to/' . $to;
+
+        $ak = $this->auth->getAccessKey();
+        $ioHost = $this->config->getIovipHost($ak, $bucket);
+
+        $url = $ioHost . $path;
+        return $this->post($url, null);
+    }
+
+    /**
+     * 从镜像源站抓取资源到空间中,如果空间中已经存在,则覆盖该资源
+     *
+     * @param $bucket     待获取资源所在的空间
+     * @param $key        代获取资源文件名
+     *
+     * @return mixed      成功返回NULL,失败返回对象Qiniu\Http\Error
+     * @link  http://developer.qiniu.com/docs/v6/api/reference/rs/prefetch.html
+     */
+    public function prefetch($bucket, $key)
+    {
+        $resource = \Qiniu\entry($bucket, $key);
+        $path = '/prefetch/' . $resource;
+
+        $ak = $this->auth->getAccessKey();
+        $ioHost = $this->config->getIovipHost($ak, $bucket);
+
+        $url = $ioHost . $path;
+        list(, $error) = $this->post($url, null);
+        return $error;
+    }
+
+    /**
+     * 在单次请求中进行多个资源管理操作
+     *
+     * @param $operations     资源管理操作数组
+     *
+     * @return array 每个资源的处理情况,结果类似:
+     *              [
+     *                   { "code" => <HttpCode int>, "data" => <Data> },
+     *                   { "code" => <HttpCode int> },
+     *                   { "code" => <HttpCode int> },
+     *                   { "code" => <HttpCode int> },
+     *                   { "code" => <HttpCode int>, "data" => { "error": "<ErrorMessage string>" } },
+     *                   ...
+     *               ]
+     * @link http://developer.qiniu.com/docs/v6/api/reference/rs/batch.html
+     */
+    public function batch($operations)
+    {
+        $params = 'op=' . implode('&op=', $operations);
+        return $this->rsPost('/batch', $params);
+    }
+
+    /**
+     * 设置文件的生命周期
+     *
+     * @param $bucket 设置文件生命周期文件所在的空间
+     * @param $key    设置文件生命周期文件的文件名
+     * @param $days   设置该文件多少天后删除,当$days设置为0时表示取消该文件的生命周期
+     *
+     * @return Mixed
+     * @link https://developer.qiniu.com/kodo/api/update-file-lifecycle
+     */
+    public function deleteAfterDays($bucket, $key, $days)
+    {
+        $entry = \Qiniu\entry($bucket, $key);
+        $path = "/deleteAfterDays/$entry/$days";
+        list(, $error) = $this->rsPost($path);
+        return $error;
+    }
+
+    private function getRsfHost()
+    {
+        $scheme = "http://";
+        if ($this->config->useHTTPS == true) {
+            $scheme = "https://";
+        }
+        return $scheme . Config::RSF_HOST;
+    }
+
+    private function getRsHost()
+    {
+        $scheme = "http://";
+        if ($this->config->useHTTPS == true) {
+            $scheme = "https://";
+        }
+        return $scheme . Config::RS_HOST;
+    }
+
+    private function getApiHost()
+    {
+        $scheme = "http://";
+        if ($this->config->useHTTPS == true) {
+            $scheme = "https://";
+        }
+        return $scheme . Config::API_HOST;
+    }
+
+    private function rsPost($path, $body = null)
+    {
+        $url = $this->getRsHost() . $path;
+        return $this->post($url, $body);
+    }
+
+    private function apiGet($path)
+    {
+        $url = $this->getApiHost() . $path;
+        return $this->get($url);
+    }
+
+    private function rsGet($path)
+    {
+        $url = $this->getRsHost() . $path;
+        return $this->get($url);
+    }
+
+    private function get($url)
+    {
+        $headers = $this->auth->authorization($url);
+        $ret = Client::get($url, $headers);
+        if (!$ret->ok()) {
+            return array(null, new Error($url, $ret));
+        }
+        return array($ret->json(), null);
+    }
+
+    private function post($url, $body)
+    {
+        $headers = $this->auth->authorization($url, $body, 'application/x-www-form-urlencoded');
+        $ret = Client::post($url, $body, $headers);
+        if (!$ret->ok()) {
+            return array(null, new Error($url, $ret));
+        }
+        $r = ($ret->body === null) ? array() : $ret->json();
+        return array($r, null);
+    }
+
+    public static function buildBatchCopy($source_bucket, $key_pairs, $target_bucket, $force)
+    {
+        return self::twoKeyBatch('/copy', $source_bucket, $key_pairs, $target_bucket, $force);
+    }
+
+
+    public static function buildBatchRename($bucket, $key_pairs, $force)
+    {
+        return self::buildBatchMove($bucket, $key_pairs, $bucket, $force);
+    }
+
+
+    public static function buildBatchMove($source_bucket, $key_pairs, $target_bucket, $force)
+    {
+        return self::twoKeyBatch('/move', $source_bucket, $key_pairs, $target_bucket, $force);
+    }
+
+
+    public static function buildBatchDelete($bucket, $keys)
+    {
+        return self::oneKeyBatch('/delete', $bucket, $keys);
+    }
+
+
+    public static function buildBatchStat($bucket, $keys)
+    {
+        return self::oneKeyBatch('/stat', $bucket, $keys);
+    }
+
+    public static function buildBatchDeleteAfterDays($bucket, $key_day_pairs)
+    {
+        $data = array();
+        foreach ($key_day_pairs as $key => $day) {
+            array_push($data, '/deleteAfterDays/' . \Qiniu\entry($bucket, $key) . '/' . $day);
+        }
+        return $data;
+    }
+
+    public static function buildBatchChangeMime($bucket, $key_mime_pairs)
+    {
+        $data = array();
+        foreach ($key_mime_pairs as $key => $mime) {
+            array_push($data, '/chgm/' . \Qiniu\entry($bucket, $key) . '/mime/' . base64_encode($mime));
+        }
+        return $data;
+    }
+
+    public static function buildBatchChangeType($bucket, $key_type_pairs)
+    {
+        $data = array();
+        foreach ($key_type_pairs as $key => $type) {
+            array_push($data, '/chtype/' . \Qiniu\entry($bucket, $key) . '/type/' . $type);
+        }
+        return $data;
+    }
+
+    private static function oneKeyBatch($operation, $bucket, $keys)
+    {
+        $data = array();
+        foreach ($keys as $key) {
+            array_push($data, $operation . '/' . \Qiniu\entry($bucket, $key));
+        }
+        return $data;
+    }
+
+    private static function twoKeyBatch($operation, $source_bucket, $key_pairs, $target_bucket, $force)
+    {
+        if ($target_bucket === null) {
+            $target_bucket = $source_bucket;
+        }
+        $data = array();
+        $forceOp = "false";
+        if ($force) {
+            $forceOp = "true";
+        }
+        foreach ($key_pairs as $from_key => $to_key) {
+            $from = \Qiniu\entry($source_bucket, $from_key);
+            $to = \Qiniu\entry($target_bucket, $to_key);
+            array_push($data, $operation . '/' . $from . '/' . $to . "/force/" . $forceOp);
+        }
+        return $data;
+    }
+}

+ 139 - 0
src/Qiniu/Storage/FormUploader.php

@@ -0,0 +1,139 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+final class FormUploader
+{
+
+    /**
+     * 上传二进制流到七牛, 内部使用
+     *
+     * @param $upToken    上传凭证
+     * @param $key        上传文件名
+     * @param $data       上传二进制流
+     * @param $config     上传配置
+     * @param $params     自定义变量,规格参考
+     *                    http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+     * @param $mime       上传数据的mimeType
+     *
+     * @return array    包含已上传文件的信息,类似:
+     *                                              [
+     *                                                  "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>"
+     *                                              ]
+     */
+    public static function put(
+        $upToken,
+        $key,
+        $data,
+        $config,
+        $params,
+        $mime,
+        $fname
+    ) {
+
+        $fields = array('token' => $upToken);
+        if ($key === null) {
+            $fname='nullkey';
+        } else {
+            $fields['key'] = $key;
+        }
+
+        //enable crc32 check by default
+        $fields['crc32'] = \Qiniu\crc32_data($data);
+
+        if ($params) {
+            foreach ($params as $k => $v) {
+                $fields[$k] = $v;
+            }
+        }
+
+        list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+        if ($err != null) {
+            return array(null, $err);
+        }
+
+        $upHost = $config->getUpHost($accessKey, $bucket);
+
+        $response = Client::multipartPost($upHost, $fields, 'file', $fname, $data, $mime);
+        if (!$response->ok()) {
+            return array(null, new Error($upHost, $response));
+        }
+        return array($response->json(), null);
+    }
+
+    /**
+     * 上传文件到七牛,内部使用
+     *
+     * @param $upToken    上传凭证
+     * @param $key        上传文件名
+     * @param $filePath   上传文件的路径
+     * @param $config     上传配置
+     * @param $params     自定义变量,规格参考
+     *                    http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+     * @param $mime       上传数据的mimeType
+     *
+     * @return array    包含已上传文件的信息,类似:
+     *                                              [
+     *                                                  "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>"
+     *                                              ]
+     */
+    public static function putFile(
+        $upToken,
+        $key,
+        $filePath,
+        $config,
+        $params,
+        $mime
+    ) {
+
+
+        $fields = array('token' => $upToken, 'file' => self::createFile($filePath, $mime));
+        if ($key !== null) {
+            $fields['key'] = $key;
+        }
+
+        $fields['crc32'] = \Qiniu\crc32_file($filePath);
+
+        if ($params) {
+            foreach ($params as $k => $v) {
+                $fields[$k] = $v;
+            }
+        }
+        $fields['key'] = $key;
+        $headers = array('Content-Type' => 'multipart/form-data');
+
+        list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+        if ($err != null) {
+            return array(null, $err);
+        }
+
+        $upHost = $config->getUpHost($accessKey, $bucket);
+
+        $response = Client::post($upHost, $fields, $headers);
+        if (!$response->ok()) {
+            return array(null, new Error($upHost, $response));
+        }
+        return array($response->json(), null);
+    }
+
+    private static function createFile($filename, $mime)
+    {
+        // PHP 5.5 introduced a CurlFile object that deprecates the old @filename syntax
+        // See: https://wiki.php.net/rfc/curl-file-upload
+        if (function_exists('curl_file_create')) {
+            return curl_file_create($filename, $mime);
+        }
+
+        // Use the old style if using an older version of PHP
+        $value = "@{$filename}";
+        if (!empty($mime)) {
+            $value .= ';type=' . $mime;
+        }
+
+        return $value;
+    }
+}

+ 169 - 0
src/Qiniu/Storage/ResumeUploader.php

@@ -0,0 +1,169 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Config;
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+/**
+ * 断点续上传类, 该类主要实现了断点续上传中的分块上传,
+ * 以及相应地创建块和创建文件过程.
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkblk.html
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/mkfile.html
+ */
+final class ResumeUploader
+{
+    private $upToken;
+    private $key;
+    private $inputStream;
+    private $size;
+    private $params;
+    private $mime;
+    private $contexts;
+    private $host;
+    private $currentUrl;
+    private $config;
+
+    /**
+     * 上传二进制流到七牛
+     *
+     * @param $upToken    上传凭证
+     * @param $key        上传文件名
+     * @param $inputStream 上传二进制流
+     * @param $size       上传流的大小
+     * @param $params     自定义变量
+     * @param $mime       上传数据的mimeType
+     *
+     * @link http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+     */
+    public function __construct(
+        $upToken,
+        $key,
+        $inputStream,
+        $size,
+        $params,
+        $mime,
+        $config
+    ) {
+
+        $this->upToken = $upToken;
+        $this->key = $key;
+        $this->inputStream = $inputStream;
+        $this->size = $size;
+        $this->params = $params;
+        $this->mime = $mime;
+        $this->contexts = array();
+        $this->config = $config;
+
+        list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($upToken);
+        if ($err != null) {
+            return array(null, $err);
+        }
+
+        $upHost = $config->getUpHost($accessKey, $bucket);
+        if ($err != null) {
+            throw new \Exception($err->message(), 1);
+        }
+        $this->host = $upHost;
+    }
+
+    /**
+     * 上传操作
+     */
+    public function upload($fname)
+    {
+        $uploaded = 0;
+        while ($uploaded < $this->size) {
+            $blockSize = $this->blockSize($uploaded);
+            $data = fread($this->inputStream, $blockSize);
+            if ($data === false) {
+                throw new \Exception("file read failed", 1);
+            }
+            $crc = \Qiniu\crc32_data($data);
+            $response = $this->makeBlock($data, $blockSize);
+            $ret = null;
+            if ($response->ok() && $response->json() != null) {
+                $ret = $response->json();
+            }
+            if ($response->statusCode < 0) {
+                list($accessKey, $bucket, $err) = \Qiniu\explodeUpToken($this->upToken);
+                if ($err != null) {
+                    return array(null, $err);
+                }
+
+                $upHostBackup = $this->config->getUpBackupHost($accessKey, $bucket);
+                $this->host = $upHostBackup;
+            }
+            if ($response->needRetry() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
+                $response = $this->makeBlock($data, $blockSize);
+                $ret = $response->json();
+            }
+
+            if (!$response->ok() || !isset($ret['crc32']) || $crc != $ret['crc32']) {
+                return array(null, new Error($this->currentUrl, $response));
+            }
+            array_push($this->contexts, $ret['ctx']);
+            $uploaded += $blockSize;
+        }
+        return $this->makeFile($fname);
+    }
+
+    /**
+     * 创建块
+     */
+    private function makeBlock($block, $blockSize)
+    {
+        $url = $this->host . '/mkblk/' . $blockSize;
+        return $this->post($url, $block);
+    }
+
+    private function fileUrl($fname)
+    {
+        $url = $this->host . '/mkfile/' . $this->size;
+        $url .= '/mimeType/' . \Qiniu\base64_urlSafeEncode($this->mime);
+        if ($this->key != null) {
+            $url .= '/key/' . \Qiniu\base64_urlSafeEncode($this->key);
+        }
+        $url .= '/fname/' . \Qiniu\base64_urlSafeEncode($fname);
+        if (!empty($this->params)) {
+            foreach ($this->params as $key => $value) {
+                $val = \Qiniu\base64_urlSafeEncode($value);
+                $url .= "/$key/$val";
+            }
+        }
+        return $url;
+    }
+
+    /**
+     * 创建文件
+     */
+    private function makeFile($fname)
+    {
+        $url = $this->fileUrl($fname);
+        $body = implode(',', $this->contexts);
+        $response = $this->post($url, $body);
+        if ($response->needRetry()) {
+            $response = $this->post($url, $body);
+        }
+        if (!$response->ok()) {
+            return array(null, new Error($this->currentUrl, $response));
+        }
+        return array($response->json(), null);
+    }
+
+    private function post($url, $data)
+    {
+        $this->currentUrl = $url;
+        $headers = array('Authorization' => 'UpToken ' . $this->upToken);
+        return Client::post($url, $data, $headers);
+    }
+
+    private function blockSize($uploaded)
+    {
+        if ($this->size < $uploaded + Config::BLOCK_SIZE) {
+            return $this->size - $uploaded;
+        }
+        return Config::BLOCK_SIZE;
+    }
+}

+ 144 - 0
src/Qiniu/Storage/UploadManager.php

@@ -0,0 +1,144 @@
+<?php
+namespace Qiniu\Storage;
+
+use Qiniu\Config;
+use Qiniu\Http\HttpClient;
+use Qiniu\Storage\ResumeUploader;
+use Qiniu\Storage\FormUploader;
+
+/**
+ * 主要涉及了资源上传接口的实现
+ *
+ * @link http://developer.qiniu.com/docs/v6/api/reference/up/
+ */
+final class UploadManager
+{
+    private $config;
+
+    public function __construct(Config $config = null)
+    {
+        if ($config === null) {
+            $config = new Config();
+        }
+        $this->config = $config;
+    }
+
+    /**
+     * 上传二进制流到七牛
+     *
+     * @param $upToken    上传凭证
+     * @param $key        上传文件名
+     * @param $data       上传二进制流
+     * @param $params     自定义变量,规格参考
+     *                    http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+     * @param $mime       上传数据的mimeType
+     * @param $checkCrc   是否校验crc32
+     *
+     * @return array    包含已上传文件的信息,类似:
+     *                                              [
+     *                                                  "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>"
+     *                                              ]
+     */
+    public function put(
+        $upToken,
+        $key,
+        $data,
+        $params = null,
+        $mime = 'application/octet-stream',
+        $fname = null
+    ) {
+    
+        $params = self::trimParams($params);
+        return FormUploader::put(
+            $upToken,
+            $key,
+            $data,
+            $this->config,
+            $params,
+            $mime,
+            $fname
+        );
+    }
+
+
+    /**
+     * 上传文件到七牛
+     *
+     * @param $upToken    上传凭证
+     * @param $key        上传文件名
+     * @param $filePath   上传文件的路径
+     * @param $params     自定义变量,规格参考
+     *                    http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html#xvar
+     * @param $mime       上传数据的mimeType
+     * @param $checkCrc   是否校验crc32
+     *
+     * @return array    包含已上传文件的信息,类似:
+     *                                              [
+     *                                                  "hash" => "<Hash string>",
+     *                                                  "key" => "<Key string>"
+     *                                              ]
+     */
+    public function putFile(
+        $upToken,
+        $key,
+        $filePath,
+        $params = null,
+        $mime = 'application/octet-stream',
+        $checkCrc = false
+    ) {
+    
+        $file = fopen($filePath, 'rb');
+        if ($file === false) {
+            throw new \Exception("file can not open", 1);
+        }
+        $params = self::trimParams($params);
+        $stat = fstat($file);
+        $size = $stat['size'];
+        if ($size <= Config::BLOCK_SIZE) {
+            $data = fread($file, $size);
+            fclose($file);
+            if ($data === false) {
+                throw new \Exception("file can not read", 1);
+            }
+            return FormUploader::put(
+                $upToken,
+                $key,
+                $data,
+                $this->config,
+                $params,
+                $mime,
+                $checkCrc,
+                basename($filePath)
+            );
+        }
+
+        $up = new ResumeUploader(
+            $upToken,
+            $key,
+            $file,
+            $size,
+            $params,
+            $mime,
+            $this->config
+        );
+        $ret = $up->upload(basename($filePath));
+        fclose($file);
+        return $ret;
+    }
+
+    public static function trimParams($params)
+    {
+        if ($params === null) {
+            return null;
+        }
+        $ret = array();
+        foreach ($params as $k => $v) {
+            $pos = strpos($k, 'x:');
+            if ($pos === 0 && !empty($v)) {
+                $ret[$k] = $v;
+            }
+        }
+        return $ret;
+    }
+}

+ 171 - 0
src/Qiniu/Zone.php

@@ -0,0 +1,171 @@
+<?php
+namespace Qiniu;
+
+use Qiniu\Http\Client;
+use Qiniu\Http\Error;
+
+final class Zone
+{
+
+    //源站上传域名
+    public $srcUpHosts;
+    //CDN加速上传域名
+    public $cdnUpHosts;
+    //资源管理域名
+    public $rsHost;
+    //资源列举域名
+    public $rsfHost;
+    //资源处理域名
+    public $apiHost;
+    //IOVIP域名
+    public $iovipHost;
+
+    //构造一个Zone对象
+    public function __construct(
+        $srcUpHosts = array(),
+        $cdnUpHosts = array(),
+        $rsHost = "rs.qiniu.com",
+        $rsfHost = "rsf.qiniu.com",
+        $apiHost = "api.qiniu.com",
+        $iovipHost = null
+    ) {
+
+        $this->srcUpHosts = $srcUpHosts;
+        $this->cdnUpHosts = $cdnUpHosts;
+        $this->rsHost = $rsHost;
+        $this->rsfHost = $rsfHost;
+        $this->apiHost = $apiHost;
+        $this->iovipHost = $iovipHost;
+    }
+
+    //华东机房
+    public static function zone0()
+    {
+        $Zone_z0 = new Zone(
+            array("up.qiniup.com", 'up-jjh.qiniup.com', 'up-xs.qiniup.com'),
+            array('upload.qiniup.com', 'upload-jjh.qiniup.com', 'upload-xs.qiniup.com'),
+            'rs.qiniu.com',
+            'rsf.qiniu.com',
+            'api.qiniu.com',
+            'iovip.qbox.me'
+        );
+        return $Zone_z0;
+    }
+
+    //华北机房
+    public static function zone1()
+    {
+        $Zone_z1 = new Zone(
+            array('up-z1.qiniup.com'),
+            array('upload-z1.qiniup.com'),
+            "rs-z1.qiniu.com",
+            "rsf-z1.qiniu.com",
+            "api-z1.qiniu.com",
+            "iovip-z1.qbox.me"
+        );
+
+        return $Zone_z1;
+    }
+
+    //华南机房
+    public static function zone2()
+    {
+        $Zone_z2 = new Zone(
+            array('up-z2.qiniup.com', 'up-dg.qiniup.com', 'up-fs.qiniup.com'),
+            array('upload-z2.qiniup.com', 'upload-dg.qiniup.com', 'upload-fs.qiniup.com'),
+            "rs-z2.qiniu.com",
+            "rsf-z2.qiniu.com",
+            "api-z2.qiniu.com",
+            "iovip-z2.qbox.me"
+        );
+        return $Zone_z2;
+    }
+
+    //北美机房
+    public static function zoneNa0()
+    {
+        //北美机房
+        $Zone_na0 = new Zone(
+            array('up-na0.qiniup.com'),
+            array('upload-na0.qiniup.com'),
+            "rs-na0.qiniu.com",
+            "rsf-na0.qiniu.com",
+            "api-na0.qiniu.com",
+            "iovip-na0.qbox.me"
+        );
+        return $Zone_na0;
+    }
+
+    //新加坡机房
+    public static function zoneAs0()
+    {
+        //新加坡机房
+        $Zone_as0 = new Zone(
+            array('up-as0.qiniup.com'),
+            array('upload-as0.qiniup.com'),
+            "rs-as0.qiniu.com",
+            "rsf-as0.qiniu.com",
+            "api-as0.qiniu.com",
+            "iovip-as0.qbox.me"
+        );
+        return $Zone_as0;
+    }
+
+    /*
+     * GET /v2/query?ak=<ak>&&bucket=<bucket>
+     **/
+    public static function queryZone($ak, $bucket)
+    {
+        $zone = new Zone();
+        $url = Config::UC_HOST . '/v2/query' . "?ak=$ak&bucket=$bucket";
+        $ret = Client::Get($url);
+        if (!$ret->ok()) {
+            return array(null, new Error($url, $ret));
+        }
+        $r = ($ret->body === null) ? array() : $ret->json();
+        //print_r($ret);
+        //parse zone;
+
+        $iovipHost = $r['io']['src']['main'][0];
+        $zone->iovipHost = $iovipHost;
+        $accMain = $r['up']['acc']['main'][0];
+        array_push($zone->cdnUpHosts, $accMain);
+        if (isset($r['up']['acc']['backup'])) {
+            foreach ($r['up']['acc']['backup'] as $key => $value) {
+                array_push($zone->cdnUpHosts, $value);
+            }
+        }
+        $srcMain = $r['up']['src']['main'][0];
+        array_push($zone->srcUpHosts, $srcMain);
+        if (isset($r['up']['src']['backup'])) {
+            foreach ($r['up']['src']['backup'] as $key => $value) {
+                array_push($zone->srcUpHosts, $value);
+            }
+        }
+
+        //set specific hosts
+        if (strstr($zone->iovipHost, "z1") !== false) {
+            $zone->rsHost = "rs-z1.qiniu.com";
+            $zone->rsfHost = "rsf-z1.qiniu.com";
+            $zone->apiHost = "api-z1.qiniu.com";
+        } elseif (strstr($zone->iovipHost, "z2") !== false) {
+            $zone->rsHost = "rs-z2.qiniu.com";
+            $zone->rsfHost = "rsf-z2.qiniu.com";
+            $zone->apiHost = "api-z2.qiniu.com";
+        } elseif (strstr($zone->iovipHost, "na0") !== false) {
+            $zone->rsHost = "rs-na0.qiniu.com";
+            $zone->rsfHost = "rsf-na0.qiniu.com";
+            $zone->apiHost = "api-na0.qiniu.com";
+        } elseif (strstr($zone->iovipHost, "as0") !== false) {
+            $zone->rsHost = "rs-as0.qiniu.com";
+            $zone->rsfHost = "rsf-as0.qiniu.com";
+            $zone->apiHost = "api-as0.qiniu.com";
+        } else {
+            $zone->rsHost = "rs.qiniu.com";
+            $zone->rsfHost = "rsf.qiniu.com";
+            $zone->apiHost = "api.qiniu.com";
+        }
+
+        return $zone;
+    }
+}

+ 264 - 0
src/Qiniu/functions.php

@@ -0,0 +1,264 @@
+<?php
+
+namespace Qiniu;
+
+use Qiniu\Config;
+
+if (!defined('QINIU_FUNCTIONS_VERSION')) {
+    define('QINIU_FUNCTIONS_VERSION', Config::SDK_VER);
+
+    /**
+     * 计算文件的crc32检验码:
+     *
+     * @param $file string  待计算校验码的文件路径
+     *
+     * @return string 文件内容的crc32校验码
+     */
+    function crc32_file($file)
+    {
+        $hash = hash_file('crc32b', $file);
+        $array = unpack('N', pack('H*', $hash));
+        return sprintf('%u', $array[1]);
+    }
+
+    /**
+     * 计算输入流的crc32检验码
+     *
+     * @param $data 待计算校验码的字符串
+     *
+     * @return string 输入字符串的crc32校验码
+     */
+    function crc32_data($data)
+    {
+        $hash = hash('crc32b', $data);
+        $array = unpack('N', pack('H*', $hash));
+        return sprintf('%u', $array[1]);
+    }
+
+    /**
+     * 对提供的数据进行urlsafe的base64编码。
+     *
+     * @param string $data 待编码的数据,一般为字符串
+     *
+     * @return string 编码后的字符串
+     * @link http://developer.qiniu.com/docs/v6/api/overview/appendix.html#urlsafe-base64
+     */
+    function base64_urlSafeEncode($data)
+    {
+        $find = array('+', '/');
+        $replace = array('-', '_');
+        return str_replace($find, $replace, base64_encode($data));
+    }
+
+    /**
+     * 对提供的urlsafe的base64编码的数据进行解码
+     *
+     * @param string $str 待解码的数据,一般为字符串
+     *
+     * @return string 解码后的字符串
+     */
+    function base64_urlSafeDecode($str)
+    {
+        $find = array('-', '_');
+        $replace = array('+', '/');
+        return base64_decode(str_replace($find, $replace, $str));
+    }
+
+    /**
+     * Wrapper for JSON decode that implements error detection with helpful
+     * error messages.
+     *
+     * @param string $json JSON data to parse
+     * @param bool $assoc When true, returned objects will be converted
+     *                        into associative arrays.
+     * @param int $depth User specified recursion depth.
+     *
+     * @return mixed
+     * @throws \InvalidArgumentException if the JSON cannot be parsed.
+     * @link http://www.php.net/manual/en/function.json-decode.php
+     */
+    function json_decode($json, $assoc = false, $depth = 512)
+    {
+        static $jsonErrors = array(
+            JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded',
+            JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch',
+            JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found',
+            JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON',
+            JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded'
+        );
+
+        if (empty($json)) {
+            return null;
+        }
+        $data = \json_decode($json, $assoc, $depth);
+
+        if (JSON_ERROR_NONE !== json_last_error()) {
+            $last = json_last_error();
+            throw new \InvalidArgumentException(
+                'Unable to parse JSON data: '
+                . (isset($jsonErrors[$last])
+                    ? $jsonErrors[$last]
+                    : 'Unknown error')
+            );
+        }
+
+        return $data;
+    }
+
+    /**
+     * 计算七牛API中的数据格式
+     *
+     * @param $bucket 待操作的空间名
+     * @param $key 待操作的文件名
+     *
+     * @return string  符合七牛API规格的数据格式
+     * @link http://developer.qiniu.com/docs/v6/api/reference/data-formats.html
+     */
+    function entry($bucket, $key)
+    {
+        $en = $bucket;
+        if (!empty($key)) {
+            $en = $bucket . ':' . $key;
+        }
+        return base64_urlSafeEncode($en);
+    }
+
+    /**
+     * array 辅助方法,无值时不set
+     *
+     * @param $array 待操作array
+     * @param $key key
+     * @param $value value 为null时 不设置
+     *
+     * @return array 原来的array,便于连续操作
+     */
+    function setWithoutEmpty(&$array, $key, $value)
+    {
+        if (!empty($value)) {
+            $array[$key] = $value;
+        }
+        return $array;
+    }
+
+    /**
+     * 缩略图链接拼接
+     *
+     * @param  string $url 图片链接
+     * @param  int $mode 缩略模式
+     * @param  int $width 宽度
+     * @param  int $height 长度
+     * @param  string $format 输出类型
+     * @param  int $quality 图片质量
+     * @param  int $interlace 是否支持渐进显示
+     * @param  int $ignoreError 忽略结果
+     * @return string
+     * @link http://developer.qiniu.com/code/v6/api/kodo-api/image/imageview2.html
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    function thumbnail(
+        $url,
+        $mode,
+        $width,
+        $height,
+        $format = null,
+        $quality = null,
+        $interlace = null,
+        $ignoreError = 1
+    ) {
+
+        static $imageUrlBuilder = null;
+        if (is_null($imageUrlBuilder)) {
+            $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+        }
+
+        return call_user_func_array(array($imageUrlBuilder, 'thumbnail'), func_get_args());
+    }
+
+    /**
+     * 图片水印
+     *
+     * @param  string $url 图片链接
+     * @param  string $image 水印图片链接
+     * @param  numeric $dissolve 透明度
+     * @param  string $gravity 水印位置
+     * @param  numeric $dx 横轴边距
+     * @param  numeric $dy 纵轴边距
+     * @param  numeric $watermarkScale 自适应原图的短边比例
+     * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    function waterImg(
+        $url,
+        $image,
+        $dissolve = 100,
+        $gravity = 'SouthEast',
+        $dx = null,
+        $dy = null,
+        $watermarkScale = null
+    ) {
+
+        static $imageUrlBuilder = null;
+        if (is_null($imageUrlBuilder)) {
+            $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+        }
+
+        return call_user_func_array(array($imageUrlBuilder, 'waterImg'), func_get_args());
+    }
+
+    /**
+     * 文字水印
+     *
+     * @param  string $url 图片链接
+     * @param  string $text 文字
+     * @param  string $font 文字字体
+     * @param  string $fontSize 文字字号
+     * @param  string $fontColor 文字颜色
+     * @param  numeric $dissolve 透明度
+     * @param  string $gravity 水印位置
+     * @param  numeric $dx 横轴边距
+     * @param  numeric $dy 纵轴边距
+     * @link   http://developer.qiniu.com/code/v6/api/kodo-api/image/watermark.html#text-watermark
+     * @return string
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    function waterText(
+        $url,
+        $text,
+        $font = '黑体',
+        $fontSize = 0,
+        $fontColor = null,
+        $dissolve = 100,
+        $gravity = 'SouthEast',
+        $dx = null,
+        $dy = null
+    ) {
+
+        static $imageUrlBuilder = null;
+        if (is_null($imageUrlBuilder)) {
+            $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder;
+        }
+
+        return call_user_func_array(array($imageUrlBuilder, 'waterText'), func_get_args());
+    }
+
+    /**
+     *  从uptoken解析accessKey和bucket
+     *
+     * @param $upToken
+     * @return array(ak,bucket,err=null)
+     */
+    function explodeUpToken($upToken)
+    {
+        $items = explode(':', $upToken);
+        if (count($items) != 3) {
+            return array(null, null, "invalid uptoken");
+        }
+        $accessKey = $items[0];
+        $putPolicy = json_decode(base64_decode($items[2]));
+        $scope = $putPolicy->scope;
+        $scopeItems = explode(':', $scope);
+        $bucket = $scopeItems[0];
+        return array($accessKey, $bucket, null);
+    }
+}

+ 4 - 0
test-env.sh

@@ -0,0 +1,4 @@
+export QINIU_ACCESS_KEY=xxx
+export QINIU_SECRET_KEY=xxx
+export QINIU_TEST_BUCKET=phpsdk
+export QINIU_TEST_DOMAIN=phpsdk.qiniudn.com

+ 71 - 0
tests/Qiniu/Tests/AuthTest.php

@@ -0,0 +1,71 @@
+<?php
+// Hack to override the time returned from the S3SignatureV4
+// @codingStandardsIgnoreStart
+namespace Qiniu {
+    function time()
+    {
+        return isset($_SERVER['override_qiniu_auth_time'])
+            ? 1234567890
+            : \time();
+    }
+}
+
+namespace Qiniu\Tests {
+    use Qiniu\Auth;
+
+    // @codingStandardsIgnoreEnd
+
+    class AuthTest extends \PHPUnit_Framework_TestCase
+    {
+
+        public function testSign()
+        {
+            global $dummyAuth;
+            $token = $dummyAuth->sign('test');
+            $this->assertEquals('abcdefghklmnopq:mSNBTR7uS2crJsyFr2Amwv1LaYg=', $token);
+        }
+
+        public function testSignWithData()
+        {
+            global $dummyAuth;
+            $token = $dummyAuth->signWithData('test');
+            $this->assertEquals('abcdefghklmnopq:-jP8eEV9v48MkYiBGs81aDxl60E=:dGVzdA==', $token);
+        }
+
+        public function testSignRequest()
+        {
+            global $dummyAuth;
+            $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', '');
+            $this->assertEquals('abcdefghklmnopq:cFyRVoWrE3IugPIMP5YJFTO-O-Y=', $token);
+            $ctype = 'application/x-www-form-urlencoded';
+            $token = $dummyAuth->signRequest('http://www.qiniu.com?go=1', 'test', $ctype);
+            $this->assertEquals($token, 'abcdefghklmnopq:svWRNcacOE-YMsc70nuIYdaa1e4=');
+        }
+
+        public function testPrivateDownloadUrl()
+        {
+            global $dummyAuth;
+            $_SERVER['override_qiniu_auth_time'] = true;
+            $url = $dummyAuth->privateDownloadUrl('http://www.qiniu.com?go=1');
+            $expect = 'http://www.qiniu.com?go=1&e=1234571490&token=abcdefghklmnopq:8vzBeLZ9W3E4kbBLFLW0Xe0u7v4=';
+            $this->assertEquals($expect, $url);
+            unset($_SERVER['override_qiniu_auth_time']);
+        }
+
+        public function testUploadToken()
+        {
+            global $dummyAuth;
+            $_SERVER['override_qiniu_auth_time'] = true;
+            $token = $dummyAuth->uploadToken('1', '2', 3600, array('endUser' => 'y'));
+            // @codingStandardsIgnoreStart
+            $exp = 'abcdefghklmnopq:yyeexeUkPOROoTGvwBjJ0F0VLEo=:eyJlbmRVc2VyIjoieSIsInNjb3BlIjoiMToyIiwiZGVhZGxpbmUiOjEyMzQ1NzE0OTB9';
+            // @codingStandardsIgnoreEnd
+            $this->assertEquals($exp, $token);
+            unset($_SERVER['override_qiniu_auth_time']);
+        }
+
+        public function testVerifyCallback()
+        {
+        }
+    }
+}

+ 14 - 0
tests/Qiniu/Tests/Base64Test.php

@@ -0,0 +1,14 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+
+class Base64Test extends \PHPUnit_Framework_TestCase
+{
+    public function testUrlSafe()
+    {
+        $a = '你好';
+        $b = \Qiniu\base64_urlSafeEncode($a);
+        $this->assertEquals($a, \Qiniu\base64_urlSafeDecode($b));
+    }
+}

+ 227 - 0
tests/Qiniu/Tests/BucketTest.php

@@ -0,0 +1,227 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\BucketManager;
+
+class BucketTest extends \PHPUnit_Framework_TestCase
+{
+    protected $bucketManager;
+    protected $dummyBucketManager;
+    protected $bucketName;
+    protected $key;
+    protected $key2;
+
+    protected function setUp()
+    {
+        global $bucketName;
+        global $key;
+        global $key2;
+        $this->bucketName = $bucketName;
+        $this->key = $key;
+        $this->key2 = $key2;
+
+        global $testAuth;
+        $this->bucketManager = new BucketManager($testAuth);
+
+        global $dummyAuth;
+        $this->dummyBucketManager = new BucketManager($dummyAuth);
+    }
+
+    public function testBuckets()
+    {
+
+        list($list, $error) = $this->bucketManager->buckets();
+        $this->assertTrue(in_array($this->bucketName, $list));
+        $this->assertNull($error);
+
+        list($list2, $error) = $this->dummyBucketManager->buckets();
+        $this->assertEquals(401, $error->code());
+        $this->assertNull($list2);
+        $this->assertNotNull($error->message());
+    }
+
+    public function testList()
+    {
+        list($ret, $error) = $this->bucketManager->listFiles($this->bucketName, null, null, 10);
+        $this->assertNotNull($ret['items'][0]);
+        $this->assertNotNull($ret['marker']);
+    }
+
+    public function testStat()
+    {
+        list($stat, $error) = $this->bucketManager->stat($this->bucketName, $this->key);
+        $this->assertNotNull($stat);
+        $this->assertNull($error);
+        $this->assertNotNull($stat['hash']);
+
+        list($stat, $error) = $this->bucketManager->stat($this->bucketName, 'nofile');
+        $this->assertNull($stat);
+        $this->assertEquals(612, $error->code());
+        $this->assertNotNull($error->message());
+
+        list($stat, $error) = $this->bucketManager->stat('nobucket', 'nofile');
+        $this->assertNull($stat);
+        $this->assertEquals(631, $error->code());
+        $this->assertNotNull($error->message());
+    }
+
+    public function testDelete()
+    {
+        $error = $this->bucketManager->delete($this->bucketName, 'del');
+        $this->assertEquals(612, $error->code());
+    }
+
+
+    public function testRename()
+    {
+        $key = 'renamefrom' . rand();
+        $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+        $key2 = 'renameto' . $key;
+        $error = $this->bucketManager->rename($this->bucketName, $key, $key2);
+        $this->assertNull($error);
+        $error = $this->bucketManager->delete($this->bucketName, $key2);
+        $this->assertNull($error);
+    }
+
+
+    public function testCopy()
+    {
+        $key = 'copyto' . rand();
+        $this->bucketManager->delete($this->bucketName, $key);
+
+        $error = $this->bucketManager->copy(
+            $this->bucketName,
+            $this->key,
+            $this->bucketName,
+            $key
+        );
+        $this->assertNull($error);
+
+        //test force copy
+        $error = $this->bucketManager->copy(
+            $this->bucketName,
+            $this->key2,
+            $this->bucketName,
+            $key,
+            true
+        );
+        $this->assertNull($error);
+
+        list($key2Stat,) = $this->bucketManager->stat($this->bucketName, $this->key2);
+        list($key2CopiedStat,) = $this->bucketManager->stat($this->bucketName, $key);
+
+        $this->assertEquals($key2Stat['hash'], $key2CopiedStat['hash']);
+
+        $error = $this->bucketManager->delete($this->bucketName, $key);
+        $this->assertNull($error);
+    }
+
+
+    public function testChangeMime()
+    {
+        $error = $this->bucketManager->changeMime(
+            $this->bucketName,
+            'php-sdk.html',
+            'text/html'
+        );
+        $this->assertNull($error);
+    }
+
+    public function testPrefetch()
+    {
+        $error = $this->bucketManager->prefetch(
+            $this->bucketName,
+            'php-sdk.html'
+        );
+        $this->assertNull($error);
+    }
+
+    public function testFetch()
+    {
+        list($ret, $error) = $this->bucketManager->fetch(
+            'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+            $this->bucketName,
+            'fetch.html'
+        );
+        $this->assertArrayHasKey('hash', $ret);
+        $this->assertNull($error);
+
+        list($ret, $error) = $this->bucketManager->fetch(
+            'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+            $this->bucketName,
+            ''
+        );
+        $this->assertArrayHasKey('key', $ret);
+        $this->assertNull($error);
+
+        list($ret, $error) = $this->bucketManager->fetch(
+            'http://developer.qiniu.com/docs/v6/sdk/php-sdk.html',
+            $this->bucketName
+        );
+        $this->assertArrayHasKey('key', $ret);
+        $this->assertNull($error);
+    }
+
+    public function testBatchCopy()
+    {
+        $key = 'copyto' . rand();
+        $ops = BucketManager::buildBatchCopy(
+            $this->bucketName,
+            array($this->key => $key),
+            $this->bucketName,
+            true
+        );
+        list($ret, $error) = $this->bucketManager->batch($ops);
+        $this->assertEquals(200, $ret[0]['code']);
+        $ops = BucketManager::buildBatchDelete($this->bucketName, array($key));
+        list($ret, $error) = $this->bucketManager->batch($ops);
+        $this->assertEquals(200, $ret[0]['code']);
+    }
+
+    public function testBatchMove()
+    {
+        $key = 'movefrom' . rand();
+        $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+        $key2 = $key . 'to';
+        $ops = BucketManager::buildBatchMove(
+            $this->bucketName,
+            array($key => $key2),
+            $this->bucketName,
+            true
+        );
+        list($ret, $error) = $this->bucketManager->batch($ops);
+        $this->assertEquals(200, $ret[0]['code']);
+        $error = $this->bucketManager->delete($this->bucketName, $key2);
+        $this->assertNull($error);
+    }
+
+    public function testBatchRename()
+    {
+        $key = 'rename' . rand();
+        $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+        $key2 = $key . 'to';
+        $ops = BucketManager::buildBatchRename($this->bucketName, array($key => $key2), true);
+        list($ret, $error) = $this->bucketManager->batch($ops);
+        $this->assertEquals(200, $ret[0]['code']);
+        $error = $this->bucketManager->delete($this->bucketName, $key2);
+        $this->assertNull($error);
+    }
+
+    public function testBatchStat()
+    {
+        $ops = BucketManager::buildBatchStat($this->bucketName, array('php-sdk.html'));
+        list($ret, $error) = $this->bucketManager->batch($ops);
+        $this->assertEquals(200, $ret[0]['code']);
+    }
+
+    public function testDeleteAfterDays()
+    {
+        $key = rand();
+        $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1);
+        $this->assertEquals(612, $err->code());
+
+        $this->bucketManager->copy($this->bucketName, $this->key, $this->bucketName, $key);
+        $err = $this->bucketManager->deleteAfterDays($this->bucketName, $key, 1);
+        $this->assertEquals(null, $err);
+    }
+}

+ 50 - 0
tests/Qiniu/Tests/CdnManagerTest.php

@@ -0,0 +1,50 @@
+<?php
+/**
+ * Created by IntelliJ IDEA.
+ * User: wf
+ * Date: 2017/6/21
+ * Time: AM8:46
+ */
+
+namespace Qiniu\Tests;
+
+use Qiniu\Cdn\CdnManager;
+use Qiniu\Http\Client;
+
+class CdnManagerTest extends \PHPUnit_Framework_TestCase
+{
+    protected $cdnManager;
+    protected $encryptKey;
+    protected $imgUrl;
+
+    protected function setUp()
+    {
+        global $timestampAntiLeechEncryptKey;
+        global $customDomain;
+        global $testAuth;
+
+        $this->cdnManager = new CdnManager($testAuth);
+        $this->encryptKey = $timestampAntiLeechEncryptKey;
+        $this->imgUrl = $customDomain . '/24.jpg';
+    }
+
+    public function testCreateTimestampAntiLeechUrl()
+    {
+
+        $signUrl = $this->cdnManager->createTimestampAntiLeechUrl($this->imgUrl, $this->encryptKey, 3600);
+
+        $response = Client::get($signUrl);
+        $this->assertEquals($response->statusCode, 200);
+        $this->assertNull($response->error);
+
+        $url2 = $this->imgUrl . '?imageInfo';
+        $signUrl2 = $this->cdnManager->createTimestampAntiLeechUrl($url2, $this->encryptKey, 3600);
+
+        $response = Client::get($signUrl2);
+        $imgInfo = $response->json();
+
+        $this->assertEquals($response->statusCode, 200);
+        $this->assertEquals($imgInfo['size'], 2196145);
+        $this->assertNull($response->error);
+    }
+}

+ 21 - 0
tests/Qiniu/Tests/Crc32Test.php

@@ -0,0 +1,21 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+
+class Crc32Test extends \PHPUnit_Framework_TestCase
+{
+    public function testData()
+    {
+        $a = '你好';
+        $b = \Qiniu\crc32_data($a);
+        $this->assertEquals('1352841281', $b);
+    }
+
+    public function testFile()
+    {
+        $b = \Qiniu\crc32_file(__file__);
+        $c = \Qiniu\crc32_file(__file__);
+        $this->assertEquals($c, $b);
+    }
+}

+ 25 - 0
tests/Qiniu/Tests/DownloadTest.php

@@ -0,0 +1,25 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Http\Client;
+
+class DownloadTest extends \PHPUnit_Framework_TestCase
+{
+    public function test()
+    {
+        global $testAuth;
+        $base_url = 'http://private-res.qiniudn.com/gogopher.jpg';
+        $private_url = $testAuth->privateDownloadUrl($base_url);
+        $response = Client::get($private_url);
+        $this->assertEquals(200, $response->statusCode);
+    }
+
+    public function testFop()
+    {
+        global $testAuth;
+        $base_url = 'http://private-res.qiniudn.com/gogopher.jpg?exif';
+        $private_url = $testAuth->privateDownloadUrl($base_url);
+        $response = Client::get($private_url);
+        $this->assertEquals(200, $response->statusCode);
+    }
+}

+ 52 - 0
tests/Qiniu/Tests/EtagTest.php

@@ -0,0 +1,52 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Etag;
+
+class EtagTest extends \PHPUnit_Framework_TestCase
+{
+    public function test0M()
+    {
+        $file = qiniuTempFile(0);
+        list($r, $error) = Etag::sum($file);
+        unlink($file);
+        $this->assertEquals('Fto5o-5ea0sNMlW_75VgGJCv2AcJ', $r);
+        $this->assertNull($error);
+    }
+
+    public function testLess4M()
+    {
+        $file = qiniuTempFile(3 * 1024 * 1024);
+        list($r, $error) = Etag::sum($file);
+        unlink($file);
+        $this->assertEquals('Fs5BpnAjRykYTg6o5E09cjuXrDkG', $r);
+        $this->assertNull($error);
+    }
+
+    public function test4M()
+    {
+        $file = qiniuTempFile(4 * 1024 * 1024);
+        list($r, $error) = Etag::sum($file);
+        unlink($file);
+        $this->assertEquals('FiuKULnybewpEnrfTmxjsxc-3dWp', $r);
+        $this->assertNull($error);
+    }
+
+    public function testMore4M()
+    {
+        $file = qiniuTempFile(5 * 1024 * 1024);
+        list($r, $error) = Etag::sum($file);
+        unlink($file);
+        $this->assertEquals('lhvyfIWMYFTq4s4alzlhXoAkqfVL', $r);
+        $this->assertNull($error);
+    }
+
+    public function test8M()
+    {
+        $file = qiniuTempFile(8 * 1024 * 1024);
+        list($r, $error) = Etag::sum($file);
+        unlink($file);
+        $this->assertEquals('lmRm9ZfGZ86bnMys4wRTWtJj9ClG', $r);
+        $this->assertNull($error);
+    }
+}

+ 37 - 0
tests/Qiniu/Tests/FopTest.php

@@ -0,0 +1,37 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Processing\Operation;
+use Qiniu\Processing\PersistentFop;
+
+class FopTest extends \PHPUnit_Framework_TestCase
+{
+    public function testExifPub()
+    {
+        $fop = new Operation('testres.qiniudn.com');
+        list($exif, $error) = $fop->execute('gogopher.jpg', 'exif');
+        $this->assertNull($error);
+        $this->assertNotNull($exif);
+    }
+
+    public function testExifPrivate()
+    {
+        global $testAuth;
+        $fop = new Operation('private-res.qiniudn.com', $testAuth);
+        list($exif, $error) = $fop->execute('noexif.jpg', 'exif');
+        $this->assertNotNull($error);
+        $this->assertNull($exif);
+    }
+
+    public function testbuildUrl()
+    {
+        $fops = 'imageView2/2/h/200';
+        $fop = new Operation('testres.qiniudn.com');
+        $url = $fop->buildUrl('gogopher.jpg', $fops);
+        $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200');
+
+        $fops = array('imageView2/2/h/200', 'imageInfo');
+        $url = $fop->buildUrl('gogopher.jpg', $fops);
+        $this->assertEquals($url, 'http://testres.qiniudn.com/gogopher.jpg?imageView2/2/h/200|imageInfo');
+    }
+}

+ 59 - 0
tests/Qiniu/Tests/FormUpTest.php

@@ -0,0 +1,59 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\FormUploader;
+use Qiniu\Storage\UploadManager;
+use Qiniu\Config;
+
+class FormUpTest extends \PHPUnit_Framework_TestCase
+{
+    protected $bucketName;
+    protected $auth;
+    protected $cfg;
+
+    protected function setUp()
+    {
+        global $bucketName;
+        $this->bucketName = $bucketName;
+
+        global $testAuth;
+        $this->auth = $testAuth;
+        $this->cfg = new Config();
+    }
+
+    public function testData()
+    {
+        $token = $this->auth->uploadToken($this->bucketName);
+        list($ret, $error) = FormUploader::put($token, 'formput', 'hello world', $this->cfg, null, 'text/plain', null);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+    }
+
+    public function testData2()
+    {
+        $upManager = new UploadManager();
+        $token = $this->auth->uploadToken($this->bucketName);
+        list($ret, $error) = $upManager->put($token, 'formput', 'hello world', null, 'text/plain', null);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+    }
+
+    public function testFile()
+    {
+        $key = 'formPutFile';
+        $token = $this->auth->uploadToken($this->bucketName, $key);
+        list($ret, $error) = FormUploader::putFile($token, $key, __file__, $this->cfg, null, 'text/plain', null);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+    }
+
+    public function testFile2()
+    {
+        $key = 'formPutFile';
+        $token = $this->auth->uploadToken($this->bucketName, $key);
+        $upManager = new UploadManager();
+        list($ret, $error) = $upManager->putFile($token, $key, __file__, null, 'text/plain', null);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+    }
+}

+ 43 - 0
tests/Qiniu/Tests/HttpTest.php

@@ -0,0 +1,43 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Http\Client;
+
+class HttpTest extends \PHPUnit_Framework_TestCase
+{
+    public function testGet()
+    {
+        $response = Client::get('baidu.com');
+        $this->assertEquals($response->statusCode, 200);
+        $this->assertNotNull($response->body);
+        $this->assertNull($response->error);
+    }
+
+    public function testGetQiniu()
+    {
+        $response = Client::get('up.qiniu.com');
+        $this->assertEquals(405, $response->statusCode);
+        $this->assertNotNull($response->body);
+        $this->assertNotNull($response->xReqId());
+        $this->assertNotNull($response->xLog());
+        $this->assertNotNull($response->error);
+    }
+
+    public function testPost()
+    {
+        $response = Client::post('baidu.com', null);
+        $this->assertEquals($response->statusCode, 200);
+        $this->assertNotNull($response->body);
+        $this->assertNull($response->error);
+    }
+
+    public function testPostQiniu()
+    {
+        $response = Client::post('up.qiniu.com', null);
+        $this->assertEquals($response->statusCode, 400);
+        $this->assertNotNull($response->body);
+        $this->assertNotNull($response->xReqId());
+        $this->assertNotNull($response->xLog());
+        $this->assertNotNull($response->error);
+    }
+}

+ 261 - 0
tests/Qiniu/Tests/ImageUrlBuilderTest.php

@@ -0,0 +1,261 @@
+<?php
+
+namespace Qiniu\Tests;
+
+/**
+ * imageprocess test
+ *
+ * @package Qiniu
+ * @subpackage test
+ * @author Sherlock Ren <sherlock_ren@icloud.com>
+ */
+class ImageUrlBuilderTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * 缩略图测试
+     *
+     * @test
+     * @return void
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function testThumbutl()
+    {
+        $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+        $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+        $url2 = $url . '?watermark/1/gravity/SouthEast/dx/0/dy/0/image/'
+            . 'aHR0cDovL2Fkcy1jZG4uY2h1Y2h1amllLmNvbS9Ga1R6bnpIY2RLdmRBUFc5cHZZZ3pTc21UY0tB';
+        // 异常测试
+        $this->assertEquals($url, $imageUrlBuilder->thumbnail($url, 1, 0, 0));
+        $this->assertEquals($url, \Qiniu\thumbnail($url, 1, 0, 0));
+
+        // 简单缩略测试
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/ignore-error/1/',
+            $imageUrlBuilder->thumbnail($url, 1, 200, 200)
+        );
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/ignore-error/1/',
+            \Qiniu\thumbnail($url, 1, 200, 200)
+        );
+
+        // 输出格式测试
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+            $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png')
+        );
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+            \Qiniu\thumbnail($url, 1, 200, 200, 'png')
+        );
+
+        // 渐进显示测试
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/',
+            $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1)
+        );
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/',
+            \Qiniu\thumbnail($url, 1, 200, 200, 'png', 2)
+        );
+
+        // 图片质量测试
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/q/80/ignore-error/1/',
+            $imageUrlBuilder->thumbnail($url, 1, 200, 200, 'png', 1, 80)
+        );
+        $this->assertEquals(
+            $url . '?imageView2/1/w/200/h/200/format/png/interlace/1/ignore-error/1/',
+            \Qiniu\thumbnail($url, 1, 200, 200, 'png', 1, 101)
+        );
+
+        // 多参数测试
+        $this->assertEquals(
+            $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/',
+            $imageUrlBuilder->thumbnail($url2, 1, 200, 200)
+        );
+        $this->assertEquals(
+            $url2 . '|imageView2/1/w/200/h/200/ignore-error/1/',
+            \Qiniu\thumbnail($url2, 1, 200, 200)
+        );
+    }
+
+    /**
+     * 图片水印测试
+     *
+     * @test
+     * @param  void
+     * @return void
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function waterImgTest()
+    {
+        $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+        $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+        $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/';
+        $image = 'http://developer.qiniu.com/resource/logo-2.jpg';
+
+        // 水印简单测试
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            $imageUrlBuilder->waterImg($url, $image)
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/gravity/SouthEast/',
+            $imageUrlBuilder->waterImg($url, $image, 101)
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw==/',
+            $imageUrlBuilder->waterImg($url, $image, 101, 'sdfsd')
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterImg($url, $image)
+        );
+
+        // 横轴边距测试
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/dx/10/',
+            $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10)
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad')
+        );
+
+        // 纵轴边距测试
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/',
+            $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10)
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf')
+        );
+
+        // 自适应原图的短边比例测试
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/dx/10/dy/10/ws/0.5/',
+            $imageUrlBuilder->waterImg($url, $image, 100, 'SouthEast', 10, 10, 0.5)
+        );
+        $this->assertEquals(
+            $url . '?watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterImg($url, $image, 100, 'SouthEast', 'sad', 'asdf', 2)
+        );
+
+        // 多参数测试
+        $this->assertEquals(
+            $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            $imageUrlBuilder->waterImg($url2, $image)
+        );
+        $this->assertEquals(
+            $url2 . '|watermark/1/image/aHR0cDovL2RldmVsb3Blci5xaW5pdS5jb20vcmVzb3VyY2UvbG9nby0yLmpwZw=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterImg($url2, $image)
+        );
+    }
+
+    /**
+     * 文字水印测试
+     *
+     * @test
+     * @param  void
+     * @return void
+     * @author Sherlock Ren <sherlock_ren@icloud.com>
+     */
+    public function waterTextTest()
+    {
+        $imageUrlBuilder = new \Qiniu\Processing\ImageUrlBuilder();
+        $url = 'http://78re52.com1.z0.glb.clouddn.com/resource/gogopher.jpg';
+        $url2 = $url . '?imageView2/1/w/200/h/200/format/png/ignore-error/1/';
+        $text = '测试一下';
+        $font = '微软雅黑';
+        $fontColor = '#FF0000';
+
+        // 水印简单测试
+        $this->assertEquals($url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+            . 'fontsize/500/dissolve/100/gravity/SouthEast/', $imageUrlBuilder->waterText($url, $text, $font, 500));
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+            . 'dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterText($url, $text, $font, 'sdf')
+        );
+
+        // 字体颜色测试
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/'
+            . 'I0ZGMDAwMA==/dissolve/100/gravity/SouthEast/',
+            $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor)
+        );
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA=='
+            . '/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor)
+        );
+
+        // 透明度测试
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+            . '/dissolve/80/gravity/SouthEast/',
+            $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80)
+        );
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA=='
+            . '/gravity/SouthEast/',
+            \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101)
+        );
+
+        // 水印位置测试
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+            . '/dissolve/80/gravity/East/',
+            $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East')
+        );
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+            \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf')
+        );
+
+        // 横轴距离测试
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+            . '/dissolve/80/gravity/East/dx/10/',
+            $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10)
+        );
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+            \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs')
+        );
+
+        // 纵轴距离测试
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fontsize/500/fill/I0ZGMDAwMA=='
+            . '/dissolve/80/gravity/East/dx/10/dy/10/',
+            $imageUrlBuilder->waterText($url, $text, $font, 500, $fontColor, 80, 'East', 10, 10)
+        );
+        $this->assertEquals(
+            $url . '?watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/fill/I0ZGMDAwMA==/',
+            \Qiniu\waterText($url, $text, $font, 'sdf', $fontColor, 101, 'sdfsdf', 'sdfs', 'ssdf')
+        );
+        // 多参数测试
+        $this->assertEquals(
+            $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+            . 'fontsize/500/dissolve/100/gravity/SouthEast/',
+            $imageUrlBuilder->waterText($url2, $text, $font, 500)
+        );
+        $this->assertEquals(
+            $url2 . '|watermark/2/text/5rWL6K-V5LiA5LiL/font/5b6u6L2v6ZuF6buR/'
+            . 'fontsize/500/dissolve/100/gravity/SouthEast/',
+            \Qiniu\waterText($url2, $text, $font, 500)
+        );
+    }
+}

+ 66 - 0
tests/Qiniu/Tests/PfopTest.php

@@ -0,0 +1,66 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Processing\Operation;
+use Qiniu\Processing\PersistentFop;
+
+class PfopTest extends \PHPUnit_Framework_TestCase
+{
+    public function testPfop()
+    {
+        global $testAuth;
+        $bucket = 'testres';
+        $key = 'sintel_trailer.mp4';
+        $pfop = new PersistentFop($testAuth, null);
+
+        $fops = 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240';
+        list($id, $error) = $pfop->execute($bucket, $key, $fops);
+        $this->assertNull($error);
+        list($status, $error) = $pfop->status($id);
+        $this->assertNotNull($status);
+        $this->assertNull($error);
+    }
+
+
+    public function testPfops()
+    {
+        global $testAuth;
+        $bucket = 'testres';
+        $key = 'sintel_trailer.mp4';
+        $fops = array(
+            'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240',
+            'vframe/jpg/offset/7/w/480/h/360',
+        );
+        $pfop = new PersistentFop($testAuth, null);
+
+        list($id, $error) = $pfop->execute($bucket, $key, $fops);
+        $this->assertNull($error);
+
+        list($status, $error) = $pfop->status($id);
+        $this->assertNotNull($status);
+        $this->assertNull($error);
+    }
+
+    public function testMkzip()
+    {
+        global $testAuth;
+        $bucket = 'phpsdk';
+        $key = 'php-logo.png';
+        $pfop = new PersistentFop($testAuth, null);
+
+        $url1 = 'http://phpsdk.qiniudn.com/php-logo.png';
+        $url2 = 'http://phpsdk.qiniudn.com/php-sdk.html';
+        $zipKey = 'test.zip';
+
+        $fops = 'mkzip/2/url/' . \Qiniu\base64_urlSafeEncode($url1);
+        $fops .= '/url/' . \Qiniu\base64_urlSafeEncode($url2);
+        $fops .= '|saveas/' . \Qiniu\base64_urlSafeEncode("$bucket:$zipKey");
+
+        list($id, $error) = $pfop->execute($bucket, $key, $fops);
+        $this->assertNull($error);
+
+        list($status, $error) = $pfop->status($id);
+        $this->assertNotNull($status);
+        $this->assertNull($error);
+    }
+}

+ 60 - 0
tests/Qiniu/Tests/ResumeUpTest.php

@@ -0,0 +1,60 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu\Storage\ResumeUploader;
+use Qiniu\Storage\UploadManager;
+use Qiniu\Config;
+use Qiniu\Zone;
+
+class ResumeUpTest extends \PHPUnit_Framework_TestCase
+{
+    protected $bucketName;
+    protected $auth;
+
+    protected function setUp()
+    {
+        global $bucketName;
+        $this->bucketName = $bucketName;
+
+        global $testAuth;
+        $this->auth = $testAuth;
+    }
+
+    public function test4ML()
+    {
+        $key = 'resumePutFile4ML';
+        $upManager = new UploadManager();
+        $token = $this->auth->uploadToken($this->bucketName, $key);
+        $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10);
+        list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+        unlink($tempFile);
+    }
+
+    public function test4ML2()
+    {
+        $key = 'resumePutFile4ML';
+        $zone = new Zone(array('up.fake.qiniu.com'), array('up.qiniup.com'));
+        $cfg = new Config($zone);
+        $upManager = new UploadManager($cfg);
+        $token = $this->auth->uploadToken($this->bucketName, $key);
+        $tempFile = qiniuTempFile(4 * 1024 * 1024 + 10);
+        list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+        $this->assertNull($error);
+        $this->assertNotNull($ret['hash']);
+        unlink($tempFile);
+    }
+
+    // public function test8M()
+    // {
+    //     $key = 'resumePutFile8M';
+    //     $upManager = new UploadManager();
+    //     $token = $this->auth->uploadToken($this->bucketName, $key);
+    //     $tempFile = qiniuTempFile(8*1024*1024+10);
+    //     list($ret, $error) = $upManager->putFile($token, $key, $tempFile);
+    //     $this->assertNull($error);
+    //     $this->assertNotNull($ret['hash']);
+    //     unlink($tempFile);
+    // }
+}

+ 59 - 0
tests/Qiniu/Tests/ZoneTest.php

@@ -0,0 +1,59 @@
+<?php
+namespace Qiniu\Tests;
+
+use Qiniu;
+use Qiniu\Zone;
+
+class ZoneTest extends \PHPUnit_Framework_TestCase
+{
+    protected $zone;
+    protected $zoneHttps;
+    protected $ak;
+
+    protected $bucketName;
+    protected $bucketNameBC;
+    protected $bucketNameNA;
+
+
+    protected function setUp()
+    {
+        global $bucketName;
+        $this->bucketName = $bucketName;
+
+        global $bucketNameBC;
+        $this->bucketNameBC = $bucketNameBC;
+
+        global $bucketNameNA;
+        $this->bucketNameNA = $bucketNameNA;
+
+        global $accessKey;
+        $this->ak = $accessKey;
+
+        $this->zone = new Zone();
+        $this->zoneHttps = new Zone('https');
+    }
+
+    public function testUpHosts()
+    {
+        $zone = Zone::queryZone($this->ak, $this->bucketName);
+        $this->assertContains('upload.qiniup.com', $zone->cdnUpHosts);
+
+        $zone = Zone::queryZone($this->ak, $this->bucketNameBC);
+        $this->assertContains('upload-z1.qiniup.com', $zone->cdnUpHosts);
+
+        $zone = Zone::queryZone($this->ak, $this->bucketNameNA);
+        $this->assertContains('upload-na0.qiniup.com', $zone->cdnUpHosts);
+    }
+
+    public function testIoHosts()
+    {
+        $zone = Zone::queryZone($this->ak, $this->bucketName);
+        $this->assertEquals($zone->iovipHost, 'iovip.qbox.me');
+
+        $zone = Zone::queryZone($this->ak, $this->bucketNameBC);
+        $this->assertEquals($zone->iovipHost, 'iovip-z1.qbox.me');
+
+        $zone = Zone::queryZone($this->ak, $this->bucketNameNA);
+        $this->assertEquals($zone->iovipHost, 'iovip-na0.qbox.me');
+    }
+}

+ 43 - 0
tests/bootstrap.php

@@ -0,0 +1,43 @@
+<?php
+// @codingStandardsIgnoreFile
+require_once __DIR__ . '/../autoload.php';
+
+use Qiniu\Auth;
+
+$accessKey = getenv('QINIU_ACCESS_KEY');
+$secretKey = getenv('QINIU_SECRET_KEY');
+$testAuth = new Auth($accessKey, $secretKey);
+
+$bucketName = 'phpsdk';
+$key = 'php-logo.png';
+$key2 = 'niu.jpg';
+
+$bucketNameBC = 'phpsdk-bc';
+$bucketNameNA = 'phpsdk-na';
+
+$dummyAccessKey = 'abcdefghklmnopq';
+$dummySecretKey = '1234567890';
+$dummyAuth = new Auth($dummyAccessKey, $dummySecretKey);
+
+//cdn
+$timestampAntiLeechEncryptKey = getenv('QINIU_TIMESTAMP_ENCRPTKEY');
+$customDomain = "http://phpsdk.qiniuts.com";
+
+$tid = getenv('TRAVIS_JOB_NUMBER');
+if (!empty($tid)) {
+    $pid = getmypid();
+    $tid = strstr($tid, '.');
+    $tid .= '.' . $pid;
+}
+
+function qiniuTempFile($size)
+{
+    $fileName = tempnam(sys_get_temp_dir(), 'qiniu_');
+    $file = fopen($fileName, 'wb');
+    if ($size > 0) {
+        fseek($file, $size - 1);
+        fwrite($file, ' ');
+    }
+    fclose($file);
+    return $fileName;
+}