diff --git a/CHANGELOG.md b/CHANGELOG.md index 0501b5aa..017acf0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 7.13.0 (2024-09-05) +* 对象存储,验证回调方法新增支持 Qiniu 签名 +* 对象存储,调整查询空间区域域名顺序与默认空间管理域名 +* 支持闲时任务配置 + ## 7.12.1 (2024-02-21) * 对象存储,添加上传策略部分字段 diff --git a/composer.json b/composer.json index 6ed0ab54..a2859f20 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,8 @@ ], "require": { "php": ">=5.3.3", + "ext-xml": "*", + "ext-curl": "*", "myclabs/php-enum": "~1.5.2 || ~1.6.6 || ~1.7.7 || ~1.8.4" }, "require-dev": { diff --git a/src/Qiniu/Auth.php b/src/Qiniu/Auth.php index 347595e4..89b625f4 100644 --- a/src/Qiniu/Auth.php +++ b/src/Qiniu/Auth.php @@ -1,4 +1,5 @@ sign($data), null); } - public function verifyCallback($contentType, $originAuthorization, $url, $body) - { - $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + public function verifyCallback( + $contentType, + $originAuthorization, + $url, + $body, + $method = "GET", + $headers = array() + ) { + if (strpos($originAuthorization, 'Qiniu') === 0) { + $qnHeaders = new Header($headers); + if (!isset($qnHeaders['Content-Type'])) { + $qnHeaders['Content-Type'] = $contentType; + } + list($sign, $err) = $this->signQiniuAuthorization( + $url, + $method, + $body, + $qnHeaders + ); + if ($err !== null) { + return false; + } + $authorization = 'Qiniu ' . $sign; + } else { + $authorization = 'QBox ' . $this->signRequest($url, $body, $contentType); + } return $originAuthorization === $authorization; } @@ -198,6 +222,7 @@ public function uploadToken($bucket, $key = null, $expires = 3600, $policy = nul 'persistentOps', 'persistentNotifyUrl', 'persistentPipeline', + 'persistentType', // 为 `1` 时开启闲时任务 'deleteAfterDays', 'fileType', diff --git a/src/Qiniu/Config.php b/src/Qiniu/Config.php index e4d597e0..0e1e67dd 100644 --- a/src/Qiniu/Config.php +++ b/src/Qiniu/Config.php @@ -4,15 +4,15 @@ final class Config { - const SDK_VER = '7.12.1'; + const SDK_VER = '7.13.0'; const BLOCK_SIZE = 4194304; //4*1024*1024 分块上传块大小,该参数为接口规格,不能修改 const RSF_HOST = 'rsf.qiniuapi.com'; const API_HOST = 'api.qiniuapi.com'; - const RS_HOST = 'rs.qiniuapi.com'; //RS Host - const UC_HOST = 'uc.qbox.me'; //UC Host - const QUERY_REGION_HOST = 'kodo-config.qiniuapi.com'; + const RS_HOST = 'rs.qiniuapi.com'; // RS Host + const UC_HOST = 'uc.qiniuapi.com'; // UC Host + const QUERY_REGION_HOST = Config::UC_HOST; const RTCAPI_HOST = 'http://rtc.qiniuapi.com'; const ARGUS_HOST = 'ai.qiniuapi.com'; const CASTER_HOST = 'pili-caster.qiniuapi.com'; @@ -50,8 +50,8 @@ public function __construct(Region $z = null) $this->ucHost = Config::UC_HOST; $this->queryRegionHost = Config::QUERY_REGION_HOST; $this->backupQueryRegionHosts = array( + "kodo-config.qiniuapi.com", "uc.qbox.me", - "api.qiniu.com" ); $this->backupUcHostsRetryTimes = 2; } diff --git a/src/Qiniu/Http/Header.php b/src/Qiniu/Http/Header.php index acea1db6..1dcf328e 100644 --- a/src/Qiniu/Http/Header.php +++ b/src/Qiniu/Http/Header.php @@ -18,8 +18,18 @@ public function __construct($obj = array()) foreach ($obj as $key => $values) { $normalizedKey = self::normalizeKey($key); $normalizedValues = array(); - foreach ($values as $value) { - array_push($normalizedValues, self::normalizeValue($value)); + if (!is_array($values)) { + array_push( + $normalizedValues, + self::normalizeValue($values) + ); + } else { + foreach ($values as $value) { + array_push( + $normalizedValues, + self::normalizeValue($value) + ); + } } $this->data[$normalizedKey] = $normalizedValues; } diff --git a/src/Qiniu/Processing/PersistentFop.php b/src/Qiniu/Processing/PersistentFop.php index 97a4ff49..3d831561 100644 --- a/src/Qiniu/Processing/PersistentFop.php +++ b/src/Qiniu/Processing/PersistentFop.php @@ -6,6 +6,7 @@ use Qiniu\Http\Error; use Qiniu\Http\Client; use Qiniu\Http\Proxy; +use Qiniu\Zone; /** * 持久化处理类,该类用于主动触发异步持久化操作. @@ -45,25 +46,34 @@ public function __construct($auth, $config = null, $proxy = null, $proxy_auth = * 对资源文件进行异步持久化处理 * @param string $bucket 资源所在空间 * @param string $key 待处理的源文件 - * @param string $fops string|array 待处理的pfop操作,多个pfop操作以array的形式传入。 + * @param string|array $fops 待处理的pfop操作,多个pfop操作以array的形式传入。 * eg. avthumb/mp3/ab/192k, vframe/jpg/offset/7/w/480/h/360 * @param string $pipeline 资源处理队列 * @param string $notify_url 处理结果通知地址 * @param bool $force 是否强制执行一次新的指令 + * @param int $type 为 `1` 时开启闲时任务 * * - * @return array 返回持久化处理的persistentId, 和返回的错误。 + * @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) - { + public function execute( + $bucket, + $key, + $fops, + $pipeline = null, + $notify_url = null, + $force = false, + $type = null + ) { 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); + \Qiniu\setWithoutEmpty($params, 'type', $type); if ($force) { $params['force'] = 1; } @@ -72,7 +82,8 @@ public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = nu if ($this->config->useHTTPS === true) { $scheme = "https://"; } - $url = $scheme . Config::API_HOST . '/pfop/'; + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . '/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, $this->proxy->makeReqOpt()); @@ -84,6 +95,10 @@ public function execute($bucket, $key, $fops, $pipeline = null, $notify_url = nu return array($id, null); } + /** + * @param string $id + * @return array 返回任务状态与可能出现的错误 + */ public function status($id) { $scheme = "http://"; @@ -91,11 +106,22 @@ public function status($id) if ($this->config->useHTTPS === true) { $scheme = "https://"; } - $url = $scheme . Config::API_HOST . "/status/get/prefop?id=$id"; + $apiHost = $this->getApiHost(); + $url = $scheme . $apiHost . "/status/get/prefop?id=$id"; $response = Client::get($url, array(), $this->proxy->makeReqOpt()); if (!$response->ok()) { return array(null, new Error($url, $response)); } return array($response->json(), null); } + + private function getApiHost() + { + if (!empty($this->config->zone) && !empty($this->config->zone->apiHost)) { + $apiHost = $this->config->zone->apiHost; + } else { + $apiHost = Config::API_HOST; + } + return $apiHost; + } } diff --git a/tests/Qiniu/Tests/AuthTest.php b/tests/Qiniu/Tests/AuthTest.php index bd1705f4..99aec858 100755 --- a/tests/Qiniu/Tests/AuthTest.php +++ b/tests/Qiniu/Tests/AuthTest.php @@ -236,5 +236,61 @@ public function testDisableQiniuTimestampSignatureEnvBeIgnored() $this->assertArrayHasKey("X-Qiniu-Date", $authedHeaders); putenv('DISABLE_QINIU_TIMESTAMP_SIGNATURE'); } + public function testQboxVerifyCallbackShouldOkWithRequiredOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123' + ); + $this->assertTrue($ok); + } + public function testQboxVerifyCallbackShouldOkWithOmitOptions() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'QBox abcdefghklmnopq:T7F-SjxX7X2zI4Fc1vANiNt1AUE=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', // this should be omit + array( + 'X-Qiniu-Bbb' => 'BBB' + ) // this should be omit + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldOk() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'GET', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertTrue($ok); + } + public function testQiniuVerifyCallbackShouldFailed() + { + $auth = new Auth('abcdefghklmnopq', '1234567890'); + $ok = $auth->verifyCallback( + 'application/x-www-form-urlencoded', + 'Qiniu abcdefghklmnopq:ZqS7EZuAKrhZaEIxqNGxDJi41IQ=', + 'https://test.qiniu.com/callback', + 'name=sunflower.jpg&hash=Fn6qeQi4VDLQ347NiRm-RlQx_4O2&location=Shanghai&price=1500.00&uid=123', + 'POST', + array( + 'X-Qiniu-Bbb' => 'BBB' + ) + ); + $this->assertFalse($ok); + } } } diff --git a/tests/Qiniu/Tests/ConfigTest.php b/tests/Qiniu/Tests/ConfigTest.php index 9910c9b5..3c39a5c2 100644 --- a/tests/Qiniu/Tests/ConfigTest.php +++ b/tests/Qiniu/Tests/ConfigTest.php @@ -66,13 +66,13 @@ public function testGetApiHostV2Errored() public function testSetUcHost() { $conf = new Config(); - $this->assertEquals("http://uc.qbox.me", $conf->getUcHost()); + $this->assertEquals('http://' . Config::UC_HOST, $conf->getUcHost()); $conf->setUcHost("uc.example.com"); $this->assertEquals("http://uc.example.com", $conf->getUcHost()); $conf = new Config(); $conf->useHTTPS = true; - $this->assertEquals("https://uc.qbox.me", $conf->getUcHost()); + $this->assertEquals('https://' . Config::UC_HOST, $conf->getUcHost()); $conf->setUcHost("uc.example.com"); $this->assertEquals("https://uc.example.com", $conf->getUcHost()); } @@ -94,7 +94,7 @@ public function testGetRegionWithBackupDomains() "fake-uc.phpsdk.qiniu.com", array( "unavailable-uc.phpsdk.qiniu.com", - "uc.qbox.me" // real uc + Config::UC_HOST // real uc ) ); list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); @@ -108,7 +108,7 @@ public function testGetRegionWithUcAndBackupDomains() $conf->setBackupQueryRegionHosts( array( "unavailable-uc.phpsdk.qiniu.com", - "uc.qbox.me" // real uc + Config::UC_HOST // real uc ) ); list(, $err) = $conf->getRsHostV2($this->accessKey, $this->bucketName); diff --git a/tests/Qiniu/Tests/MiddlewareTest.php b/tests/Qiniu/Tests/MiddlewareTest.php index 819b8eaa..969cad4f 100644 --- a/tests/Qiniu/Tests/MiddlewareTest.php +++ b/tests/Qiniu/Tests/MiddlewareTest.php @@ -52,7 +52,7 @@ public function testSendWithMiddleware() $request = new Request( "GET", - "https://qiniu.com/index.html", + "http://localhost:9000/ok.php", array(), null, $reqOpt @@ -79,7 +79,7 @@ public function testSendWithRetryDomains() new Middleware\RetryDomainsMiddleware( array( "unavailable.phpsdk.qiniu.com", - "qiniu.com", + "localhost:9000", ), 3 ), @@ -88,7 +88,7 @@ public function testSendWithRetryDomains() $request = new Request( "GET", - "https://fake.phpsdk.qiniu.com/index.html", + "http://fake.phpsdk.qiniu.com/ok.php", array(), null, $reqOpt @@ -130,7 +130,7 @@ public function testSendFailFastWithRetryDomains() new Middleware\RetryDomainsMiddleware( array( "unavailable.phpsdk.qiniu.com", - "qiniu.com", + "localhost:9000", ), 3, function () { @@ -142,7 +142,7 @@ function () { $request = new Request( "GET", - "https://fake.phpsdk.qiniu.com/index.html", + "http://fake.phpsdk.qiniu.com/ok.php", array(), null, $reqOpt diff --git a/tests/Qiniu/Tests/PfopTest.php b/tests/Qiniu/Tests/PfopTest.php index 19bacc00..1d10ac78 100755 --- a/tests/Qiniu/Tests/PfopTest.php +++ b/tests/Qiniu/Tests/PfopTest.php @@ -3,17 +3,26 @@ use PHPUnit\Framework\TestCase; -use Qiniu\Processing\Operation; use Qiniu\Processing\PersistentFop; +use Qiniu\Storage\UploadManager; +use Qiniu\Region; +use Qiniu\Config; class PfopTest extends TestCase { - public function testPfop() + private static function getConfig() + { + // use this func to test in test env + // `null` means to use production env + return null; + } + + public function testPfopExecuteAndStatusWithSingleFop() { global $testAuth; $bucket = 'testres'; $key = 'sintel_trailer.mp4'; - $pfop = new PersistentFop($testAuth, null); + $pfop = new PersistentFop($testAuth, self::getConfig()); $fops = 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240'; list($id, $error) = $pfop->execute($bucket, $key, $fops); @@ -24,7 +33,7 @@ public function testPfop() } - public function testPfops() + public function testPfopExecuteAndStatusWithMultipleFops() { global $testAuth; $bucket = 'testres'; @@ -33,7 +42,7 @@ public function testPfops() 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240', 'vframe/jpg/offset/7/w/480/h/360', ); - $pfop = new PersistentFop($testAuth, null); + $pfop = new PersistentFop($testAuth, self::getConfig()); list($id, $error) = $pfop->execute($bucket, $key, $fops); $this->assertNull($error); @@ -43,6 +52,123 @@ public function testPfops() $this->assertNull($error); } + private function pfopTypeTestData() + { + return array( + array( + 'type' => null + ), + array( + 'type' => -1 + ), + array( + 'type' => 0 + ), + array( + 'type' => 1 + ), + array( + 'type' => 2 + ) + ); + } + + public function testPfopWithIdleTimeType() + { + global $testAuth; + + $bucket = 'testres'; + $key = 'sintel_trailer.mp4'; + $persistentEntry = \Qiniu\entry($bucket, 'test-pfop-type_1'); + $fops = 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240|saveas/' . $persistentEntry; + $pfop = new PersistentFop($testAuth, self::getConfig()); + + $testCases = $this->pfopTypeTestData(); + + foreach ($testCases as $testCase) { + list($id, $error) = $pfop->execute( + $bucket, + $key, + $fops, + null, + null, + false, + $testCase['type'] + ); + + if (in_array($testCase['type'], array(null, 0, 1))) { + $this->assertNull($error); + list($status, $error) = $pfop->status($id); + $this->assertNotNull($status); + $this->assertNull($error); + if ($testCase['type'] == 1) { + $this->assertEquals(1, $status['type']); + } + $this->assertNotEmpty($status['creationDate']); + } else { + $this->assertNotNull($error); + } + } + } + + + public function testPfopByUploadPolicy() + { + global $testAuth; + $bucket = 'testres'; + $key = 'sintel_trailer.mp4'; + $persistentEntry = \Qiniu\entry($bucket, 'test-pfop-type_1'); + $fops = 'avthumb/m3u8/segtime/10/vcodec/libx264/s/320x240|saveas/' . $persistentEntry; + + $testCases = $this->pfopTypeTestData(); + + foreach ($testCases as $testCase) { + $putPolicy = array( + 'persistentOps' => $fops, + 'persistentType' => $testCase['type'] + ); + + if ($testCase['type'] == null) { + unset($putPolicy['persistentType']); + } + + $token = $testAuth->uploadToken( + $bucket, + $key, + 3600, + $putPolicy + ); + $upManager = new UploadManager(self::getConfig()); + list($ret, $error) = $upManager->putFile( + $token, + $key, + __file__, + null, + 'text/plain', + true + ); + + if (in_array($testCase['type'], array(null, 0, 1))) { + $this->assertNull($error); + $this->assertNotEmpty($ret['persistentId']); + $id = $ret['persistentId']; + } else { + $this->assertNotNull($error); + return; + } + + $pfop = new PersistentFop($testAuth, self::getConfig()); + list($status, $error) = $pfop->status($id); + + $this->assertNotNull($status); + $this->assertNull($error); + if ($testCase['type'] == 1) { + $this->assertEquals(1, $status['type']); + } + $this->assertNotEmpty($status['creationDate']); + } + } + public function testMkzip() { global $testAuth;