中秋节快乐
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use think\helper\Str;
|
||||
use think\queue\Connector;
|
||||
|
||||
/**
|
||||
* Class Queue
|
||||
* @package think\queue
|
||||
*
|
||||
* @method Connector driver($driver = null)
|
||||
*/
|
||||
class Queue extends Factory
|
||||
{
|
||||
protected $namespace = '\\think\\queue\\connector\\';
|
||||
|
||||
/**
|
||||
* Get the queue connector configuration.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
protected function getConfig($name)
|
||||
{
|
||||
return $this->app->config->get("queue.connectors.{$name}", ['driver' => 'sync']);
|
||||
}
|
||||
|
||||
protected function createDriver($name)
|
||||
{
|
||||
$driver = $this->getConfig($name)['driver'];
|
||||
|
||||
$class = false !== strpos($driver, '\\') ? $driver : $this->namespace . Str::studly($driver);
|
||||
|
||||
/** @var Connector $driver */
|
||||
if (class_exists($class)) {
|
||||
$driver = $this->app->invokeClass($class, [$this->getConfig($driver)]);
|
||||
|
||||
return $driver->setApp($this->app)
|
||||
->setConnectorName($name);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Driver [$driver] not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认驱动
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultDriver()
|
||||
{
|
||||
return $this->app->config->get('queue.default', 'sync');
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use think\facade\Queue;
|
||||
|
||||
if (!function_exists('queue')) {
|
||||
|
||||
/**
|
||||
* 添加到队列
|
||||
* @param $job
|
||||
* @param string $data
|
||||
* @param int $delay
|
||||
* @param null $queue
|
||||
*/
|
||||
function queue($job, $data = '', $delay = 0, $queue = null)
|
||||
{
|
||||
if ($delay > 0) {
|
||||
Queue::later($delay, $job, $data, $queue);
|
||||
} else {
|
||||
Queue::push($job, $data, $queue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
return [
|
||||
'default' => 'sync',
|
||||
'connectors' => [
|
||||
'sync' => [
|
||||
'driver' => 'sync',
|
||||
],
|
||||
'database' => [
|
||||
'driver' => 'database',
|
||||
'queue' => 'default',
|
||||
'table' => 'jobs',
|
||||
],
|
||||
'redis' => [
|
||||
'driver' => 'redis',
|
||||
'queue' => 'default',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'password' => '',
|
||||
'select' => 0,
|
||||
'timeout' => 0,
|
||||
'persistent' => false,
|
||||
],
|
||||
],
|
||||
'failed' => [
|
||||
'type' => 'none',
|
||||
'table' => 'failed_jobs',
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace think\facade;
|
||||
|
||||
use think\Facade;
|
||||
|
||||
/**
|
||||
* Class Queue
|
||||
* @package think\facade
|
||||
* @mixin \think\Queue
|
||||
*/
|
||||
class Queue extends Facade
|
||||
{
|
||||
protected static function getFacadeClass()
|
||||
{
|
||||
return 'queue';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
class CallQueuedHandler
|
||||
{
|
||||
|
||||
public function call(Job $job, array $data)
|
||||
{
|
||||
$command = unserialize($data['command']);
|
||||
|
||||
call_user_func([$command, 'handle']);
|
||||
|
||||
if (!$job->isDeletedOrReleased()) {
|
||||
$job->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(array $data)
|
||||
{
|
||||
$command = unserialize($data['command']);
|
||||
|
||||
if (method_exists($command, 'failed')) {
|
||||
$command->failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use DateTimeInterface;
|
||||
use InvalidArgumentException;
|
||||
use think\App;
|
||||
|
||||
abstract class Connector
|
||||
{
|
||||
/** @var App */
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* The connector name for the queue.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connectorName;
|
||||
|
||||
protected $options = [];
|
||||
|
||||
abstract public function size($queue);
|
||||
|
||||
abstract public function push($job, $data = '', $queue = null);
|
||||
|
||||
public function pushOn($queue, $job, $data = '')
|
||||
{
|
||||
return $this->push($job, $data, $queue);
|
||||
}
|
||||
|
||||
abstract public function pushRaw($payload, $queue = null, array $options = []);
|
||||
|
||||
abstract public function later($delay, $job, $data = '', $queue = null);
|
||||
|
||||
public function laterOn($queue, $delay, $job, $data = '')
|
||||
{
|
||||
return $this->later($delay, $job, $data, $queue);
|
||||
}
|
||||
|
||||
public function bulk($jobs, $data = '', $queue = null)
|
||||
{
|
||||
foreach ((array) $jobs as $job) {
|
||||
$this->push($job, $data, $queue);
|
||||
}
|
||||
}
|
||||
|
||||
abstract public function pop($queue = null);
|
||||
|
||||
protected function createPayload($job, $data = '')
|
||||
{
|
||||
$payload = $this->createPayloadArray($job, $data);
|
||||
|
||||
$payload = json_encode($payload);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
protected function createPayloadArray($job, $data = '')
|
||||
{
|
||||
return is_object($job)
|
||||
? $this->createObjectPayload($job)
|
||||
: $this->createPlainPayload($job, $data);
|
||||
}
|
||||
|
||||
protected function createPlainPayload($job, $data)
|
||||
{
|
||||
return [
|
||||
'job' => $job,
|
||||
'maxTries' => null,
|
||||
'timeout' => null,
|
||||
'data' => $data,
|
||||
];
|
||||
}
|
||||
|
||||
protected function createObjectPayload($job)
|
||||
{
|
||||
$payload = [
|
||||
'job' => 'think\queue\CallQueuedHandler@call',
|
||||
'maxTries' => $job->tries ?? null,
|
||||
'timeout' => $job->timeout ?? null,
|
||||
'timeoutAt' => $this->getJobExpiration($job),
|
||||
'data' => [
|
||||
'commandName' => $job,
|
||||
'command' => $job,
|
||||
],
|
||||
];
|
||||
|
||||
return array_merge($payload, [
|
||||
'data' => [
|
||||
'commandName' => get_class($job),
|
||||
'command' => serialize(clone $job),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function getJobExpiration($job)
|
||||
{
|
||||
if (!method_exists($job, 'retryUntil') && !isset($job->timeoutAt)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expiration = $job->timeoutAt ?? $job->retryUntil();
|
||||
|
||||
return $expiration instanceof DateTimeInterface
|
||||
? $expiration->getTimestamp() : $expiration;
|
||||
}
|
||||
|
||||
protected function setMeta($payload, $key, $value)
|
||||
{
|
||||
$payload = json_decode($payload, true);
|
||||
$payload[$key] = $value;
|
||||
$payload = json_encode($payload);
|
||||
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new InvalidArgumentException('Unable to create payload: ' . json_last_error_msg());
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
public function setApp(App $app)
|
||||
{
|
||||
$this->app = $app;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connector name for the queue.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnectorName()
|
||||
{
|
||||
return $this->connectorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connector name for the queue.
|
||||
*
|
||||
* @param string $name
|
||||
* @return $this
|
||||
*/
|
||||
public function setConnectorName($name)
|
||||
{
|
||||
$this->connectorName = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
abstract class FailedJob
|
||||
{
|
||||
/**
|
||||
* Log a failed job into storage.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param string $queue
|
||||
* @param string $payload
|
||||
* @param \Exception $exception
|
||||
* @return int|null
|
||||
*/
|
||||
abstract public function log($connection, $queue, $payload, $exception);
|
||||
|
||||
/**
|
||||
* Get a list of all of the failed jobs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract public function all();
|
||||
|
||||
/**
|
||||
* Get a single failed job.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return object|null
|
||||
*/
|
||||
abstract public function find($id);
|
||||
|
||||
/**
|
||||
* Delete a single failed job from storage.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return bool
|
||||
*/
|
||||
abstract public function forget($id);
|
||||
|
||||
/**
|
||||
* Flush all of the failed jobs from storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function flush();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateInterval;
|
||||
use DateTimeInterface;
|
||||
|
||||
trait InteractsWithTime
|
||||
{
|
||||
/**
|
||||
* Get the number of seconds until the given DateTime.
|
||||
*
|
||||
* @param DateTimeInterface|DateInterval|int $delay
|
||||
* @return int
|
||||
*/
|
||||
protected function secondsUntil($delay)
|
||||
{
|
||||
$delay = $this->parseDateInterval($delay);
|
||||
|
||||
return $delay instanceof DateTimeInterface
|
||||
? max(0, $delay->getTimestamp() - $this->currentTime())
|
||||
: (int) $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "available at" UNIX timestamp.
|
||||
*
|
||||
* @param DateTimeInterface|DateInterval|int $delay
|
||||
* @return int
|
||||
*/
|
||||
protected function availableAt($delay = 0)
|
||||
{
|
||||
$delay = $this->parseDateInterval($delay);
|
||||
|
||||
return $delay instanceof DateTimeInterface
|
||||
? $delay->getTimestamp()
|
||||
: Carbon::now()->addRealSeconds($delay)->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given value is an interval, convert it to a DateTime instance.
|
||||
*
|
||||
* @param DateTimeInterface|DateInterval|int $delay
|
||||
* @return DateTimeInterface|int
|
||||
*/
|
||||
protected function parseDateInterval($delay)
|
||||
{
|
||||
if ($delay instanceof DateInterval) {
|
||||
$delay = Carbon::now()->add($delay);
|
||||
}
|
||||
|
||||
return $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current system time as a UNIX timestamp.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function currentTime()
|
||||
{
|
||||
return Carbon::now()->getTimestamp();
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use Exception;
|
||||
use think\App;
|
||||
|
||||
abstract class Job
|
||||
{
|
||||
|
||||
/**
|
||||
* The job handler instance.
|
||||
* @var mixed
|
||||
*/
|
||||
protected $instance;
|
||||
|
||||
/**
|
||||
* @var App
|
||||
*/
|
||||
protected $app;
|
||||
|
||||
/**
|
||||
* The name of the queue the job belongs to.
|
||||
* @var string
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* The name of the connection the job belongs to.
|
||||
*/
|
||||
protected $connector;
|
||||
|
||||
/**
|
||||
* Indicates if the job has been deleted.
|
||||
* @var bool
|
||||
*/
|
||||
protected $deleted = false;
|
||||
|
||||
/**
|
||||
* Indicates if the job has been released.
|
||||
* @var bool
|
||||
*/
|
||||
protected $released = false;
|
||||
|
||||
/**
|
||||
* Indicates if the job has failed.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $failed = false;
|
||||
|
||||
/**
|
||||
* Get the decoded body of the job.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function payload()
|
||||
{
|
||||
return json_decode($this->getRawBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire the job.
|
||||
* @return void
|
||||
*/
|
||||
public function fire()
|
||||
{
|
||||
$payload = $this->payload();
|
||||
|
||||
list($class, $method) = $this->parseJob($payload['job']);
|
||||
|
||||
$this->instance = $this->resolve($class);
|
||||
if ($this->instance) {
|
||||
$this->instance->{$method}($this, $payload['data']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the job from the queue.
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$this->deleted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the job has been deleted.
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeleted()
|
||||
{
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the job back into the queue.
|
||||
* @param int $delay
|
||||
* @return void
|
||||
*/
|
||||
public function release($delay = 0)
|
||||
{
|
||||
$this->released = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the job was released back into the queue.
|
||||
* @return bool
|
||||
*/
|
||||
public function isReleased()
|
||||
{
|
||||
return $this->released;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the job has been deleted or released.
|
||||
* @return bool
|
||||
*/
|
||||
public function isDeletedOrReleased()
|
||||
{
|
||||
return $this->isDeleted() || $this->isReleased();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getJobId();
|
||||
|
||||
/**
|
||||
* Get the number of times the job has been attempted.
|
||||
* @return int
|
||||
*/
|
||||
abstract public function attempts();
|
||||
|
||||
/**
|
||||
* Get the raw body string for the job.
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getRawBody();
|
||||
|
||||
/**
|
||||
* Parse the job declaration into class and method.
|
||||
* @param string $job
|
||||
* @return array
|
||||
*/
|
||||
protected function parseJob($job)
|
||||
{
|
||||
$segments = explode('@', $job);
|
||||
|
||||
return count($segments) > 1 ? $segments : [$segments[0], 'fire'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given job handler.
|
||||
* @param string $name
|
||||
* @return mixed
|
||||
*/
|
||||
protected function resolve($name)
|
||||
{
|
||||
if (strpos($name, '\\') === false) {
|
||||
|
||||
if (strpos($name, '/') === false) {
|
||||
$app = '';
|
||||
} else {
|
||||
list($app, $name) = explode('/', $name, 2);
|
||||
}
|
||||
|
||||
$name = ($this->app->config->get('app.app_namespace') ?: 'app\\') . ($app ? strtolower($app) . '\\' : '') . 'job\\' . $name;
|
||||
}
|
||||
|
||||
return $this->app->make($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the job has been marked as a failure.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFailed()
|
||||
{
|
||||
return $this->failed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the job as "failed".
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markAsFailed()
|
||||
{
|
||||
$this->failed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an exception that caused the job to fail.
|
||||
*
|
||||
* @param Exception $e
|
||||
* @return void
|
||||
*/
|
||||
public function failed($e)
|
||||
{
|
||||
$this->markAsFailed();
|
||||
|
||||
$payload = $this->payload();
|
||||
|
||||
list($class, $method) = $this->parseJob($payload['job']);
|
||||
|
||||
if (method_exists($this->instance = $this->resolve($class), 'failed')) {
|
||||
$this->instance->failed($payload['data'], $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of times to attempt a job.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function maxTries()
|
||||
{
|
||||
return $this->payload()['maxTries'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of seconds the job can run.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function timeout()
|
||||
{
|
||||
return $this->payload()['timeout'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timestamp indicating when the job should timeout.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function timeoutAt()
|
||||
{
|
||||
return $this->payload()['timeoutAt'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the queued job class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->payload()['job'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the connection the job belongs to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getConnector()
|
||||
{
|
||||
return $this->connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the queue the job belongs to.
|
||||
* @return string
|
||||
*/
|
||||
public function getQueue()
|
||||
{
|
||||
return $this->queue;
|
||||
}
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use Closure;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use think\App;
|
||||
|
||||
class Listener
|
||||
{
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $commandPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $workerCommand;
|
||||
|
||||
/**
|
||||
* @var \Closure|null
|
||||
*/
|
||||
protected $outputHandler;
|
||||
|
||||
/**
|
||||
* @param string $commandPath
|
||||
*/
|
||||
public function __construct($commandPath)
|
||||
{
|
||||
$this->commandPath = $commandPath;
|
||||
}
|
||||
|
||||
public static function __make(App $app)
|
||||
{
|
||||
return new self($app->getRootPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PHP binary.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function phpBinary()
|
||||
{
|
||||
return (new PhpExecutableFinder)->find(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param string $queue
|
||||
* @param int $delay
|
||||
* @param int $sleep
|
||||
* @param int $maxTries
|
||||
* @param int $memory
|
||||
* @param int $timeout
|
||||
* @return void
|
||||
*/
|
||||
public function listen($connector, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60)
|
||||
{
|
||||
$process = $this->makeProcess($connector, $queue, $delay, $sleep, $maxTries, $memory, $timeout);
|
||||
|
||||
while (true) {
|
||||
$this->runProcess($process, $memory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param string $queue
|
||||
* @param int $delay
|
||||
* @param int $sleep
|
||||
* @param int $maxTries
|
||||
* @param int $memory
|
||||
* @param int $timeout
|
||||
* @return Process
|
||||
*/
|
||||
public function makeProcess($connector, $queue, $delay, $sleep, $maxTries, $memory, $timeout)
|
||||
{
|
||||
$command = array_filter([
|
||||
$this->phpBinary(),
|
||||
'think',
|
||||
'queue:work',
|
||||
$connector,
|
||||
'--once',
|
||||
"--queue={$queue}",
|
||||
"--delay={$delay}",
|
||||
"--memory={$memory}",
|
||||
"--sleep={$sleep}",
|
||||
"--tries={$maxTries}",
|
||||
], function ($value) {
|
||||
return !is_null($value);
|
||||
});
|
||||
|
||||
return new Process($command, $this->commandPath, null, null, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Process $process
|
||||
* @param int $memory
|
||||
*/
|
||||
public function runProcess(Process $process, $memory)
|
||||
{
|
||||
$process->run(function ($type, $line) {
|
||||
$this->handleWorkerOutput($type, $line);
|
||||
});
|
||||
|
||||
if ($this->memoryExceeded($memory)) {
|
||||
$this->stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
* @param string $line
|
||||
* @return void
|
||||
*/
|
||||
protected function handleWorkerOutput($type, $line)
|
||||
{
|
||||
if (isset($this->outputHandler)) {
|
||||
call_user_func($this->outputHandler, $type, $line);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $memoryLimit
|
||||
* @return bool
|
||||
*/
|
||||
public function memoryExceeded($memoryLimit)
|
||||
{
|
||||
return (memory_get_usage() / 1024 / 1024) >= $memoryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function stop()
|
||||
{
|
||||
die;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure $outputHandler
|
||||
* @return void
|
||||
*/
|
||||
public function setOutputHandler(Closure $outputHandler)
|
||||
{
|
||||
$this->outputHandler = $outputHandler;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
trait Queueable
|
||||
{
|
||||
|
||||
/** @var string 队列名称 */
|
||||
public $queue;
|
||||
|
||||
/** @var integer 延迟时间 */
|
||||
public $delay;
|
||||
|
||||
/**
|
||||
* 设置队列名
|
||||
* @param $queue
|
||||
* @return $this
|
||||
*/
|
||||
public function queue($queue)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置延迟时间
|
||||
* @param $delay
|
||||
* @return $this
|
||||
*/
|
||||
public function delay($delay)
|
||||
{
|
||||
$this->delay = $delay;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use think\helper\Arr;
|
||||
use think\helper\Str;
|
||||
use think\Queue;
|
||||
use think\queue\command\FailedTable;
|
||||
use think\queue\command\FlushFailed;
|
||||
use think\queue\command\ForgetFailed;
|
||||
use think\queue\command\Listen;
|
||||
use think\queue\command\ListFailed;
|
||||
use think\queue\command\Restart;
|
||||
use think\queue\command\Retry;
|
||||
use think\queue\command\Table;
|
||||
use think\queue\command\Work;
|
||||
|
||||
class Service extends \think\Service
|
||||
{
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind('queue', Queue::class);
|
||||
$this->app->bind('queue.failer', function () {
|
||||
|
||||
$config = $this->app->config->get('queue.failed', []);
|
||||
|
||||
$type = Arr::pull($config, 'type', 'none');
|
||||
|
||||
$class = false !== strpos($type, '\\') ? $type : '\\think\\queue\\failed\\' . Str::studly($type);
|
||||
|
||||
return $this->app->invokeClass($class, [$config]);
|
||||
});
|
||||
}
|
||||
|
||||
public function boot()
|
||||
{
|
||||
$this->commands([
|
||||
FailedJob::class,
|
||||
Table::class,
|
||||
FlushFailed::class,
|
||||
ForgetFailed::class,
|
||||
ListFailed::class,
|
||||
Retry::class,
|
||||
Work::class,
|
||||
Restart::class,
|
||||
Listen::class,
|
||||
FailedTable::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
interface ShouldQueue
|
||||
{
|
||||
|
||||
}
|
||||
+408
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue;
|
||||
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
use think\Cache;
|
||||
use think\Event;
|
||||
use think\exception\Handle;
|
||||
use think\Queue;
|
||||
use think\queue\event\JobExceptionOccurred;
|
||||
use think\queue\event\JobFailed;
|
||||
use think\queue\event\JobProcessed;
|
||||
use think\queue\event\JobProcessing;
|
||||
use think\queue\event\WorkerStopping;
|
||||
use Throwable;
|
||||
|
||||
class Worker
|
||||
{
|
||||
/** @var Event */
|
||||
protected $event;
|
||||
/** @var Handle */
|
||||
protected $handle;
|
||||
/** @var Queue */
|
||||
protected $queue;
|
||||
|
||||
/** @var Cache */
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* Indicates if the worker should exit.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $shouldQuit = false;
|
||||
|
||||
/**
|
||||
* Indicates if the worker is paused.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $paused = false;
|
||||
|
||||
public function __construct(Queue $queue, Event $event, Handle $handle, Cache $cache)
|
||||
{
|
||||
$this->queue = $queue;
|
||||
$this->event = $event;
|
||||
$this->handle = $handle;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param string $queue
|
||||
* @param int $delay
|
||||
* @param int $sleep
|
||||
* @param int $maxTries
|
||||
* @param int $memory
|
||||
* @param int $timeout
|
||||
*/
|
||||
public function daemon($connector, $queue, $delay = 0, $sleep = 3, $maxTries = 0, $memory = 128, $timeout = 60)
|
||||
{
|
||||
if ($this->supportsAsyncSignals()) {
|
||||
$this->listenForSignals();
|
||||
}
|
||||
|
||||
$lastRestart = $this->getTimestampOfLastQueueRestart();
|
||||
|
||||
while (true) {
|
||||
|
||||
$job = $this->getNextJob(
|
||||
$this->queue->driver($connector), $queue
|
||||
);
|
||||
|
||||
if ($this->supportsAsyncSignals()) {
|
||||
$this->registerTimeoutHandler($job, $timeout);
|
||||
}
|
||||
|
||||
if ($job) {
|
||||
$this->runJob($job, $connector, $maxTries, $delay);
|
||||
} else {
|
||||
$this->sleep($sleep);
|
||||
}
|
||||
|
||||
if ($this->shouldQuit || $this->queueShouldRestart($lastRestart)) {
|
||||
$this->stop();
|
||||
} elseif ($this->memoryExceeded($memory)) {
|
||||
$this->stop(12);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the queue worker should restart.
|
||||
*
|
||||
* @param int|null $lastRestart
|
||||
* @return bool
|
||||
*/
|
||||
protected function queueShouldRestart($lastRestart)
|
||||
{
|
||||
return $this->getTimestampOfLastQueueRestart() != $lastRestart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the memory limit has been exceeded.
|
||||
*
|
||||
* @param int $memoryLimit
|
||||
* @return bool
|
||||
*/
|
||||
public function memoryExceeded($memoryLimit)
|
||||
{
|
||||
return (memory_get_usage(true) / 1024 / 1024) >= $memoryLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列重启时间
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getTimestampOfLastQueueRestart()
|
||||
{
|
||||
if ($this->cache) {
|
||||
return $this->cache->get('think:queue:restart');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the worker timeout handler.
|
||||
*
|
||||
* @param Job|null $job
|
||||
* @param int $timeout
|
||||
* @return void
|
||||
*/
|
||||
protected function registerTimeoutHandler($job, $timeout)
|
||||
{
|
||||
pcntl_signal(SIGALRM, function () {
|
||||
$this->kill(1);
|
||||
});
|
||||
|
||||
pcntl_alarm(
|
||||
max($this->timeoutForJob($job, $timeout), 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop listening and bail out of the script.
|
||||
*
|
||||
* @param int $status
|
||||
* @return void
|
||||
*/
|
||||
public function stop($status = 0)
|
||||
{
|
||||
$this->event->trigger(new WorkerStopping($status));
|
||||
|
||||
exit($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the process.
|
||||
*
|
||||
* @param int $status
|
||||
* @return void
|
||||
*/
|
||||
public function kill($status = 0)
|
||||
{
|
||||
$this->event->trigger(new WorkerStopping($status));
|
||||
|
||||
if (extension_loaded('posix')) {
|
||||
posix_kill(getmypid(), SIGKILL);
|
||||
}
|
||||
|
||||
exit($status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate timeout for the given job.
|
||||
*
|
||||
* @param Job|null $job
|
||||
* @param int $timeout
|
||||
* @return int
|
||||
*/
|
||||
protected function timeoutForJob($job, $timeout)
|
||||
{
|
||||
return $job && !is_null($job->timeout()) ? $job->timeout() : $timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if "async" signals are supported.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function supportsAsyncSignals()
|
||||
{
|
||||
return extension_loaded('pcntl');
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable async signals for the process.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function listenForSignals()
|
||||
{
|
||||
pcntl_async_signals(true);
|
||||
|
||||
pcntl_signal(SIGTERM, function () {
|
||||
$this->shouldQuit = true;
|
||||
});
|
||||
|
||||
pcntl_signal(SIGUSR2, function () {
|
||||
$this->paused = true;
|
||||
});
|
||||
|
||||
pcntl_signal(SIGCONT, function () {
|
||||
$this->paused = false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行下个任务
|
||||
* @param string $connectorName
|
||||
* @param string $queue
|
||||
* @param int $delay
|
||||
* @param int $sleep
|
||||
* @param int $maxTries
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function runNextJob($connectorName, $queue, $delay = 0, $sleep = 3, $maxTries = 0)
|
||||
{
|
||||
|
||||
$job = $this->getNextJob($this->queue->driver($connectorName), $queue);
|
||||
|
||||
if (!$job) {
|
||||
$this->runJob($job, $connectorName, $maxTries, $delay);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->sleep($sleep);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行任务
|
||||
* @param Job $job
|
||||
* @param string $connectorName
|
||||
* @param int $maxTries
|
||||
* @param int $delay
|
||||
* @return void
|
||||
*/
|
||||
protected function runJob($job, $connectorName, $maxTries, $delay)
|
||||
{
|
||||
try {
|
||||
$this->process($connectorName, $job, $maxTries, $delay);
|
||||
} catch (Exception | Throwable $e) {
|
||||
$this->handle->report($e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下个任务
|
||||
* @param Connector $connector
|
||||
* @param string $queue
|
||||
* @return Job
|
||||
*/
|
||||
protected function getNextJob($connector, $queue)
|
||||
{
|
||||
try {
|
||||
foreach (explode(',', $queue) as $queue) {
|
||||
if (!is_null($job = $connector->pop($queue))) {
|
||||
return $job;
|
||||
}
|
||||
}
|
||||
} catch (Exception | Throwable $e) {
|
||||
$this->handle->report($e);
|
||||
$this->sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a given job from the queue.
|
||||
* @param string $connector
|
||||
* @param Job $job
|
||||
* @param int $maxTries
|
||||
* @param int $delay
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function process($connector, Job $job, $maxTries = 0, $delay = 0)
|
||||
{
|
||||
try {
|
||||
$this->event->trigger(new JobProcessing($connector, $job));
|
||||
|
||||
$this->markJobAsFailedIfAlreadyExceedsMaxAttempts(
|
||||
$connector, $job, (int) $maxTries
|
||||
);
|
||||
|
||||
$job->fire();
|
||||
|
||||
$this->event->trigger(new JobProcessed($connector, $job));
|
||||
} catch (Exception | Throwable $e) {
|
||||
try {
|
||||
if (!$job->hasFailed()) {
|
||||
$this->markJobAsFailedIfWillExceedMaxAttempts($connector, $job, (int) $maxTries, $e);
|
||||
}
|
||||
|
||||
$this->event->trigger(new JobExceptionOccurred($connector, $job, $e));
|
||||
} finally {
|
||||
if (!$job->isDeleted() && !$job->isReleased() && !$job->hasFailed()) {
|
||||
$job->release($delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param Job $job
|
||||
* @param int $maxTries
|
||||
*/
|
||||
protected function markJobAsFailedIfAlreadyExceedsMaxAttempts($connector, $job, $maxTries)
|
||||
{
|
||||
$maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries;
|
||||
|
||||
$timeoutAt = $job->timeoutAt();
|
||||
|
||||
if ($timeoutAt && time() <= $timeoutAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$timeoutAt && ($maxTries === 0 || $job->attempts() <= $maxTries)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->failJob($connector, $job, $e = new RuntimeException(
|
||||
$job->getName() . ' has been attempted too many times or run too long. The job may have previously timed out.'
|
||||
));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param Job $job
|
||||
* @param int $maxTries
|
||||
* @param Exception $e
|
||||
*/
|
||||
protected function markJobAsFailedIfWillExceedMaxAttempts($connector, $job, $maxTries, $e)
|
||||
{
|
||||
$maxTries = !is_null($job->maxTries()) ? $job->maxTries() : $maxTries;
|
||||
|
||||
if ($job->timeoutAt() && $job->timeoutAt() <= time()) {
|
||||
$this->failJob($connector, $job, $e);
|
||||
}
|
||||
|
||||
if ($maxTries > 0 && $job->attempts() >= $maxTries) {
|
||||
$this->failJob($connector, $job, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $connector
|
||||
* @param Job $job
|
||||
* @param Exception $e
|
||||
*/
|
||||
protected function failJob($connector, $job, $e)
|
||||
{
|
||||
$job->markAsFailed();
|
||||
|
||||
if ($job->isDeleted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$job->delete();
|
||||
|
||||
$job->failed($e);
|
||||
} finally {
|
||||
$this->event->trigger(new JobFailed(
|
||||
$connector, $job, $e ?: new RuntimeException('ManuallyFailed')
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep the script for a given number of seconds.
|
||||
* @param int $seconds
|
||||
* @return void
|
||||
*/
|
||||
public function sleep($seconds)
|
||||
{
|
||||
if ($seconds < 1) {
|
||||
usleep($seconds * 1000000);
|
||||
} else {
|
||||
sleep($seconds);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\helper\Str;
|
||||
use think\migration\Creator;
|
||||
|
||||
class FailedTable extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:failed-table')
|
||||
->setDescription('Create a migration for the failed queue jobs database table');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->app->has('migration.creator')) {
|
||||
$this->output->error('Install think-migration first please');
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->app->config->get('queue.failed.table');
|
||||
|
||||
$className = Str::studly("create_{$table}_table");
|
||||
|
||||
/** @var Creator $creator */
|
||||
$creator = $this->app->get('migration.creator');
|
||||
|
||||
$path = $creator->create($className);
|
||||
|
||||
// Load the alternative template if it is defined.
|
||||
$contents = file_get_contents(__DIR__ . '/stubs/failed_jobs.stub');
|
||||
|
||||
// inject the class names appropriate to this migration
|
||||
$contents = strtr($contents, [
|
||||
'CreateFailedJobsTable' => $className,
|
||||
'{{table}}' => $table,
|
||||
]);
|
||||
|
||||
file_put_contents($path, $contents);
|
||||
|
||||
$this->output->info('Migration created successfully!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
|
||||
class FlushFailed extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:flush')
|
||||
->setDescription('Flush all of the failed queue jobs');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->app->get('queue.failer')->flush();
|
||||
|
||||
$this->output->info('All failed jobs deleted successfully!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\input\Argument;
|
||||
|
||||
class ForgetFailed extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:forget')
|
||||
->addArgument('id', Argument::REQUIRED, 'The ID of the failed job')
|
||||
->setDescription('Delete a failed queue job');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if ($this->app['queue.failer']->forget($this->input->getArgument('id'))) {
|
||||
$this->output->info('Failed job deleted successfully!');
|
||||
} else {
|
||||
$this->output->error('No failed job matches the given ID.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Table;
|
||||
use think\helper\Arr;
|
||||
|
||||
class ListFailed extends Command
|
||||
{
|
||||
/**
|
||||
* The table headers for the command.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $headers = ['ID', 'Connection', 'Queue', 'Class', 'Failed At'];
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:failed')
|
||||
->setDescription('List all of the failed queue jobs');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (count($jobs = $this->getFailedJobs()) === 0) {
|
||||
$this->output->info('No failed jobs!');
|
||||
return;
|
||||
}
|
||||
$this->displayFailedJobs($jobs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the failed jobs in the console.
|
||||
*
|
||||
* @param array $jobs
|
||||
* @return void
|
||||
*/
|
||||
protected function displayFailedJobs(array $jobs)
|
||||
{
|
||||
$table = new Table();
|
||||
$table->setHeader($this->headers);
|
||||
$table->setRows($jobs);
|
||||
|
||||
$this->table($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the failed jobs into a displayable format.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getFailedJobs()
|
||||
{
|
||||
$failed = $this->app['queue.failer']->all();
|
||||
|
||||
return collect($failed)->map(function ($failed) {
|
||||
return $this->parseFailedJob((array) $failed);
|
||||
})->filter()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the failed job row.
|
||||
*
|
||||
* @param array $failed
|
||||
* @return array
|
||||
*/
|
||||
protected function parseFailedJob(array $failed)
|
||||
{
|
||||
$row = array_values(Arr::except($failed, ['payload', 'exception']));
|
||||
|
||||
array_splice($row, 3, 0, $this->extractJobName($failed['payload']));
|
||||
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the failed job name from payload.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string|null
|
||||
*/
|
||||
private function extractJobName($payload)
|
||||
{
|
||||
$payload = json_decode($payload, true);
|
||||
|
||||
if ($payload && (!isset($payload['data']['command']))) {
|
||||
return $payload['job'] ?? null;
|
||||
} elseif ($payload && isset($payload['data']['command'])) {
|
||||
return $this->matchJobName($payload);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the job name from the payload.
|
||||
*
|
||||
* @param array $payload
|
||||
* @return string
|
||||
*/
|
||||
protected function matchJobName($payload)
|
||||
{
|
||||
preg_match('/"([^"]+)"/', $payload['data']['command'], $matches);
|
||||
|
||||
if (isset($matches[1])) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return $payload['job'] ?? null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\queue\Listener;
|
||||
|
||||
class Listen extends Command
|
||||
{
|
||||
/** @var Listener */
|
||||
protected $listener;
|
||||
|
||||
public function __construct(Listener $listener)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->listener = $listener;
|
||||
$this->listener->setOutputHandler(function ($type, $line) {
|
||||
$this->output->write($line);
|
||||
});
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:listen')
|
||||
->addArgument('connector', Argument::OPTIONAL, 'The name of the queue connector to work', null)
|
||||
->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on', null)
|
||||
->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0)
|
||||
->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128)
|
||||
->addOption('timeout', null, Option::VALUE_OPTIONAL, 'Seconds a job may run before timing out', 60)
|
||||
->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Seconds to wait before checking queue for jobs', 3)
|
||||
->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0)
|
||||
->setDescription('Listen to a given queue');
|
||||
}
|
||||
|
||||
public function execute(Input $input, Output $output)
|
||||
{
|
||||
$connector = $input->getArgument('connector') ?: $this->app->config->get('queue.connector', 'sync');
|
||||
|
||||
$queue = $input->getOption('queue') ?: $this->app->config->get("queue.{$connector}", 'default');
|
||||
$delay = $input->getOption('delay');
|
||||
$memory = $input->getOption('memory');
|
||||
$timeout = $input->getOption('timeout');
|
||||
$sleep = $input->getOption('sleep');
|
||||
$tries = $input->getOption('tries');
|
||||
|
||||
$this->listener->listen($connector, $queue, $delay, $sleep, $tries, $memory, $timeout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\Cache;
|
||||
use think\console\Command;
|
||||
|
||||
class Restart extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:restart')
|
||||
->setDescription('Restart queue worker daemons after their current job');
|
||||
}
|
||||
|
||||
public function handle(Cache $cache)
|
||||
{
|
||||
$cache->set('think:queue:restart', time());
|
||||
$this->output->info("Broadcasting queue restart signal.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use stdClass;
|
||||
use think\console\Command;
|
||||
use think\console\input\Argument;
|
||||
use think\helper\Arr;
|
||||
|
||||
class Retry extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:retry')
|
||||
->addArgument('id', Argument::IS_ARRAY | Argument::REQUIRED, 'The ID of the failed job or "all" to retry all jobs')
|
||||
->setDescription('Retry a failed queue job');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
foreach ($this->getJobIds() as $id) {
|
||||
$job = $this->app['queue.failer']->find($id);
|
||||
|
||||
if (is_null($job)) {
|
||||
$this->output->error("Unable to find failed job with ID [{$id}].");
|
||||
} else {
|
||||
$this->retryJob($job);
|
||||
|
||||
$this->output->info("The failed job [{$id}] has been pushed back onto the queue!");
|
||||
|
||||
$this->app['queue.failer']->forget($id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry the queue job.
|
||||
*
|
||||
* @param stdClass $job
|
||||
* @return void
|
||||
*/
|
||||
protected function retryJob($job)
|
||||
{
|
||||
$this->app['queue']->driver($job->connector)->pushRaw(
|
||||
$this->resetAttempts($job->payload), $job->queue
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the payload attempts.
|
||||
*
|
||||
* Applicable to Redis jobs which store attempts in their payload.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
*/
|
||||
protected function resetAttempts($payload)
|
||||
{
|
||||
$payload = json_decode($payload, true);
|
||||
|
||||
if (isset($payload['attempts'])) {
|
||||
$payload['attempts'] = 0;
|
||||
}
|
||||
|
||||
return json_encode($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job IDs to be retried.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getJobIds()
|
||||
{
|
||||
$ids = (array) $this->input->getArgument('id');
|
||||
|
||||
if (count($ids) === 1 && $ids[0] === 'all') {
|
||||
$ids = Arr::pluck($this->app['queue.failer']->all(), 'id');
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\helper\Str;
|
||||
use think\migration\Creator;
|
||||
|
||||
class Table extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:table')
|
||||
->setDescription('Create a migration for the queue jobs database table');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!$this->app->has('migration.creator')) {
|
||||
$this->output->error('Install think-migration first please');
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $this->app->config->get('queue.connectors.database.table');
|
||||
|
||||
$className = Str::studly("create_{$table}_table");
|
||||
|
||||
/** @var Creator $creator */
|
||||
$creator = $this->app->get('migration.creator');
|
||||
|
||||
$path = $creator->create($className);
|
||||
|
||||
// Load the alternative template if it is defined.
|
||||
$contents = file_get_contents(__DIR__ . '/stubs/jobs.stub');
|
||||
|
||||
// inject the class names appropriate to this migration
|
||||
$contents = strtr($contents, [
|
||||
'CreateJobsTable' => $className,
|
||||
'{{table}}' => $table,
|
||||
]);
|
||||
|
||||
file_put_contents($path, $contents);
|
||||
|
||||
$this->output->info('Migration created successfully!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace think\queue\command;
|
||||
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Argument;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\queue\event\JobFailed;
|
||||
use think\queue\event\JobProcessed;
|
||||
use think\queue\event\JobProcessing;
|
||||
use think\queue\Job;
|
||||
use think\queue\Worker;
|
||||
|
||||
class Work extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* The queue worker instance.
|
||||
* @var Worker
|
||||
*/
|
||||
protected $worker;
|
||||
|
||||
public function __construct(Worker $worker)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->worker = $worker;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('queue:work')
|
||||
->addArgument('connector', Argument::OPTIONAL, 'The name of the queue connector to work', null)
|
||||
->addOption('queue', null, Option::VALUE_OPTIONAL, 'The queue to listen on')
|
||||
->addOption('once', null, Option::VALUE_NONE, 'Only process the next job on the queue')
|
||||
->addOption('delay', null, Option::VALUE_OPTIONAL, 'Amount of time to delay failed jobs', 0)
|
||||
->addOption('force', null, Option::VALUE_NONE, 'Force the worker to run even in maintenance mode')
|
||||
->addOption('memory', null, Option::VALUE_OPTIONAL, 'The memory limit in megabytes', 128)
|
||||
->addOption('timeout', null, Option::VALUE_OPTIONAL, 'The number of seconds a child process can run', 60)
|
||||
->addOption('sleep', null, Option::VALUE_OPTIONAL, 'Number of seconds to sleep when no job is available', 3)
|
||||
->addOption('tries', null, Option::VALUE_OPTIONAL, 'Number of times to attempt a job before logging it failed', 0)
|
||||
->setDescription('Process the next job on a queue');
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* @param Input $input
|
||||
* @param Output $output
|
||||
* @return int|null|void
|
||||
*/
|
||||
public function execute(Input $input, Output $output)
|
||||
{
|
||||
$connector = $input->getArgument('connector') ?: $this->app->config->get('queue.connector', 'sync');
|
||||
|
||||
$queue = $input->getOption('queue') ?: $this->app->config->get("queue.{$connector}", 'default');
|
||||
$delay = $input->getOption('delay');
|
||||
$sleep = $input->getOption('sleep');
|
||||
$tries = $input->getOption('tries');
|
||||
|
||||
$this->listenForEvents();
|
||||
|
||||
if ($input->getOption('once')) {
|
||||
$this->worker->runNextJob($connector, $queue, $delay, $sleep, $tries);
|
||||
} else {
|
||||
$memory = $input->getOption('memory');
|
||||
$timeout = $input->getOption('timeout');
|
||||
$this->worker->daemon($connector, $queue, $delay, $sleep, $tries, $memory, $timeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册事件
|
||||
*/
|
||||
protected function listenForEvents()
|
||||
{
|
||||
$this->app->event->listen(JobProcessing::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'starting');
|
||||
});
|
||||
|
||||
$this->app->event->listen(JobProcessed::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'success');
|
||||
});
|
||||
|
||||
$this->app->event->listen(JobFailed::class, function ($event) {
|
||||
$this->writeOutput($event->job, 'failed');
|
||||
|
||||
$this->logFailedJob($event);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the status output for the queue worker.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param $status
|
||||
*/
|
||||
protected function writeOutput(Job $job, $status)
|
||||
{
|
||||
switch ($status) {
|
||||
case 'starting':
|
||||
$this->writeStatus($job, 'Processing', 'comment');
|
||||
break;
|
||||
case 'success':
|
||||
$this->writeStatus($job, 'Processed', 'info');
|
||||
break;
|
||||
case 'failed':
|
||||
$this->writeStatus($job, 'Failed', 'error');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the status output for the queue worker.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param string $status
|
||||
* @param string $type
|
||||
* @return void
|
||||
*/
|
||||
protected function writeStatus(Job $job, $status, $type)
|
||||
{
|
||||
$this->output->writeln(sprintf(
|
||||
"<{$type}>[%s][%s] %s</{$type}> %s",
|
||||
date('Y-m-d H:i:s'),
|
||||
$job->getJobId(),
|
||||
str_pad("{$status}:", 11), $job->getName()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录失败任务
|
||||
* @param JobFailed $event
|
||||
*/
|
||||
protected function logFailedJob(JobFailed $event)
|
||||
{
|
||||
$this->app['queue.failer']->log(
|
||||
$event->connector, $event->job->getQueue(),
|
||||
$event->job->getRawBody(), $event->exception
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
use think\migration\db\Column;
|
||||
use think\migration\Migrator;
|
||||
|
||||
class CreateFailedJobsTable extends Migrator
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$this->table('{{table}}')
|
||||
->addColumn(Column::text('connection'))
|
||||
->addColumn(Column::text('queue'))
|
||||
->addColumn(Column::longText('payload'))
|
||||
->addColumn(Column::longText('exception'))
|
||||
->addColumn(Column::timestamp('failed_at')->setDefault('CURRENT_TIMESTAMP'))
|
||||
->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
use think\migration\db\Column;
|
||||
use think\migration\Migrator;
|
||||
|
||||
class CreateJobsTable extends Migrator
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$this->table('{{table}}')
|
||||
->addColumn(Column::string('queue'))
|
||||
->addColumn(Column::longText('payload'))
|
||||
->addColumn(Column::tinyInteger('attempts')->setUnsigned())
|
||||
->addColumn(Column::unsignedInteger('reserved_at')->setNullable())
|
||||
->addColumn(Column::unsignedInteger('available_at'))
|
||||
->addColumn(Column::unsignedInteger('create_at'))
|
||||
->addIndex('queue')
|
||||
->create();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\connector;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use stdClass;
|
||||
use think\Db;
|
||||
use think\db\Query;
|
||||
use think\queue\Connector;
|
||||
use think\queue\InteractsWithTime;
|
||||
use think\queue\job\Database as DatabaseJob;
|
||||
|
||||
class Database extends Connector
|
||||
{
|
||||
|
||||
use InteractsWithTime;
|
||||
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* The database table that holds the jobs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
/**
|
||||
* The name of the default queue.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $default;
|
||||
|
||||
/**
|
||||
* The expiration time of a job.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $retryAfter = 60;
|
||||
|
||||
public function __construct(Db $db, $table, $default = 'default', $retryAfter = 60)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->table = $table;
|
||||
$this->default = $default;
|
||||
$this->retryAfter = $retryAfter;
|
||||
}
|
||||
|
||||
public static function __make(Db $db, $config)
|
||||
{
|
||||
return new self($db, $config['table'], $config['queue'], $config['retry_after'] ?? 60);
|
||||
}
|
||||
|
||||
public function size($queue = null)
|
||||
{
|
||||
$this->db->name($this->table)
|
||||
->where('queue', $this->getQueue($queue))
|
||||
->count();
|
||||
}
|
||||
|
||||
public function push($job, $data = '', $queue = null)
|
||||
{
|
||||
return $this->pushToDatabase($queue, $this->createPayload($job, $data));
|
||||
}
|
||||
|
||||
public function pushRaw($payload, $queue = null, array $options = [])
|
||||
{
|
||||
return $this->pushToDatabase($queue, $payload);
|
||||
}
|
||||
|
||||
public function later($delay, $job, $data = '', $queue = null)
|
||||
{
|
||||
return $this->pushToDatabase($queue, $this->createPayload($job, $data), $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新发布任务
|
||||
*
|
||||
* @param string $queue
|
||||
* @param StdClass $job
|
||||
* @param int $delay
|
||||
* @return mixed
|
||||
*/
|
||||
public function release($queue, $job, $delay)
|
||||
{
|
||||
return $this->pushToDatabase($queue, $job->payload, $delay, $job->attempts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a raw payload to the database with a given delay.
|
||||
*
|
||||
* @param \DateTime|int $delay
|
||||
* @param string|null $queue
|
||||
* @param string $payload
|
||||
* @param int $attempts
|
||||
* @return mixed
|
||||
*/
|
||||
protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0)
|
||||
{
|
||||
return $this->db->name($this->table)->insertGetId([
|
||||
'queue' => $this->getQueue($queue),
|
||||
'attempts' => $attempts,
|
||||
'reserved_at' => null,
|
||||
'available_at' => $this->availableAt($delay),
|
||||
'created_at' => $this->currentTime(),
|
||||
'payload' => $payload,
|
||||
]);
|
||||
}
|
||||
|
||||
public function pop($queue = null)
|
||||
{
|
||||
$queue = $this->getQueue($queue);
|
||||
|
||||
return $this->db->transaction(function () use ($queue) {
|
||||
|
||||
if ($job = $this->getNextAvailableJob($queue)) {
|
||||
|
||||
$job = $this->markJobAsReserved($job);
|
||||
|
||||
return new DatabaseJob($this->app, $this, $job, $this->connectorName, $queue);
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下个有效任务
|
||||
*
|
||||
* @param string|null $queue
|
||||
* @return StdClass|null
|
||||
*/
|
||||
protected function getNextAvailableJob($queue)
|
||||
{
|
||||
|
||||
$job = $this->db->name($this->table)
|
||||
->lock(true)
|
||||
->where('queue', $this->getQueue($queue))
|
||||
->where(function (Query $query) {
|
||||
$query->where(function (Query $query) {
|
||||
$query->whereNull('reserved_at')
|
||||
->where('available_at', '<=', $this->currentTime());
|
||||
});
|
||||
|
||||
//超时任务重试
|
||||
$expiration = Carbon::now()->subSeconds($this->retryAfter)->getTimestamp();
|
||||
|
||||
$query->whereOr(function (Query $query) use ($expiration) {
|
||||
$query->where('reserved_at', '<=', $expiration);
|
||||
});
|
||||
})
|
||||
->order('id', 'asc')
|
||||
->find();
|
||||
|
||||
return $job ? (object) $job : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记任务正在执行.
|
||||
*
|
||||
* @param stdClass $job
|
||||
* @return stdClass
|
||||
*/
|
||||
protected function markJobAsReserved($job)
|
||||
{
|
||||
$this->db->name($this->table)->where('id', $job->id)->update([
|
||||
'reserved_at' => $job->reserved_at = $this->currentTime(),
|
||||
'attempts' => ++$job->attempts,
|
||||
]);
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
*
|
||||
* @param string $id
|
||||
* @return void
|
||||
*/
|
||||
public function deleteReserved($id)
|
||||
{
|
||||
$this->db->transaction(function () use ($id) {
|
||||
if ($this->db->name($this->table)->lock(true)->find($id)) {
|
||||
$this->db->name($this->table)->where('id', $id)->delete();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function getQueue($queue)
|
||||
{
|
||||
return $queue ?: $this->default;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\connector;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use think\helper\Str;
|
||||
use think\queue\Connector;
|
||||
use think\queue\InteractsWithTime;
|
||||
use think\queue\job\Redis as RedisJob;
|
||||
|
||||
class Redis extends Connector
|
||||
{
|
||||
use InteractsWithTime;
|
||||
|
||||
/** @var \Redis */
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* The name of the default queue.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $default;
|
||||
|
||||
/**
|
||||
* The expiration time of a job.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $retryAfter = 60;
|
||||
|
||||
/**
|
||||
* The maximum number of seconds to block for a job.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
protected $blockFor = null;
|
||||
|
||||
public function __construct(\Redis $redis, $default = 'default', $retryAfter = 60, $blockFor = null)
|
||||
{
|
||||
$this->redis = $redis;
|
||||
$this->default = $default;
|
||||
$this->retryAfter = $retryAfter;
|
||||
$this->blockFor = $blockFor;
|
||||
}
|
||||
|
||||
public static function __make($config)
|
||||
{
|
||||
if (!extension_loaded('redis')) {
|
||||
throw new Exception('redis扩展未安装');
|
||||
}
|
||||
|
||||
$func = $config['persistent'] ? 'pconnect' : 'connect';
|
||||
|
||||
$redis = new \Redis;
|
||||
$redis->$func($config['host'], $config['port'], $config['timeout']);
|
||||
|
||||
if ('' != $config['password']) {
|
||||
$redis->auth($config['password']);
|
||||
}
|
||||
|
||||
if (0 != $config['select']) {
|
||||
$redis->select($config['select']);
|
||||
}
|
||||
|
||||
return new self($redis, $config['queue'], $config['retry_after'] ?? 60, $config['block_for'] ?? null);
|
||||
}
|
||||
|
||||
public function size($queue)
|
||||
{
|
||||
$queue = $this->getQueue($queue);
|
||||
|
||||
return $this->redis->lLen($queue) + $this->redis->zCard("{$queue}:delayed") + $this->redis->zCard("{$queue}:reserved");
|
||||
}
|
||||
|
||||
public function push($job, $data = '', $queue = null)
|
||||
{
|
||||
return $this->pushRaw($this->createPayload($job, $data), $queue);
|
||||
}
|
||||
|
||||
public function pushRaw($payload, $queue = null, array $options = [])
|
||||
{
|
||||
$this->redis->rPush($this->getQueue($queue), $payload);
|
||||
|
||||
return json_decode($payload, true)['id'] ?? null;
|
||||
}
|
||||
|
||||
public function later($delay, $job, $data = '', $queue = null)
|
||||
{
|
||||
return $this->laterRaw($delay, $this->createPayload($job, $data), $queue);
|
||||
}
|
||||
|
||||
protected function laterRaw($delay, $payload, $queue = null)
|
||||
{
|
||||
$this->redis->zadd(
|
||||
$this->getQueue($queue) . ':delayed', $this->availableAt($delay), $payload
|
||||
);
|
||||
|
||||
return json_decode($payload, true)['id'] ?? null;
|
||||
}
|
||||
|
||||
public function pop($queue = null)
|
||||
{
|
||||
$this->migrate($prefixed = $this->getQueue($queue));
|
||||
|
||||
if (empty($nextJob = $this->retrieveNextJob($prefixed))) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$job, $reserved] = $nextJob;
|
||||
|
||||
if ($reserved) {
|
||||
return new RedisJob($this->app, $this, $job, $reserved, $this->connectorName, $queue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate any delayed or expired jobs onto the primary queue.
|
||||
*
|
||||
* @param string $queue
|
||||
* @return void
|
||||
*/
|
||||
protected function migrate($queue)
|
||||
{
|
||||
$this->migrateExpiredJobs($queue . ':delayed', $queue);
|
||||
|
||||
if (!is_null($this->retryAfter)) {
|
||||
$this->migrateExpiredJobs($queue . ':reserved', $queue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动延迟任务
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @param bool $attempt
|
||||
*/
|
||||
public function migrateExpiredJobs($from, $to, $attempt = true)
|
||||
{
|
||||
$this->redis->watch($from);
|
||||
|
||||
$jobs = $this->redis->zRangeByScore($from, '-inf', $this->currentTime());
|
||||
|
||||
if (!empty($jobs)) {
|
||||
$this->transaction(function () use ($from, $to, $jobs, $attempt) {
|
||||
|
||||
$this->redis->zRemRangeByRank($from, 0, count($jobs) - 1);
|
||||
|
||||
for ($i = 0; $i < count($jobs); $i += 100) {
|
||||
|
||||
$values = array_slice($jobs, $i, 100);
|
||||
|
||||
$this->redis->rPush($to, ...$values);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$this->redis->unwatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next job from the queue.
|
||||
*
|
||||
* @param string $queue
|
||||
* @return array
|
||||
*/
|
||||
protected function retrieveNextJob($queue)
|
||||
{
|
||||
if (!is_null($this->blockFor)) {
|
||||
return $this->blockingPop($queue);
|
||||
}
|
||||
|
||||
$job = $this->redis->lpop($queue);
|
||||
$reserved = false;
|
||||
|
||||
if ($job) {
|
||||
$reserved = json_decode($job);
|
||||
$reserved['attempts']++;
|
||||
$reserved = json_encode($reserved);
|
||||
$this->redis->zAdd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved);
|
||||
}
|
||||
|
||||
return [$job, $reserved];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the next job by blocking-pop.
|
||||
*
|
||||
* @param string $queue
|
||||
* @return array
|
||||
*/
|
||||
protected function blockingPop($queue)
|
||||
{
|
||||
$rawBody = $this->redis->blpop($queue, $this->blockFor);
|
||||
|
||||
if (!empty($rawBody)) {
|
||||
$payload = json_decode($rawBody[1], true);
|
||||
|
||||
$payload['attempts']++;
|
||||
|
||||
$reserved = json_encode($payload);
|
||||
|
||||
$this->redis->zadd($queue . ':reserved', $this->availableAt($this->retryAfter), $reserved);
|
||||
|
||||
return [$rawBody[1], $reserved];
|
||||
}
|
||||
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
*
|
||||
* @param string $queue
|
||||
* @param RedisJob $job
|
||||
* @return void
|
||||
*/
|
||||
public function deleteReserved($queue, $job)
|
||||
{
|
||||
$this->redis->zRem($this->getQueue($queue) . ':reserved', $job->getReservedJob());
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a reserved job from the reserved queue and release it.
|
||||
*
|
||||
* @param string $queue
|
||||
* @param RedisJob $job
|
||||
* @param int $delay
|
||||
* @return void
|
||||
*/
|
||||
public function deleteAndRelease($queue, $job, $delay)
|
||||
{
|
||||
$queue = $this->getQueue($queue);
|
||||
|
||||
$reserved = $job->getReservedJob();
|
||||
|
||||
$this->redis->zRem($queue . ':reserved', $reserved);
|
||||
|
||||
$this->redis->zAdd($queue . ':delayed', $this->availableAt($delay), $reserved);
|
||||
}
|
||||
|
||||
/**
|
||||
* redis事务
|
||||
* @param Closure $closure
|
||||
*/
|
||||
protected function transaction(Closure $closure)
|
||||
{
|
||||
$this->redis->multi();
|
||||
try {
|
||||
call_user_func($closure);
|
||||
if (!$this->redis->exec()) {
|
||||
$this->redis->discard();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->redis->discard();
|
||||
}
|
||||
}
|
||||
|
||||
protected function createPayloadArray($job, $data = '')
|
||||
{
|
||||
return array_merge(parent::createPayloadArray($job, $data), [
|
||||
'id' => $this->getRandomId(),
|
||||
'attempts' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getRandomId()
|
||||
{
|
||||
return Str::random(32);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列名
|
||||
*
|
||||
* @param string|null $queue
|
||||
* @return string
|
||||
*/
|
||||
protected function getQueue($queue)
|
||||
{
|
||||
return 'queues:' . ($queue ?: $this->default);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\connector;
|
||||
|
||||
use Exception;
|
||||
use think\queue\Connector;
|
||||
use think\queue\event\JobFailed;
|
||||
use think\queue\event\JobProcessed;
|
||||
use think\queue\event\JobProcessing;
|
||||
use think\queue\job\Sync as SyncJob;
|
||||
use Throwable;
|
||||
|
||||
class Sync extends Connector
|
||||
{
|
||||
|
||||
public function size($queue = null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function push($job, $data = '', $queue = null)
|
||||
{
|
||||
$queueJob = $this->resolveJob($this->createPayload($job, $data), $queue);
|
||||
|
||||
try {
|
||||
$this->triggerEvent(new JobProcessing($this->connectorName, $job));
|
||||
|
||||
$queueJob->fire();
|
||||
|
||||
$this->triggerEvent(new JobProcessed($this->connectorName, $job));
|
||||
} catch (Exception | Throwable $e) {
|
||||
|
||||
$this->triggerEvent(new JobFailed($this->connectorName, $job, $e));
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function triggerEvent($event)
|
||||
{
|
||||
$this->app->event->trigger($event);
|
||||
}
|
||||
|
||||
public function pop($queue = null)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected function resolveJob($payload, $queue)
|
||||
{
|
||||
return new SyncJob($this->app, $payload, $this->connectorName, $queue);
|
||||
}
|
||||
|
||||
public function pushRaw($payload, $queue = null, array $options = [])
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function later($delay, $job, $data = '', $queue = null)
|
||||
{
|
||||
return $this->push($job, $data, $queue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\event;
|
||||
|
||||
use Exception;
|
||||
use think\queue\Job;
|
||||
|
||||
class JobExceptionOccurred
|
||||
{
|
||||
/**
|
||||
* The connection name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $connectionName;
|
||||
|
||||
/**
|
||||
* The job instance.
|
||||
*
|
||||
* @var Job
|
||||
*/
|
||||
public $job;
|
||||
|
||||
/**
|
||||
* The exception instance.
|
||||
*
|
||||
* @var Exception
|
||||
*/
|
||||
public $exception;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param string $connectionName
|
||||
* @param Job $job
|
||||
* @param Exception $exception
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($connectionName, $job, $exception)
|
||||
{
|
||||
$this->job = $job;
|
||||
$this->exception = $exception;
|
||||
$this->connectionName = $connectionName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\event;
|
||||
|
||||
use think\queue\Job;
|
||||
|
||||
class JobFailed
|
||||
{
|
||||
/** @var string */
|
||||
public $connector;
|
||||
|
||||
/** @var Job */
|
||||
public $job;
|
||||
|
||||
/** @var \Exception */
|
||||
public $exception;
|
||||
|
||||
public function __construct($connector, $job, $exception)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->job = $job;
|
||||
$this->exception = $exception;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\event;
|
||||
|
||||
use think\queue\Job;
|
||||
|
||||
class JobProcessed
|
||||
{
|
||||
/** @var string */
|
||||
public $connector;
|
||||
|
||||
/** @var Job */
|
||||
public $job;
|
||||
|
||||
public function __construct($connector, $job)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->job = $job;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\event;
|
||||
|
||||
use think\queue\Job;
|
||||
|
||||
class JobProcessing
|
||||
{
|
||||
/** @var string */
|
||||
public $connector;
|
||||
|
||||
/** @var Job */
|
||||
public $job;
|
||||
|
||||
public function __construct($connector, $job)
|
||||
{
|
||||
$this->connector = $connector;
|
||||
$this->job = $job;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\event;
|
||||
|
||||
class WorkerStopping
|
||||
{
|
||||
/**
|
||||
* The exit status.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $status;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @param int $status
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($status = 0)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\failed;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use think\Db;
|
||||
use think\queue\FailedJob;
|
||||
|
||||
class Database extends FailedJob
|
||||
{
|
||||
|
||||
/** @var Db */
|
||||
protected $db;
|
||||
|
||||
/**
|
||||
* The database table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table;
|
||||
|
||||
public function __construct(Db $db, $table)
|
||||
{
|
||||
$this->db = $db;
|
||||
$this->table = $table;
|
||||
}
|
||||
|
||||
public static function __make(Db $db, $config)
|
||||
{
|
||||
return new self($db, $config['table']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a failed job into storage.
|
||||
*
|
||||
* @param string $connector
|
||||
* @param string $queue
|
||||
* @param string $payload
|
||||
* @param \Exception $exception
|
||||
* @return int|null
|
||||
*/
|
||||
public function log($connector, $queue, $payload, $exception)
|
||||
{
|
||||
$failed_at = Carbon::now();
|
||||
|
||||
$exception = (string) $exception;
|
||||
|
||||
return $this->getTable()->insertGetId(compact(
|
||||
'connector', 'queue', 'payload', 'exception', 'failed_at'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all of the failed jobs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return $this->getTable()->order('id', 'desc')->select()->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single failed job.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return object|null
|
||||
*/
|
||||
public function find($id)
|
||||
{
|
||||
return $this->getTable()->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single failed job from storage.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return bool
|
||||
*/
|
||||
public function forget($id)
|
||||
{
|
||||
return $this->getTable()->where('id', $id)->delete() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all of the failed jobs from storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
$this->getTable()->delete(true);
|
||||
}
|
||||
|
||||
protected function getTable()
|
||||
{
|
||||
return $this->db->name($this->table);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace think\queue\failed;
|
||||
|
||||
use think\queue\FailedJob;
|
||||
|
||||
class None extends FailedJob
|
||||
{
|
||||
|
||||
/**
|
||||
* Log a failed job into storage.
|
||||
*
|
||||
* @param string $connection
|
||||
* @param string $queue
|
||||
* @param string $payload
|
||||
* @param \Exception $exception
|
||||
*/
|
||||
public function log($connection, $queue, $payload, $exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of all of the failed jobs.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function all()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single failed job.
|
||||
*
|
||||
* @param mixed $id
|
||||
*/
|
||||
public function find($id)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a single failed job from storage.
|
||||
*
|
||||
* @param mixed $id
|
||||
* @return bool
|
||||
*/
|
||||
public function forget($id)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all of the failed jobs from storage.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function flush()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace think\queue\job;
|
||||
|
||||
use think\App;
|
||||
use think\queue\connector\Database as DatabaseQueue;
|
||||
use think\queue\Job;
|
||||
|
||||
class Database extends Job
|
||||
{
|
||||
/**
|
||||
* The database queue instance.
|
||||
* @var DatabaseQueue
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The database job payload.
|
||||
* @var Object
|
||||
*/
|
||||
protected $job;
|
||||
|
||||
public function __construct(App $app, DatabaseQueue $database, $job, $connector, $queue)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->job = $job;
|
||||
$this->queue = $queue;
|
||||
$this->database = $database;
|
||||
$this->connector = $connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
$this->database->deleteReserved($this->job->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新发布任务
|
||||
* @param int $delay
|
||||
* @return void
|
||||
*/
|
||||
public function release($delay = 0)
|
||||
{
|
||||
parent::release($delay);
|
||||
|
||||
$this->delete();
|
||||
|
||||
$this->database->release($this->queue, $this->job, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前任务尝试次数
|
||||
* @return int
|
||||
*/
|
||||
public function attempts()
|
||||
{
|
||||
return (int) $this->job->attempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body string for the job.
|
||||
* @return string
|
||||
*/
|
||||
public function getRawBody()
|
||||
{
|
||||
return $this->job->payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJobId()
|
||||
{
|
||||
return $this->job->id;
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\job;
|
||||
|
||||
use think\App;
|
||||
use think\queue\connector\Redis as RedisQueue;
|
||||
use think\queue\Job;
|
||||
|
||||
class Redis extends Job
|
||||
{
|
||||
|
||||
/**
|
||||
* The redis queue instance.
|
||||
* @var RedisQueue
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
/**
|
||||
* The database job payload.
|
||||
* @var Object
|
||||
*/
|
||||
protected $job;
|
||||
|
||||
/**
|
||||
* The JSON decoded version of "$job".
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $decoded;
|
||||
|
||||
/**
|
||||
* The Redis job payload inside the reserved queue.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $reserved;
|
||||
|
||||
public function __construct(App $app, RedisQueue $redis, $job, $reserved, $connector, $queue)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->job = $job;
|
||||
$this->queue = $queue;
|
||||
$this->connector = $connector;
|
||||
$this->redis = $redis;
|
||||
$this->reserved = $reserved;
|
||||
|
||||
$this->decoded = $this->payload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of times the job has been attempted.
|
||||
* @return int
|
||||
*/
|
||||
public function attempts()
|
||||
{
|
||||
return ($this->decoded['attempts'] ?? null) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body string for the job.
|
||||
* @return string
|
||||
*/
|
||||
public function getRawBody()
|
||||
{
|
||||
return $this->job;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
$this->redis->deleteReserved($this->queue, $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新发布任务
|
||||
*
|
||||
* @param int $delay
|
||||
* @return void
|
||||
*/
|
||||
public function release($delay = 0)
|
||||
{
|
||||
parent::release($delay);
|
||||
|
||||
$this->redis->deleteAndRelease($this->queue, $this, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJobId()
|
||||
{
|
||||
return $this->decoded['id'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying reserved Redis job.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReservedJob()
|
||||
{
|
||||
return $this->reserved;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2006-2015 http://thinkphp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: yunwuxin <448901948@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace think\queue\job;
|
||||
|
||||
use think\App;
|
||||
use think\queue\Job;
|
||||
|
||||
class Sync extends Job
|
||||
{
|
||||
/**
|
||||
* The queue message data.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $payload;
|
||||
|
||||
public function __construct(App $app, $payload, $connector, $queue)
|
||||
{
|
||||
$this->app = $app;
|
||||
$this->connector = $connector;
|
||||
$this->queue = $queue;
|
||||
$this->payload = $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of times the job has been attempted.
|
||||
* @return int
|
||||
*/
|
||||
public function attempts()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw body string for the job.
|
||||
* @return string
|
||||
*/
|
||||
public function getRawBody()
|
||||
{
|
||||
return $this->payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getJobId()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getQueue()
|
||||
{
|
||||
return 'sync';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user