├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── config
└── rocketmq.php
├── phpunit.xml.dist
├── src
├── LaravelQueueRocketMQServiceProvider.php
└── Queue
│ ├── Connectors
│ └── RocketMQConnector.php
│ ├── Contracts
│ └── PlainPayload.php
│ ├── Jobs
│ └── RocketMQJob.php
│ └── RocketMQQueue.php
└── tests
└── RocketMQQueueTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /composer.lock
3 | /vendor
4 | .idea
5 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | # external_code_coverage: true
3 | php_mess_detector: true
4 | php_code_sniffer: true
5 | sensiolabs_security_checker: true
6 | # php_code_coverage: true
7 | php_pdepend: true
8 | php_loc:
9 | enabled: true
10 | excluded_dirs: [vendor, tests]
11 | checks:
12 | php:
13 | code_rating: true
14 | duplication: true
15 | filter:
16 | excluded_paths:
17 | - 'tests/*'
18 | build:
19 | tests:
20 | override:
21 | -
22 | command: vendor/bin/phpunit --coverage-clover my-coverage-file
23 | coverage:
24 | file: my-coverage-file
25 | format: php-clover
26 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.0
5 | - 7.1
6 | - 7.2
7 |
8 | sudo: false
9 |
10 | install: travis_retry composer install --no-interaction --prefer-source
11 |
12 | script: vendor/bin/phpunit --verbose
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 freyo
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
32 |
33 | ## Installation
34 |
35 | ```shell
36 | composer require freyo/laravel-queue-rocketmq
37 | ```
38 |
39 | ## Configure
40 |
41 | **Laravel 5.5+ uses Package Auto-Discovery, so doesn't require you to manually add the ServiceProvider.**
42 |
43 | 1. `config/app.php`:
44 |
45 | ```php
46 | 'providers' => [
47 | // ...
48 | Freyo\LaravelQueueRocketMQ\LaravelQueueRocketMQServiceProvider::class,
49 | ]
50 | ```
51 |
52 | 2. `.env`:
53 |
54 | ```
55 | QUEUE_DRIVER=rocketmq
56 |
57 | ROCKETMQ_ACCESS_KEY=your-access-key
58 | ROCKETMQ_ACCESS_ID=your-access-id
59 |
60 | ROCKETMQ_ENDPOINT=http://***.mqrest.***.aliyuncs.com
61 | ROCKETMQ_INSTANCE_ID=MQ_INST_***_***
62 | ROCKETMQ_GROUP_ID=GID_***
63 |
64 | ROCKETMQ_QUEUE=topic_name # default topic name
65 |
66 | ROCKETMQ_USE_MESSAGE_TAG=false # set to true to use message tag
67 | ROCKETMQ_WAIT_SECONDS=0
68 | ```
69 |
70 | ## Usage
71 |
72 | Once you completed the configuration you can use Laravel Queue API. If you used other queue drivers you do not need to change anything else. If you do not know how to use Queue API, please refer to the official Laravel documentation: http://laravel.com/docs/queues
73 |
74 | ### Example
75 |
76 | #### Dispatch Jobs
77 |
78 | The default connection name is `rocketmq`
79 |
80 | ```php
81 | // Without message tag (ROCKETMQ_USE_MESSAGE_TAG=false)
82 | Job::dispatch()->onConnection('connection-name')->onQueue('TopicTestMQ');
83 | // or dispatch((new Job())->onConnection('connection-name')->onQueue('TopicTestMQ'))
84 |
85 | // With message tag (ROCKETMQ_USE_MESSAGE_TAG=true)
86 | Job::dispatch()->onConnection('connection-name')->onQueue('TagA');
87 | // or dispatch((new Job())->onConnection('connection-name')->onQueue('TagA'))
88 | ```
89 |
90 | #### Multiple Queues
91 |
92 | Configure `config/queue.php`
93 |
94 | ```php
95 | 'connections' => [
96 | //...
97 | 'new-connection-name' => [
98 | 'driver' => 'rocketmq',
99 | 'access_key' => 'your-access-key',
100 | 'access_id' => 'your-access-id',
101 | 'endpoint' => 'http://***.mqrest.***.aliyuncs.com',
102 | 'instance_id' => 'MQ_INST_***_***',
103 | 'group_id' => 'GID_***',
104 | 'queue' => 'your-default-topic-name',
105 | 'use_message_tag' => false,
106 | 'wait_seconds' => 0,
107 | 'plain' => [
108 | 'enable' => false,
109 | 'job' => 'App\Jobs\RocketMQPlainJobHandler@handle',
110 | ],
111 | ];
112 | //...
113 | ];
114 | ```
115 |
116 | #### Process Jobs
117 |
118 | ```bash
119 | php artisan queue:work {connection-name} --queue={queue-name}
120 | ```
121 |
122 | #### Plain Mode
123 |
124 | Configure `.env`
125 |
126 | ```
127 | ROCKETMQ_PLAIN_ENABLE=true
128 | ROCKETMQ_PLAIN_JOB=App\Jobs\RocketMQPlainJob@handle
129 | ```
130 |
131 | Create a job implements `PlainPayload` interface. The method `getPayload` must return a sting value.
132 |
133 | ```php
134 | payload = $payload;
158 | }
159 |
160 | /**
161 | * Get the plain payload of the job.
162 | *
163 | * @return string
164 | */
165 | public function getPayload()
166 | {
167 | return $this->payload;
168 | }
169 | }
170 | ```
171 |
172 | Create a plain job handler
173 |
174 | ```php
175 | release();
198 |
199 | // delete message when processed.
200 | if (! $job->isDeletedOrReleased()) {
201 | $job->delete();
202 | }
203 | }
204 | }
205 | ```
206 |
207 | ## References
208 |
209 | - [Product Documentation](https://www.alibabacloud.com/product/mq)
210 |
211 | ## License
212 |
213 | The MIT License (MIT). Please see [License File](LICENSE) for more information.
214 |
215 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Ffreyo%2Flaravel-queue-rocketmq?ref=badge_large)
216 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "freyo/laravel-queue-rocketmq",
3 | "description": "Queue Adapter for AlibabaMQ(Apache RocketMQ) SDK",
4 | "keywords": ["laravel-queue", "rocketmq", "aliyun", "alibabamq"],
5 | "require": {
6 | "php": ">=5.6.4",
7 | "illuminate/queue": "5.2.*|5.3.*|5.4.*|5.5.*|5.6.*|5.7.*|5.8.*",
8 | "ext-json": "*",
9 | "ext-curl": "*",
10 | "aliyunmq/mq-http-sdk": "^1.0",
11 | "guzzlehttp/guzzle": "^6.0.0"
12 | },
13 | "require-dev": {
14 | "php": "^7.0",
15 | "phpunit/phpunit": ">=6.0.0",
16 | "vlucas/phpdotenv": "^3.3",
17 | "mockery/mockery": "^1.2.3"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "Freyo\\LaravelQueueRocketMQ\\": "src/"
22 | }
23 | },
24 | "autoload-dev": {
25 | "psr-4": {
26 | "Freyo\\LaravelQueueRocketMQ\\Tests\\": "tests"
27 | }
28 | },
29 | "license": "MIT",
30 | "authors": [
31 | {
32 | "name": "freyo",
33 | "email": "freyhsiao@gmail.com"
34 | }
35 | ],
36 | "minimum-stability": "dev",
37 | "extra": {
38 | "laravel": {
39 | "providers": [
40 | "Freyo\\LaravelQueueRocketMQ\\LaravelQueueRocketMQServiceProvider"
41 | ]
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/config/rocketmq.php:
--------------------------------------------------------------------------------
1 | 'rocketmq',
11 |
12 | 'access_key' => env('ROCKETMQ_ACCESS_KEY', 'your-access-key'),
13 | 'access_id' => env('ROCKETMQ_ACCESS_ID', 'your-access-id'),
14 |
15 | 'endpoint' => env('ROCKETMQ_ENDPOINT'),
16 | 'instance_id' => env('ROCKETMQ_INSTANCE_ID'),
17 | 'group_id' => env('ROCKETMQ_GROUP_ID'),
18 |
19 | 'queue' => env('ROCKETMQ_QUEUE', 'default'),
20 |
21 | 'use_message_tag' => env('ROCKETMQ_USE_MESSAGE_TAG', false),
22 | 'wait_seconds' => env('ROCKETMQ_WAIT_SECONDS', 0),
23 |
24 | 'plain' => [
25 | 'enable' => env('ROCKETMQ_PLAIN_ENABLE', false),
26 | 'job' => env('ROCKETMQ_PLAIN_JOB', 'App\Jobs\RocketMQPlainJobHandler@handle'),
27 | ],
28 |
29 | ];
30 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 | ./tests/
14 |
15 |
16 |
17 |
18 | src/
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/LaravelQueueRocketMQServiceProvider.php:
--------------------------------------------------------------------------------
1 | app instanceof LumenApplication) {
20 | $this->app->configure('queue');
21 | }
22 |
23 | $this->mergeConfigFrom(
24 | __DIR__.'/../config/rocketmq.php', 'queue.connections.rocketmq'
25 | );
26 | }
27 |
28 | /**
29 | * Register the application's event listeners.
30 | *
31 | * @return void
32 | */
33 | public function boot()
34 | {
35 | /** @var QueueManager $queue */
36 | $queue = $this->app['queue'];
37 |
38 | $queue->addConnector('rocketmq', function () {
39 | return new RocketMQConnector();
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Queue/Connectors/RocketMQConnector.php:
--------------------------------------------------------------------------------
1 | container = $container;
40 | $this->connection = $connection;
41 | $this->message = $message;
42 | $this->queue = $queue;
43 | $this->connectionName = $connectionName;
44 | }
45 |
46 | /**
47 | * Get the job identifier.
48 | *
49 | * @return string
50 | */
51 | public function getJobId()
52 | {
53 | return $this->message->getMessageId();
54 | }
55 |
56 | /**
57 | * Get the raw body of the job.
58 | *
59 | * @return string
60 | */
61 | public function getRawBody()
62 | {
63 | return $this->message->getMessageBody();
64 | }
65 |
66 | /**
67 | * Get the number of times the job has been attempted.
68 | *
69 | * @return int
70 | */
71 | public function attempts()
72 | {
73 | return $this->message->getConsumedTimes();
74 | }
75 |
76 | /**
77 | * Fire the job.
78 | *
79 | * @return void
80 | */
81 | public function fire()
82 | {
83 | method_exists($this, 'resolveAndFire')
84 | ? $this->resolveAndFire($this->payload())
85 | : parent::fire();
86 | }
87 |
88 | /**
89 | * Get the decoded body of the job.
90 | *
91 | * @return array
92 | */
93 | public function payload()
94 | {
95 | if ($this->connection->isPlain()) {
96 | $job = $this->connection->getPlainJob();
97 |
98 | return [
99 | 'displayName' => is_string($job) ? explode('@', $job)[0] : null,
100 | 'job' => $job,
101 | 'maxTries' => null,
102 | 'timeout' => null,
103 | 'data' => $this->getRawBody(),
104 | ];
105 | }
106 |
107 | return json_decode($this->getRawBody(), true);
108 | }
109 |
110 | /**
111 | * Delete the job from the queue.
112 | *
113 | * @return void
114 | */
115 | public function delete()
116 | {
117 | parent::delete();
118 |
119 | $this->connection
120 | ->getConsumer($this->getQueue())
121 | ->ackMessage([$this->message->getReceiptHandle()]);
122 | }
123 |
124 | /**
125 | * Release the job back into the queue.
126 | *
127 | * @param int $delay
128 | *
129 | * @return void
130 | */
131 | public function release($delay = 0)
132 | {
133 | parent::release($delay);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/Queue/RocketMQQueue.php:
--------------------------------------------------------------------------------
1 | client = $client;
41 | $this->config = $config;
42 |
43 | $this->createPayload = new \ReflectionMethod($this, 'createPayload');
44 | }
45 |
46 | /**
47 | * @return bool
48 | */
49 | public function isPlain()
50 | {
51 | return (bool)Arr::get($this->config, 'plain.enable');
52 | }
53 |
54 | /**
55 | * @return string
56 | */
57 | public function getPlainJob()
58 | {
59 | return Arr::get($this->config, 'plain.job');
60 | }
61 |
62 | /**
63 | * Get the size of the queue.
64 | *
65 | * @param string $queue
66 | *
67 | * @return int
68 | */
69 | public function size($queue = null)
70 | {
71 | return 1;
72 | }
73 |
74 | /**
75 | * Push a new job onto the queue.
76 | *
77 | * @param string|object $job
78 | * @param mixed $data
79 | * @param string $queue
80 | *
81 | * @throws \Exception
82 | *
83 | * @return mixed
84 | */
85 | public function push($job, $data = '', $queue = null)
86 | {
87 | if ($this->isPlain()) {
88 | return $this->pushRaw($job->getPayload(), $queue);
89 | }
90 |
91 | $payload = $this->createPayload->getNumberOfParameters() === 3
92 | ? $this->createPayload($job, $queue, $data) // version >= 5.7
93 | : $this->createPayload($job, $data);
94 |
95 | return $this->pushRaw($payload, $queue);
96 | }
97 |
98 | /**
99 | * Push a raw payload onto the queue.
100 | *
101 | * @param string $payload
102 | * @param string $queue
103 | * @param array $options
104 | *
105 | * @throws \Exception
106 | *
107 | * @return TopicMessage
108 | */
109 | public function pushRaw($payload, $queue = null, array $options = [])
110 | {
111 | $message = new TopicMessage($payload);
112 |
113 | if ($this->config['use_message_tag'] && $queue) {
114 | $message->setMessageTag($queue);
115 | }
116 |
117 | if ($delay = Arr::get($options, 'delay', 0)) {
118 | $message->setStartDeliverTime(time() * 1000 + $delay * 1000);
119 | }
120 |
121 | return $this->getProducer(
122 | $this->config['use_message_tag'] ? $this->config['queue'] : $queue
123 | )->publishMessage($message);
124 | }
125 |
126 | /**
127 | * Push a new job onto the queue after a delay.
128 | *
129 | * @param \DateTimeInterface|\DateInterval|int $delay
130 | * @param string|object $job
131 | * @param mixed $data
132 | * @param string $queue
133 | *
134 | * @throws \Exception
135 | *
136 | * @return mixed
137 | */
138 | public function later($delay, $job, $data = '', $queue = null)
139 | {
140 | $delay = method_exists($this, 'getSeconds')
141 | ? $this->getSeconds($delay)
142 | : $this->secondsUntil($delay);
143 |
144 | if ($this->isPlain()) {
145 | return $this->pushRaw($job->getPayload(), $queue, ['delay' => $delay]);
146 | }
147 |
148 | $payload = $this->createPayload->getNumberOfParameters() === 3
149 | ? $this->createPayload($job, $queue, $data) // version >= 5.7
150 | : $this->createPayload($job, $data);
151 |
152 | return $this->pushRaw($payload, $queue, ['delay' => $delay]);
153 | }
154 |
155 | /**
156 | * Pop the next job off of the queue.
157 | *
158 | * @param string $queue
159 | *
160 | * @return \Illuminate\Contracts\Queue\Job|null
161 | *
162 | * @throws \Exception
163 | */
164 | public function pop($queue = null)
165 | {
166 | try {
167 |
168 | $consumer = $this->config['use_message_tag']
169 | ? $this->getConsumer($this->config['queue'], $queue)
170 | : $this->getConsumer($queue);
171 |
172 | /** @var array $messages */
173 | $messages = $consumer->consumeMessage(1, $this->config['wait_seconds']);
174 |
175 | } catch (\Exception $e) {
176 | if ($e instanceof \MQ\Exception\MessageNotExistException) {
177 | return null;
178 | }
179 |
180 | throw $e;
181 | }
182 |
183 | return new RocketMQJob(
184 | $this->container ?: Container::getInstance(),
185 | $this,
186 | Arr::first($messages),
187 | $this->config['use_message_tag'] ? $this->config['queue'] : $queue,
188 | $this->connectionName ?? null
189 | );
190 | }
191 |
192 | /**
193 | * Get the consumer.
194 | *
195 | * @param string $topicName
196 | * @param string $messageTag
197 | *
198 | * @return \MQ\MQConsumer
199 | */
200 | public function getConsumer($topicName = null, $messageTag = null)
201 | {
202 | return $this->client->getConsumer(
203 | $this->config['instance_id'],
204 | $topicName ?: $this->config['queue'],
205 | $this->config['group_id'],
206 | $messageTag
207 | );
208 | }
209 |
210 | /**
211 | * Get the producer.
212 | *
213 | * @param string $topicName
214 | *
215 | * @return \MQ\MQProducer
216 | */
217 | public function getProducer($topicName = null)
218 | {
219 | return $this->client->getProducer(
220 | $this->config['instance_id'],
221 | $topicName ?: $this->config['queue']
222 | );
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/tests/RocketMQQueueTest.php:
--------------------------------------------------------------------------------
1 | 'rocketmq',
22 |
23 | 'access_key' => env('ROCKETMQ_ACCESS_KEY', 'your-access-key'),
24 | 'access_id' => env('ROCKETMQ_ACCESS_ID', 'your-access-id'),
25 |
26 | 'endpoint' => env('ROCKETMQ_ENDPOINT'),
27 | 'instance_id' => env('ROCKETMQ_INSTANCE_ID'),
28 | 'group_id' => env('ROCKETMQ_GROUP_ID'),
29 |
30 | 'queue' => env('ROCKETMQ_QUEUE', 'default'),
31 |
32 | 'use_message_tag' => env('ROCKETMQ_USE_MESSAGE_TAG', false),
33 | 'wait_seconds' => env('ROCKETMQ_WAIT_SECONDS', 0),
34 |
35 | 'plain' => [
36 | 'enable' => env('ROCKETMQ_PLAIN_ENABLE', false),
37 | 'job' => env('ROCKETMQ_PLAIN_JOB', 'App\Jobs\CMQPlainJob@handle'),
38 | ],
39 |
40 | ];
41 |
42 | $connector = new RocketMQConnector();
43 |
44 | $queue = $connector->connect($config);
45 |
46 | return [
47 | [$queue, $config],
48 | ];
49 | }
50 |
51 | /**
52 | * @dataProvider provider
53 | */
54 | public function testIsPlain(RocketMQQueue $queue, $config)
55 | {
56 | $this->assertSame($config['plain']['enable'], $queue->isPlain());
57 | }
58 |
59 | /**
60 | * @dataProvider provider
61 | */
62 | public function testGetPlainJob(RocketMQQueue $queue, $config)
63 | {
64 | $this->assertSame($config['plain']['job'], $queue->getPlainJob());
65 | }
66 |
67 | /**
68 | * @dataProvider provider
69 | */
70 | public function testSize(RocketMQQueue $queue, $config)
71 | {
72 | $this->assertGreaterThanOrEqual(1, $queue->size());
73 | }
74 |
75 | /**
76 | * @dataProvider provider
77 | */
78 | public function testPush(RocketMQQueue $queue, $config)
79 | {
80 | $queue = \Mockery::mock(RocketMQQueue::class)
81 | ->shouldAllowMockingProtectedMethods();
82 |
83 | $queue->expects()
84 | ->push('App\Jobs\RocketMQJob@handle')
85 | ->andReturn(new TopicMessage('MockMessageBody'));
86 |
87 | $this->assertInstanceOf(
88 | TopicMessage::class, $queue->push('App\Jobs\RocketMQJob@handle')
89 | );
90 | }
91 |
92 | /**
93 | * @dataProvider provider
94 | */
95 | public function testPushRaw(RocketMQQueue $queue, $config)
96 | {
97 | $queue = \Mockery::mock(RocketMQQueue::class)
98 | ->shouldAllowMockingProtectedMethods();
99 |
100 | $queue->expects()
101 | ->pushRaw('App\Jobs\RocketMQJob@handle')
102 | ->andReturn(new TopicMessage('MockMessageBody'));
103 |
104 | $this->assertInstanceOf(
105 | TopicMessage::class, $queue->pushRaw('App\Jobs\RocketMQJob@handle')
106 | );
107 | }
108 |
109 | /**
110 | * @dataProvider provider
111 | */
112 | public function testLater(RocketMQQueue $queue, $config)
113 | {
114 | $queue = \Mockery::mock(RocketMQQueue::class)
115 | ->shouldAllowMockingProtectedMethods();
116 |
117 | $queue->expects()
118 | ->later(0, 'App\Jobs\RocketMQJob@handle')
119 | ->andReturn(new TopicMessage('MockMessageBody'));
120 |
121 | $this->assertInstanceOf(
122 | TopicMessage::class, $queue->later(0, 'App\Jobs\RocketMQJob@handle')
123 | );
124 | }
125 |
126 | /**
127 | * @dataProvider provider
128 | */
129 | public function testPop(RocketMQQueue $queue, $config)
130 | {
131 | $client = \Mockery::mock(RocketMQQueue::class)
132 | ->shouldAllowMockingProtectedMethods();
133 |
134 | $client->expects()
135 | ->pop()
136 | ->andReturn(
137 | \Mockery::mock(RocketMQJob::class)
138 | );
139 |
140 | $this->assertInstanceOf(RocketMQJob::class, $client->pop());
141 | }
142 |
143 | /**
144 | * @dataProvider provider
145 | */
146 | public function testGetConsumer(RocketMQQueue $queue, $config)
147 | {
148 | $this->assertInstanceOf(MQConsumer::class, $queue->getConsumer());
149 | }
150 |
151 | /**
152 | * @dataProvider provider
153 | */
154 | public function testGetProducer(RocketMQQueue $queue, $config)
155 | {
156 | $this->assertInstanceOf(MQProducer::class, $queue->getProducer());
157 | }
158 | }
159 |
--------------------------------------------------------------------------------