├── .gitignore ├── src ├── Exception │ ├── ClassNotFoundException.php │ ├── SubClassException.php │ ├── InvalidResponseBodyException.php │ └── ServiceNotFoundException.php ├── Handler │ ├── HandlerInterface.php │ └── AbstractHandler.php ├── Util │ └── Time.php ├── Container │ ├── ContainerAwareTrait.php │ └── Container.php ├── Job.php ├── Process │ └── Worker.php └── DelayQueue.php ├── config.ini ├── phpunit.xml ├── demo ├── Handler │ └── OrderHandler.php └── demo.php ├── tests ├── Handler.php ├── JobTest.php └── DelayQueueTest.php ├── .travis.yml ├── package.json ├── composer.json ├── LICENSE ├── bin └── delayqueue-php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm 2 | .idea 3 | 4 | # composer 5 | composer.lock 6 | vendor 7 | 8 | # node 9 | node_modules -------------------------------------------------------------------------------- /src/Exception/ClassNotFoundException.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | -------------------------------------------------------------------------------- /demo/Handler/OrderHandler.php: -------------------------------------------------------------------------------- 1 | setTopic('order'); 11 | $job->setId('15702398321'); 12 | $job->setDelay(1 * Time::MINUTE); 13 | $job->setTtr(20 * Time::SECOND); 14 | $job->setBody([ 15 | 'uid' => 10829378, 16 | 'created' => 1498657365, 17 | ]); 18 | 19 | 20 | $delayQueue = new DelayQueue('http://127.0.0.1:9277'); 21 | $className = 'Demo\\Handler\\OrderHandler'; 22 | try { 23 | // 添加一个Job到延迟队列 24 | $delayQueue->push($className, $job); 25 | } catch (Exception $exception) { 26 | echo $exception->getMessage(); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/Container/ContainerAwareTrait.php: -------------------------------------------------------------------------------- 1 | container = $container; 27 | } 28 | 29 | public function setContainer(ContainerInterface $container) 30 | { 31 | $this->container = $container; 32 | } 33 | 34 | 35 | public function __get($id) 36 | { 37 | return $this->container->get($id); 38 | } 39 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "delayqueue-php", 3 | "version": "1.0.0", 4 | "description": "[延迟队列](https://github.com/ouqiang/delay-queue)PHP客户端", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ouqiang/delayqueue-php.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/ouqiang/delayqueue-php/issues" 21 | }, 22 | "homepage": "https://github.com/ouqiang/delayqueue-php#readme", 23 | "devDependencies": { 24 | "cz-conventional-changelog": "^2.0.0" 25 | }, 26 | "config": { 27 | "commitizen": { 28 | "path": "./node_modules/cz-conventional-changelog" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "start-point/delayqueue-php", 3 | "type": "library", 4 | "description": "PHP client for DelayQueue", 5 | "keywords": ["delayqueue","delayqueue-php"], 6 | "homepage": "https://github.com/ouqiang/delayqueue-php", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "ouqiang", 11 | "email": "qingqianludao@gmail.com", 12 | "homepage": "https://github.com/ouqiang" 13 | } 14 | ], 15 | "require": { 16 | "php": ">= 5.4", 17 | "ext-pcntl": "*", 18 | "monolog/monolog": "^1.0", 19 | "guzzlehttp/guzzle": "^5.0", 20 | "psr/container": "^1.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "4.5.*" 24 | }, 25 | "bin": [ 26 | "bin/delayqueue-php" 27 | ], 28 | "autoload": { 29 | "psr-4": { 30 | "DelayQueue\\": "src/" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "DelayQueue\\Tests\\": "tests/", 36 | "Demo\\Handler\\": "demo/Handler" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 qiang.ou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Handler/AbstractHandler.php: -------------------------------------------------------------------------------- 1 | id = $id; 34 | } 35 | 36 | /** 37 | * @param array $body 38 | */ 39 | public function setBody(array $body) 40 | { 41 | $this->body = $body; 42 | } 43 | 44 | public function run() 45 | { 46 | $this->setUp(); 47 | 48 | try { 49 | $this->perform(); 50 | $this->delayQueue->finish($this->id); 51 | } catch (Exception $exception) { 52 | $this->logger->warning(sprintf('Job execution failed %s', $exception->getMessage())); 53 | } 54 | 55 | $this->tearDown(); 56 | } 57 | 58 | protected function setUp() { } 59 | 60 | protected function tearDown() { } 61 | 62 | abstract protected function perform(); 63 | } -------------------------------------------------------------------------------- /tests/JobTest.php: -------------------------------------------------------------------------------- 1 | setTopic('order'); 16 | $job->setId('156236252625'); 17 | $job->setDelay(1 * Time::MINUTE); 18 | $job->setTtr(120 * Time::SECOND); 19 | $body = [ 20 | 'uid' => 12562, 21 | ]; 22 | $job->setBody($body); 23 | 24 | return [ 25 | [ 26 | $job, 27 | ], 28 | ]; 29 | } 30 | 31 | public function testAppendValueToBody() 32 | { 33 | $job = new Job(); 34 | $key = 'className'; 35 | $value = 'Test'; 36 | $job->appendValueToBody($key, $value); 37 | $this->assertArrayHasKey($key, $job->body); 38 | $this->assertEquals($value, $job->body[$key]); 39 | $job->setBody([]); 40 | } 41 | 42 | /** 43 | * @dataProvider providerJob 44 | */ 45 | public function testToArray(Job $job) 46 | { 47 | $arr = $job->toArray(); 48 | $this->assertArrayHasKey('id', $arr); 49 | $this->assertEquals('156236252625', $arr['id']); 50 | $this->assertArrayHasKey('topic', $arr); 51 | $this->assertEquals('order', $arr['topic']); 52 | $this->assertArrayHasKey('delay', $arr); 53 | $this->assertEquals(1 * Time::MINUTE, $arr['delay']); 54 | $this->assertArrayHasKey('ttr', $arr); 55 | $this->assertEquals(120 * Time::SECOND, $arr['ttr']); 56 | $this->assertArrayHasKey('body', $arr); 57 | $this->assertJsonStringEqualsJsonString(json_encode(['uid' => 12562]), $arr['body']); 58 | 59 | return $job; 60 | } 61 | 62 | /** 63 | * @dataProvider providerJob 64 | */ 65 | public function testJsonSerialize(Job $job) 66 | { 67 | $this->assertJsonStringEqualsJsonString(json_encode($job->toArray()), json_encode($job)); 68 | } 69 | } -------------------------------------------------------------------------------- /bin/delayqueue-php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | set('logger', function () { 57 | $logger = new Logger('delay-queue'); 58 | $logger->pushHandler( 59 | new StreamHandler( 60 | 'php://stdout', 61 | Logger::INFO, 62 | true, 63 | null, 64 | true) 65 | ); 66 | $logger->pushProcessor(new PsrLogMessageProcessor()); 67 | 68 | return $logger; 69 | }); 70 | 71 | $container->set('delayQueue', function () use($server, $pollingTimeout) { 72 | $delayQueue = new DelayQueue($server); 73 | $delayQueue->setTimeout($pollingTimeout); 74 | 75 | return $delayQueue; 76 | }); 77 | 78 | $topics = explode(',', $config['topic']); 79 | 80 | $worker = new Worker($container); 81 | $worker->setTopics($topics); 82 | $container['logger']->notice('Starting worker', 83 | [ 84 | 'pid' => getmypid(), 85 | 'topic' => $config['topic'], 86 | ] 87 | ); 88 | $worker->run(); -------------------------------------------------------------------------------- /src/Container/Container.php: -------------------------------------------------------------------------------- 1 | definitions[$id] = $callback; 34 | } 35 | } 36 | 37 | /** 38 | * 查找服务是否存在 39 | * 40 | * @param string $id 服务唯一标识 41 | * @return bool 42 | */ 43 | public function has($id) 44 | { 45 | return isset($this->definitions[$id]); 46 | } 47 | 48 | /** 49 | * 获取服务 50 | * 51 | * @param string $id 服务唯一标识 52 | * @return mixed 53 | * @throws ServiceNotFoundException 54 | */ 55 | public function get($id) 56 | { 57 | if (isset($this->instances[$id])) { 58 | return $this->instances[$id]; 59 | } 60 | 61 | if (!isset($this->definitions[$id])) { 62 | $message = sprintf('service [%s] not exists', $id); 63 | throw new ServiceNotFoundException($message); 64 | } 65 | 66 | /** @var Closure $callback */ 67 | $callback = $this->definitions[$id]; 68 | $callback = $callback->bindTo($this); 69 | 70 | $this->instances[$id] = $callback(); 71 | 72 | return $this->instances[$id]; 73 | } 74 | 75 | public function __get($name) 76 | { 77 | return $this->get($name); 78 | } 79 | 80 | public function __set($name, $value) 81 | { 82 | $this->set($name, $value); 83 | } 84 | 85 | public function offsetExists($offset) 86 | { 87 | return $this->has($offset); 88 | } 89 | 90 | public function offsetGet($offset) 91 | { 92 | return $this->get($offset); 93 | } 94 | 95 | public function offsetSet($offset, $value) 96 | { 97 | $this->set($offset, $value); 98 | } 99 | 100 | public function offsetUnset($offset) 101 | { 102 | unset($this->definitions[$offset]); 103 | unset($this->instances[$offset]); 104 | } 105 | } -------------------------------------------------------------------------------- /src/Job.php: -------------------------------------------------------------------------------- 1 | topic; 44 | } 45 | 46 | /** 47 | * @param string $topic 48 | */ 49 | public function setTopic($topic) 50 | { 51 | $this->topic = $topic; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getId() 58 | { 59 | return $this->id; 60 | } 61 | 62 | /** 63 | * @param string $id 64 | */ 65 | public function setId($id) 66 | { 67 | $this->id = $id; 68 | } 69 | 70 | /** 71 | * @return int 72 | */ 73 | public function getDelay() 74 | { 75 | return $this->delay; 76 | } 77 | 78 | /** 79 | * @param int $delay 80 | */ 81 | public function setDelay($delay) 82 | { 83 | $this->delay = $delay; 84 | } 85 | 86 | /** 87 | * @return int 88 | */ 89 | public function getTtr() 90 | { 91 | return $this->ttr; 92 | } 93 | 94 | /** 95 | * @param int $ttr 96 | */ 97 | public function setTtr($ttr) 98 | { 99 | $this->ttr = $ttr; 100 | } 101 | 102 | /** 103 | * @return array 104 | */ 105 | public function getBody() 106 | { 107 | return $this->body; 108 | } 109 | 110 | /** 111 | * @param array $body 112 | */ 113 | public function setBody(array $body) 114 | { 115 | $this->body = $body; 116 | } 117 | 118 | /** 119 | * @param string $key 120 | * @param mixed $value 121 | */ 122 | public function appendValueToBody($key, $value) 123 | { 124 | $this->body[$key] = $value; 125 | } 126 | 127 | /** 128 | * @return array 129 | */ 130 | public function toArray() 131 | { 132 | $arr = (array) $this; 133 | $arr['body'] = json_encode($arr['body']); 134 | 135 | return $arr; 136 | } 137 | 138 | /** 139 | * @return array 140 | */ 141 | public function jsonSerialize() 142 | { 143 | return $this->toArray(); 144 | } 145 | } -------------------------------------------------------------------------------- /src/Process/Worker.php: -------------------------------------------------------------------------------- 1 | topics = $topics; 28 | } 29 | 30 | 31 | public function run() 32 | { 33 | $this->registerSignalHandlers(); 34 | while(true) { 35 | if ($this->shutdown) { 36 | break; 37 | } 38 | $data = null; 39 | try { 40 | $data = $this->delayQueue->pop($this->topics); 41 | } catch (Exception $exception) { 42 | $this->logger->warning(sprintf('polling queue exception: %s', $exception->getMessage())); 43 | continue; 44 | } 45 | 46 | if (!$data) { 47 | // 空轮询 48 | continue; 49 | } 50 | 51 | try { 52 | $this->delayQueue->validateClassName($data['className']); 53 | } catch(Exception $exception) { 54 | $this->logger->emergency($exception->getMessage()); 55 | continue; 56 | } 57 | 58 | $this->perform($data); 59 | } 60 | } 61 | 62 | protected function perform(array $data) 63 | { 64 | $pid = pcntl_fork(); 65 | if ($pid< 0) { 66 | $this->logger->emergency('Unable to fork child worker', ['job' => $data]); 67 | return; 68 | } 69 | if ($pid === 0) { 70 | // 子进程 71 | /** @var AbstractHandler $class */ 72 | $class = new $data['className']($this->container); 73 | $class->setId($data['id']); 74 | $class->setBody($data['body']); 75 | $this->logger->info('Start processing Job', ['data' => $data]); 76 | $class->run(); 77 | $this->logger->info('Job finished', ['data' => $data]); 78 | exit(0); 79 | } 80 | // 父进程 81 | $status = null; 82 | pcntl_wait($status); 83 | $exitStatus = pcntl_wexitstatus($status); 84 | if ($exitStatus !== 0) { 85 | // 执行失败 86 | $this->logger->warning('Job exited with exit code ' . $exitStatus); 87 | } 88 | } 89 | 90 | /** 91 | * 注册信号处理 92 | */ 93 | protected function registerSignalHandlers() 94 | { 95 | pcntl_signal(SIGTERM, [$this, 'shutdown']); 96 | pcntl_signal(SIGINT , [$this, 'shutdown']); 97 | } 98 | 99 | /** 100 | * 无Job处理时退出 101 | */ 102 | public function shutdown() 103 | { 104 | $this->logger->notice('Shutting down'); 105 | $this->shutdown = true; 106 | } 107 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # delayqueue-php 2 | [延迟队列](https://github.com/ouqiang/delay-queue)PHP客户端 3 | 4 | [![Build Status](https://travis-ci.org/ouqiang/delayqueue-php.png)](https://travis-ci.org/ouqiang/delayqueue-php) 5 | [![Latest Stable Version](https://poser.pugx.org/start-point/delayqueue-php/version)](https://packagist.org/packages/start-point/delayqueue-php) 6 | [![Total Downloads](https://poser.pugx.org/start-point/delayqueue-php/downloads)](https://packagist.org/packages/start-point/delayqueue-php) 7 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/ouqiang/delayqueue-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/ouqiang/delayqueue-php/?branch=master) 8 | [![Latest Unstable Version](https://poser.pugx.org/start-point/delayqueue-php/v/unstable)](//packagist.org/packages/start-point/delayqueue-php) 9 | [![License](https://poser.pugx.org/start-point/delayqueue-php/license)](https://packagist.org/packages/start-point/delayqueue-php) 10 | [![composer.lock available](https://poser.pugx.org/start-point/delayqueue-php/composerlock)](https://packagist.org/packages/start-point/delayqueue-php) 11 | 12 | 依赖 13 | -------- 14 | * PHP5.4+ 15 | * ext-pcntl 16 | 17 | 安装 18 | ------------ 19 | ```shell 20 | composer require start-point/delayqueue-php 21 | ``` 22 | 23 | 使用 24 | ------------ 25 | 26 | ```php 27 | setTopic('order'); 37 | $job->setId('15702398321'); 38 | $job->setDelay(1 * Time::MINUTE); 39 | $job->setTtr(20 * Time::SECOND); 40 | $job->setBody([ 41 | 'uid' => 10829378, 42 | 'created' => 1498657365, 43 | ]); 44 | 45 | 46 | $delayQueue = new DelayQueue('http://127.0.0.1:9277'); 47 | // 处理Job的类名 48 | $className = 'Demo\\Handler\\OrderHandler'; 49 | try { 50 | // 添加一个Job到队列 51 | $delayQueue->push($className, $job); 52 | 53 | // 从队列中删除Job 54 | $delayQueue->delete('15702398321'); 55 | } catch (Exception $exception) { 56 | echo $exception->getMessage(); 57 | } 58 | ```` 59 | 60 | # 运行Worker处理Job 61 | 62 | 63 | ### 单个Worker 64 | 65 | ```shell 66 | php vendor/bin/delayqueue-php -c /path/to/config.ini 67 | ``` 68 | 69 | ### PHP实现多个Worker 70 | 71 | ```php 72 | #!/usr/bin/env php 73 | id; 124 | 125 | // Job自定义内容 126 | // $this->body; 127 | } 128 | } 129 | ``` 130 | -------------------------------------------------------------------------------- /tests/DelayQueueTest.php: -------------------------------------------------------------------------------- 1 | delayQueue = new DelayQueue('http://127.0.0.1:9277'); 27 | } 28 | 29 | public function providerJob() 30 | { 31 | $job = new Job(); 32 | $job->setTopic($this->topic); 33 | $job->setId($this->jobId); 34 | $job->setDelay(5 * Time::SECOND); 35 | $job->setTtr(60 * Time::SECOND); 36 | $job->setBody([ 37 | 'uid' => 10829378, 38 | 'created' => 1498657365, 39 | ]); 40 | 41 | return [ 42 | [ 43 | $job 44 | ] 45 | ]; 46 | } 47 | 48 | /** 49 | * @dataProvider providerJob 50 | * 51 | * @param Job $job 52 | * @throws \DelayQueue\Exception\ClassNotFoundException 53 | * @throws \DelayQueue\Exception\SubClassException 54 | */ 55 | public function testPushException(Job $job) 56 | { 57 | $this->setExpectedException('DelayQueue\Exception\ClassNotFoundException'); 58 | $this->delayQueue->push('testHandler', $job); 59 | 60 | $this->setExpectedException('DelayQueue\Exception\SubClassException'); 61 | $this->delayQueue->push('DelayQueue\Tests\DelayQueueTest', $job); 62 | } 63 | 64 | /** 65 | * @dataProvider providerJob 66 | * @param Job $job 67 | * @throws \DelayQueue\Exception\ClassNotFoundException 68 | * @throws \DelayQueue\Exception\SubClassException 69 | */ 70 | public function testPush(Job $job) 71 | { 72 | $className = '\\DelayQueue\\Tests\Handler'; 73 | $this->delayQueue->push($className, $job); 74 | sleep(3 * Time::SECOND); 75 | $this->delayQueue->delete($job->id); 76 | } 77 | 78 | /** 79 | * 80 | * @dataProvider providerJob 81 | * @param Job $Job 82 | * @throws \DelayQueue\Exception\ClassNotFoundException 83 | * @throws \DelayQueue\Exception\SubClassException 84 | */ 85 | public function testPop(Job $job) 86 | { 87 | $job->id = '56352695584'; 88 | $oldTimeout = $this->delayQueue->getTimeout(); 89 | $this->delayQueue->setTimeout(20); 90 | $className = '\\DelayQueue\\Tests\Handler'; 91 | $cloneJob = clone $job; 92 | $this->delayQueue->push($className, $cloneJob); 93 | $data = $this->delayQueue->pop([$this->topic]); 94 | $this->assertEquals($job->id, $data['id']); 95 | $this->assertEquals($className, $data['className']); 96 | $this->assertEquals($job->body, $data['body']); 97 | /** @var AbstractHandler $class */ 98 | $container = new Container(); 99 | $container->set('logger', function () { 100 | $logger = new Logger('delay-queue'); 101 | $logger->pushHandler( 102 | new StreamHandler( 103 | 'php://stdout', 104 | Logger::INFO, 105 | true, 106 | null, 107 | true) 108 | ); 109 | 110 | return $logger; 111 | }); 112 | $class = new $data['className']($container); 113 | $class->setId($data['id']); 114 | $class->setBody($data['body']); 115 | $class->run(); 116 | $this->delayQueue->finish($job->id); 117 | $this->delayQueue->setTimeout($oldTimeout); 118 | } 119 | } -------------------------------------------------------------------------------- /src/DelayQueue.php: -------------------------------------------------------------------------------- 1 | server = rtrim($server, '/'); 27 | } 28 | 29 | /** 30 | * @param int $timeout 31 | */ 32 | public function setTimeout($timeout) 33 | { 34 | $this->timeout = $timeout; 35 | } 36 | 37 | /** 38 | * @return int 39 | */ 40 | public function getTimeout() 41 | { 42 | return $this->timeout; 43 | } 44 | 45 | /** 46 | * 添加Job到延迟队列中 47 | * 48 | * @param string $className 处理Job的类名, 必须是[DelayQueue\Handler\AbstractHandler]的子类 49 | * @param Job $job 50 | * @throws ClassNotFoundException 51 | * @throws Exception 52 | * @throws InvalidResponseBodyException 53 | * @throws SubClassException 54 | */ 55 | public function push($className, Job $job) 56 | { 57 | $this->validateClassName($className); 58 | $job->appendValueToBody('className', $className); 59 | 60 | $response = $this->getHttpClient()->post('/push', [ 61 | 'json' => $job, 62 | ]); 63 | $this->checkResponseBody($response->json()); 64 | } 65 | 66 | /** 67 | * 从队列中取出已过期的Job 68 | * 69 | * @param array $topics 队列名称 70 | * @return null|array 71 | * @throws Exception 72 | * @throws InvalidResponseBodyException 73 | */ 74 | public function pop(array $topics) 75 | { 76 | if (!$topics) { 77 | return null; 78 | } 79 | $response = $this->getHttpClient()->post('/pop', [ 80 | 'json' => [ 81 | 'topic' => implode(',', $topics), 82 | ] 83 | ]); 84 | 85 | $data = $response->json(); 86 | $this->checkResponseBody($data); 87 | if (!isset($data['data']) || empty($data['data'])) { 88 | return null; 89 | } 90 | 91 | if (!isset($data['data']['id']) || !isset($data['data']['body'])) { 92 | throw new InvalidResponseBodyException('response body miss required parameter, id or body'); 93 | } 94 | $id = $data['data']['id']; 95 | $body = json_decode($data['data']['body'], true); 96 | if (!isset($body['className'])) { 97 | throw new InvalidResponseBodyException('response body miss required parameter className'); 98 | } 99 | $className = $body['className']; 100 | unset($body['className']); 101 | 102 | return [ 103 | 'className' => $className, 104 | 'id' => $id, 105 | 'body' => $body, 106 | ]; 107 | } 108 | 109 | /** 110 | * 从延迟队列中删除Job 111 | * 112 | * @param string $id Job唯一标识 113 | * @throws Exception 114 | * @throws InvalidResponseBodyException 115 | */ 116 | public function delete($id) 117 | { 118 | $response = $this->getHttpClient()->post('/delete', [ 119 | 'json' => [ 120 | 'id' => $id 121 | ] 122 | ]); 123 | $body = $response->json(); 124 | $this->checkResponseBody($body); 125 | } 126 | 127 | /** 128 | * Job处理完成, 确认删除 129 | * 130 | * @param string $id Job唯一标识 131 | * @return true 132 | * @throws Exception 133 | * @throws InvalidResponseBodyException 134 | */ 135 | public function finish($id) 136 | { 137 | $response = $this->getHttpClient()->post('/finish', [ 138 | 'json' => [ 139 | 'id' => $id, 140 | ] 141 | ]); 142 | $body = $response->json(); 143 | $this->checkResponseBody($body); 144 | } 145 | 146 | public function validateClassName($className) { 147 | if (!class_exists($className)) { 148 | throw new ClassNotFoundException(sprintf('can not find class [%s]', $className)); 149 | } 150 | $reflection = new ReflectionClass($className); 151 | $parentClassName = 'DelayQueue\Handler\AbstractHandler'; 152 | if (!$reflection->isSubclassOf($parentClassName)) { 153 | throw new SubClassException(sprintf('[%s] is not subclass of [%s]', $className, $parentClassName)); 154 | } 155 | } 156 | 157 | protected function getHttpClient() 158 | { 159 | $httpClient = new HttpClient( 160 | [ 161 | 'base_url' => $this->server, 162 | 'defaults' => [ 163 | 'timeout' => $this->timeout, 164 | 'allow_redirects' => false, 165 | ] 166 | ] 167 | ); 168 | 169 | return $httpClient; 170 | } 171 | 172 | /** 173 | * @param array $body 174 | * @throws Exception 175 | * @throws InvalidResponseBodyException 176 | */ 177 | protected function checkResponseBody(array $body) 178 | { 179 | if (!array_key_exists('code', $body) || !array_key_exists('message', $body)) { 180 | throw new InvalidResponseBodyException('response body miss required parameter, code or message'); 181 | } 182 | if ($body['code'] !== 0) { 183 | throw new Exception($body['message']); 184 | } 185 | } 186 | } --------------------------------------------------------------------------------