<file_sep><?php
namespace Imi\Db\Aop;

use Imi\Aop\AroundJoinPoint;
use Imi\Aop\Annotation\Around;
use Imi\Aop\Annotation\Aspect;
use Imi\Aop\Annotation\PointCut;
use Imi\Db\Exception\DbException;
use Imi\Util\Traits\Aspect\TDefer;

/**
 * @Aspect
 */
class MysqlAspect
{
	use TDefer;

	/**
	 * Db 延迟收包
	 * @PointCut(
	 * 		allow={
	 * 			"Swoole\Coroutine\MySQL::query",
	 * 			"Swoole\Coroutine\MySQL::prepare",
	 * 		}
	 * )
	 * @Around
	 * @return mixed
	 */
	public function defer(AroundJoinPoint $joinPoint)
	{
		$result = $this->parseDefer($joinPoint);
		if(false === $result)
		{
			$statement = $joinPoint->getTarget();
			throw new DbException('sql query error: [' . $statement->errorCode() . '] ' . $statement->errorInfo() . ' sql: ' . $statement->getSql());
		}
		return $result;
	}

	/**
	 * Statement 延迟收包
	 * @PointCut(
	 * 		allow={
	 * 			"Imi\Db\Drivers\CoroutineMysql\Statement::execute",
	 * 		}
	 * )
	 * @Around
	 * @return mixed
	 */
	public function statementDefer(AroundJoinPoint $joinPoint)
	{
		$statement = $joinPoint->getTarget();
		$client = $statement->getDb()->getInstance();
		// 获取调用前的defer状态
		$isDefer = $client->getDefer();
		if(!$isDefer)
		{
			// 强制设为延迟收包
			$client->setDefer(true);
		}
		// 调用原方法
		$result = $joinPoint->proceed();
		if(!$isDefer)
		{
			// 设为调用前状态
			$client->setDefer(false);
		}
		if(false === $result)
		{
			throw new DbException('sql query error: [' . $statement->errorCode() . '] ' . $statement->errorInfo() . ' sql: ' . $statement->getSql());
		}
		return $result;
	}

}<file_sep><?php
namespace Imi\Util;

abstract class Coroutine extends \Swoole\Coroutine
{
	/**
	 * 是否在协程中
	 * @return boolean
	 */
	public static function isIn()
	{
		return static::getuid() > -1;
	}
}<file_sep><?php
namespace Imi\Process;

use Imi\Process\Parser\ProcessParser;
use Imi\Bean\BeanFactory;
use Imi\Event\Event;

/**
 * 进程管理类
 */
abstract class ProcessManager
{
	/**
	 * 创建进程
	 * 本方法无法在控制器中使用
	 * 返回\Swoole\Process对象实例
	 * 
	 * @param string $name
	 * @param array $args
	 * @param boolean $redirectStdinStdout
	 * @param int $pipeType
	 * @return \Swoole\Process
	 */
	public static function create($name, $args = [], $redirectStdinStdout = null, $pipeType = null): \Swoole\Process
	{
		$processOption = ProcessParser::getInstance()->getProcess($name);
		if(null === $redirectStdinStdout)
		{
			$redirectStdinStdout = $processOption['Process']->redirectStdinStdout;
		}
		if(null === $pipeType)
		{
			$pipeType = $processOption['Process']->pipeType;
		}
		$processInstance = BeanFactory::newInstance($processOption['className'], $args);
		$process = new \Swoole\Process(function(\Swoole\Process $swooleProcess) use($processInstance, $name){
			// 设置进程名称
			$swooleProcess->name($name);
			// 进程开始事件
			Event::trigger('IMI.PROCESS.BEGIN', [
				'name'		=>	$name,
				'process'	=>	$swooleProcess,
			]);
			// 执行任务
			call_user_func([$processInstance, 'run'], $swooleProcess);
			// 进程结束事件
			Event::trigger('IMI.PROCESS.END', [
				'name'		=>	$name,
				'process'	=>	$swooleProcess,
			]);
		}, $redirectStdinStdout, $pipeType);
		return $process;
	}

	/**
	 * 运行进程，同步阻塞等待进程执行返回
	 * 不返回\Swoole\Process对象实例
	 * 执行失败返回false，执行成功返回数组，包含了进程退出的状态码、信号、输出内容。
	 * array(
	 * 	'code' => 0,
	 * 	'signal' => 0,
	 * 	'output' => '',
	 * );
	 *
	 * @param string $name
	 * @param array $args
	 * @param boolean $redirectStdinStdout
	 * @param int $pipeType
	 * @return array
	 */
	public static function run($name, $args = [], $redirectStdinStdout = null, $pipeType = null)
	{
		$cmd = 'php ' . $_SERVER['argv'][0] . ' process/start -name ' . $name;
		if(null !== $redirectStdinStdout)
		{
			$cmd .= ' -redirectStdinStdout ' . $redirectStdinStdout;
		}
		if(null !== $pipeType)
		{
			$cmd .= ' -pipeType ' . $pipeType;
		}
		return \Swoole\Coroutine::exec($cmd);
	}

	/**
	 * 运行进程，创建一个协程执行进程，无法获取进程执行结果
	 * 执行失败返回false，执行成功返回数组，包含了进程退出的状态码、信号、输出内容。
	 * array(
	 * 	'code' => 0,
	 * 	'signal' => 0,
	 * 	'output' => '',
	 * );
	 *
	 * @param string $name
	 * @param array $args
	 * @param boolean $redirectStdinStdout
	 * @param int $pipeType
	 * @return void
	 */
	public static function coRun($name, $args = [], $redirectStdinStdout = null, $pipeType = null)
	{
		go(function() use($name, $args, $redirectStdinStdout, $pipeType){
			static::run($name, $args, $redirectStdinStdout, $pipeType);
		});
	}
}<file_sep><?php
namespace Imi\Util\Traits\Aspect;

use Imi\Aop\AroundJoinPoint;

/**
 * 处理 Swoole 协程客户端延迟收包
 * 适合用于 Aop 切入相应方法时
 */
trait TDefer
{
	private static $hasMulti = [];

	public function parseDefer(AroundJoinPoint $joinPoint)
	{
		$client = $joinPoint->getTarget();
		// 获取调用前的defer状态
		$isDefer = $client->getDefer();
		if(!$isDefer)
		{
			// 强制设为延迟收包
			$client->setDefer(true);
		}
		// 调用原方法
		if($joinPoint->proceed())
		{
			$lowerMethod = strtolower($joinPoint->getMethod());

			$isRecv = true;

			if($this->hasMulti($joinPoint->getTarget()))
			{
				if('exec' === $lowerMethod)
				{
					$this->setHasMulti($joinPoint->getTarget(), false);
				}
				else
				{
					$isRecv = false;
				}
			}
			else
			{
				if('multi' === $lowerMethod)
				{
					$this->setHasMulti($joinPoint->getTarget(), true);
					$isRecv = false;
				}
			}

			if($isRecv)
			{
				// 接收结果
				$result = $client->recv();
			}
			else
			{
				$result = true;
			}

			if(!$isDefer)
			{
				// 设为调用前状态
				$client->setDefer(false);
			}
			return $result;
		}
		else
		{
			return false;
		}
	}

	private function hasMulti($redis)
	{
		$hash = spl_object_hash($redis);
		return static::$hasMulti[$hash] ?? false;
	}

	private function setHasMulti($redis, $has)
	{
		$hash = spl_object_hash($redis);
		static::$hasMulti[$hash] = $has;
	}
}<file_sep><?php
namespace Imi\Log\Handler;

use Imi\Bean\Annotation\Bean;

/**
 * @Bean("ConsoleLog")
 */
class Console extends Base
{
	/**
	 * 真正的保存操作实现
	 * @return void
	 */
    protected function __save()
    {
        foreach($this->records as $record)
        {
            echo $this->getLogString($record), PHP_EOL;
        }
    }
}<file_sep><?php
namespace Imi\Listener;

use Imi\App;
use Imi\Config;
use Imi\Worker;
use Imi\Util\File;
use Imi\Main\Helper;
use Imi\Util\Coroutine;
use Imi\Bean\Annotation;
use Imi\Pool\PoolConfig;
use Imi\Pool\PoolManager;
use Imi\Cache\CacheManager;
use Imi\Bean\Annotation\Listener;
use Imi\Util\CoroutineChannelManager;
use Imi\Server\Event\Param\WorkerStartEventParam;
use Imi\Server\Event\Listener\IWorkerStartEventListener;

/**
 * @Listener(eventName="IMI.MAIN_SERVER.WORKER.START",priority=PHP_INT_MAX)
 */
class WorkerInit implements IWorkerStartEventListener
{
	/**
	 * 事件处理方法
	 * @param EventParam $e
	 * @return void
	 */
	public function handle(WorkerStartEventParam $e)
	{
		$GLOBALS['WORKER_START_END_RESUME_COIDS'] = [];

		// 清除当前 worker 进程的 Bean 类缓存
		$path = Config::get('@app.beanClassCache', sys_get_temp_dir());
		$path = File::path($path, 'imiBeanCache', $e->server->getSwooleServer()->worker_id);
		foreach (File::enum($path) as $file)
		{
			if (is_file($file))
			{
				unlink($file);
			}
		}

		// 当前进程的 WorkerID 设置
		Worker::setWorkerID($e->server->getSwooleServer()->worker_id);

		// 初始化 worker
		App::initWorker();
	}
}<file_sep><?php
namespace Imi\Util;

abstract class ClassObject
{
	/**
	 * 是否是匿名类
	 * @param object $object
	 * @return boolean
	 */
	public static function isAnymous($object)
	{
		return strpos(get_class($object), 'class@anonymous') >= 0;
	}
}<file_sep><?php
namespace Imi\Db\Query;

use Imi\Model\Model;
use Imi\Bean\BeanFactory;
use Imi\Db\Interfaces\IStatement;
use Imi\Db\Query\Interfaces\IResult;

class Result implements IResult
{
	/**
	 * Statement
	 * @var IStatement
	 */
	private $statement;

	/**
	 * 是否执行成功
	 * @var bool
	 */
	private $isSuccess;

	/**
	 * 查询结果类的类名，为null则为数组
	 * @var string
	 */
	private $modelClass;

	public function __construct($statement, $modelClass = null)
	{
		$this->modelClass = $modelClass;
		if($statement instanceof IStatement)
		{
			$this->statement = clone $statement;
			$this->isSuccess = '' === $this->statement->errorInfo();
		}
		else
		{
			$this->isSuccess = false;
		}
	}

	/**
	 * SQL是否执行成功
	 * @return boolean
	 */
	public function isSuccess(): bool
	{
		return $this->isSuccess;
	}

	/**
	 * 获取最后插入的ID
	 * @return string
	 */
	public function getLastInsertId()
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		return $this->statement->lastInsertId();
	}

	/**
	 * 获取影响行数
	 * @return int
	 */
	public function getAffectedRows()
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		return $this->statement->rowCount();
	}

	/**
	 * 返回一行数据，数组或对象
	 * @param string $className 实体类名，为null则返回数组
	 * @return mixed
	 */
	public function get($className = null)
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		$result = $this->statement->fetch();

		if(null === $className)
		{
			$className = $this->modelClass;
		}
		if(null === $className)
		{
			return $result;
		}
		else
		{
			if(is_subclass_of($className, Model::class))
			{
				$object = BeanFactory::newInstance($className, $result);
			}
			else
			{
				$object = BeanFactory::newInstance($className);
				foreach($result as $k => $v)
				{
					$object->$k = $v;
				}
			}
			return $object;
		}
	}

	/**
	 * 返回数组
	 * @param string $className 实体类名，为null则数组每个成员为数组
	 * @return array
	 */
	public function getArray($className = null)
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		$result = $this->statement->fetchAll();

		if(null === $className)
		{
			$className = $this->modelClass;
		}
		if(null === $className)
		{
			return $result;
		}
		else
		{
			$list = [];
			$isModelClass = is_subclass_of($className, Model::class);
			foreach($result as $item)
			{
				if($isModelClass)
				{
					$object = BeanFactory::newInstance($className, $item);
				}
				else
				{
					$object = $item;
				}
				$list[] = $object;
			}
			return $list;
		}
	}

	/**
	 * 获取一列数据
	 * @return array
	 */
	public function getColumn($column = 0)
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		return $this->statement->fetchAll(\PDO::FETCH_COLUMN, $column);
	}

	/**
	 * 获取标量结果
	 * @param integer|string $columnKey
	 * @return mixed
	 */
	public function getScalar($columnKey = 0)
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		return $this->statement->fetchColumn();
	}
	
	/**
	 * 获取记录行数
	 * @return int
	 */
	public function getRowCount()
	{
		if(!$this->isSuccess)
		{
			throw new \RuntimeException('Result is not success!');
		}
		return count($this->statement->fetchAll());
	}
}<file_sep><?php
namespace Imi\Tool\Listener;

use Imi\App;
use Imi\Util\Args;
use Imi\Main\Helper;
use Imi\Tool\ArgType;
use Imi\RequestContext;
use Imi\Bean\Annotation;
use Imi\Pool\PoolConfig;
use Imi\Event\EventParam;
use Imi\Pool\PoolManager;
use Imi\Cache\CacheManager;
use Imi\Tool\Annotation\Arg;
use Imi\Event\IEventListener;
use Imi\Tool\Parser\ToolParser;
use Imi\Bean\Annotation\Listener;

/**
 * @Listener(eventName="IMI.INITED")
 */
class Init implements IEventListener
{
	/**
	 * 事件处理方法
	 * @param EventParam $e
	 * @return void
	 */
	public function handle(EventParam $e)
	{
		try{
			if(!isset($_SERVER['argv'][1]))
			{
				throw new \RuntimeException(sprintf('tool args error!'));
			}
			if(false === strpos($_SERVER['argv'][1], '/'))
			{
				throw new \RuntimeException(sprintf('tool name and operation not found!'));
			}
			$this->init();
			// cli参数初始化
			Args::init(2);
			// 工具名/操作名
			list($tool, $operation) = explode('/', $_SERVER['argv'][1]);
			// 获取回调
			$callable = ToolParser::getInstance()->getCallable($tool, $operation);
			if(null === $callable)
			{
				Annotation::getInstance()->init([Helper::getMain(App::getNamespace())]);
			}
			$callable = ToolParser::getInstance()->getCallable($tool, $operation);
			if(null === $callable)
			{
				throw new \RuntimeException(sprintf('tool %s does not exists!', $_SERVER['argv'][1]));
			}
			if(Args::get('h'))
			{
				// 帮助
				$className = get_parent_class($callable[0]);
				$refClass = new \ReflectionClass($className);
				$required = [];
				$other = [];
				foreach(ToolParser::getInstance()->getData()['class'][$className]['Methods'][$callable[1]]['Args'] ?? [] as $arg)
				{
					if($arg->required)
					{
						$required[] = $arg;
					}
					else
					{
						$other[] = $arg;
					}
				}
				echo '工具名称：', $tool, '/', $operation, PHP_EOL, PHP_EOL;
				echo $this->parseComment($refClass->getMethod($callable[1])->getDocComment()), PHP_EOL;
				if(isset($required[0]))
				{
					echo PHP_EOL, '必选参数：', PHP_EOL;
					foreach($required as $arg)
					{
						echo '-', $arg->name, ' ', $arg->comments, PHP_EOL;
					}
				}
				if(isset($other[0]))
				{
					echo PHP_EOL, '可选参数：', PHP_EOL;
					foreach($other as $arg)
					{
						echo '-', $arg->name, ' ', $arg->comments, PHP_EOL;
					}
				}
			}
			else
			{
				// 执行参数
				$args = $this->getCallToolArgs($callable, $tool, $operation);
				// 执行工具操作
				call_user_func_array($callable, $args);
				swoole_event_wait();
			}
		}
		catch(\Throwable $ex)
		{
			echo $ex->getMessage(), PHP_EOL;
		}
	}

	/**
	 * 初始化
	 * @return void
	 */
	private function init()
	{
		RequestContext::create();
		// 获取配置
		$pools = $caches = [];
		foreach(Helper::getMains() as $main)
		{
			$pools = array_merge($pools, $main->getConfig()['pools'] ?? []);
			$caches = array_merge($caches, $main->getConfig()['caches'] ?? []);
		}
		// 同步池子初始化
		foreach($pools as $name => $pool)
		{
			if(isset($pool['sync']))
			{
				$pool = $pool['sync'];
				PoolManager::addName($name, $pool['pool']['class'], new PoolConfig($pool['pool']['config']), $pool['resource']);
			}
		}
		// 缓存初始化
		foreach($caches as $name => $cache)
		{
			CacheManager::addName($name, $cache['handlerClass'], $cache['option']);
		}
	}

	/**
	 * 获取执行参数
	 * @param callable $callable
	 * @param string $tool
	 * @param string $operation
	 * @return array
	 */
	private function getCallToolArgs($callable, $tool, $operation)
	{
		$className = get_parent_class($callable[0]);
		$methodRef = new \ReflectionMethod($className, $callable[1]);
		$args = [];
		foreach(ToolParser::getInstance()->getData()['class'][$className]['Methods'][$methodRef->name]['Args'] ?? [] as $annotation)
		{
			if(Args::exists($annotation->name))
			{
				$value = $this->parseArgValue(Args::get($annotation->name), $annotation);
			}
			else if($annotation->required)
			{
				throw new \InvalidArgumentException(sprintf('tool %s/%s param %s is required', $tool, $operation, $annotation->name));
			}
			else
			{
				$value = $annotation->default;
			}
			$args[] = $value;
		}
		return $args;
	}

	/**
	 * 处理参数值
	 * @param string $value
	 * @param Arg $annotation
	 * @return mixed
	 */
	private function parseArgValue($value, Arg $annotation)
	{
		switch($annotation->type)
		{
			case ArgType::STRING:
				break;
			case ArgType::INT:
				$value = (int)$value;
				break;
			case ArgType::FLOAT:
			case ArgType::DOUBLE:
				$value = (float)$value;
				break;
			case ArgType::BOOL:
			case ArgType::BOOLEAN:
				$value = (bool)json_decode($value);
				break;
			case ArgType::ARRAY:
				$value = explode(',', $value);
				break;
		}
		return $value;
	}

	/**
	 * 处理注释
	 * @param string $content
	 * @return string
	 */
	private function parseComment($content)
	{
		return trim(preg_replace('/@.+\n/', '', preg_replace('/\/*\s*\*\s*\/*/', PHP_EOL, $content)));
	}
}<file_sep><?php
namespace Imi\Util;

abstract class Text
{
	/**
	 * 字符串是否以另一个字符串开头
	 * @param string $string
	 * @param string $compare
	 * @return string
	 */
	public static function startwith($string, $compare)
	{
		return 0 === strpos($string, $compare);
	}

	/**
	 * 字符串是否以另一个字符串结尾
	 * @param string $string
	 * @param string $compare
	 * @return string
	 */
	public static function endwith($string, $compare)
	{
		return substr($string, -strlen($compare)) === $compare;
	}

	/**
	 * 插入字符串
	 * @param string $string 原字符串
	 * @param int $position 位置
	 * @param string $insertString 被插入的字符串
	 * @return string
	 */
	public static function insert($string, $position, $insertString)
	{
		return substr_replace($string, $insertString, $position, 0);
	}

	/**
	 * 字符串是否为空字符串或者为null
	 * @param string $string
	 * @return boolean
	 */
	public static function isEmpty($string)
	{
		return '' === $string || null === $string;
	}

	/**
	 * 转为驼峰命名，会把下划线后字母转为大写
	 * @param string $name
	 * @return string
	 */
	public static function toCamelName($name)
	{
		return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $name))));
	}

	/**
	 * 转为每个单词大写的命名，会把下划线后字母转为大写
	 * @param string $name
	 * @return string
	 */
	public static function toPascalName($name)
	{
		return ucfirst(static::toCamelName($name));
	}
}<file_sep><?php
namespace Imi\Redis\Aop;

use Imi\Aop\AroundJoinPoint;
use Imi\Aop\Annotation\Around;
use Imi\Aop\Annotation\Aspect;
use Imi\Aop\Annotation\PointCut;
use Imi\Util\Traits\Aspect\TDefer;

/**
 * @Aspect
 */
class RedisAspect
{
	use TDefer;

	/**
	 * Redis 延迟收包
	 * @PointCut(
	 * 		allow={
	 * 			"Swoole\Coroutine\Redis::set",
	 * 			"Swoole\Coroutine\Redis::setBit",
	 * 			"Swoole\Coroutine\Redis::setEx",
	 * 			"Swoole\Coroutine\Redis::psetEx",
	 * 			"Swoole\Coroutine\Redis::lSet",
	 * 			"Swoole\Coroutine\Redis::get",
	 * 			"Swoole\Coroutine\Redis::mGet",
	 * 			"Swoole\Coroutine\Redis::del",
	 * 			"Swoole\Coroutine\Redis::hDel",
	 * 			"Swoole\Coroutine\Redis::hSet",
	 * 			"Swoole\Coroutine\Redis::hMSet",
	 * 			"Swoole\Coroutine\Redis::hSetNx",
	 * 			"Swoole\Coroutine\Redis::delete",
	 * 			"Swoole\Coroutine\Redis::mSet",
	 * 			"Swoole\Coroutine\Redis::mSetNx",
	 * 			"Swoole\Coroutine\Redis::getKeys",
	 * 			"Swoole\Coroutine\Redis::keys",
	 * 			"Swoole\Coroutine\Redis::exists",
	 * 			"Swoole\Coroutine\Redis::type",
	 * 			"Swoole\Coroutine\Redis::strLen",
	 * 			"Swoole\Coroutine\Redis::lPop",
	 * 			"Swoole\Coroutine\Redis::blPop",
	 * 			"Swoole\Coroutine\Redis::rPop",
	 * 			"Swoole\Coroutine\Redis::brPop",
	 * 			"Swoole\Coroutine\Redis::bRPopLPush",
	 * 			"Swoole\Coroutine\Redis::lSize",
	 * 			"Swoole\Coroutine\Redis::lLen",
	 * 			"Swoole\Coroutine\Redis::sSize",
	 * 			"Swoole\Coroutine\Redis::scard",
	 * 			"Swoole\Coroutine\Redis::sPop",
	 * 			"Swoole\Coroutine\Redis::sMembers",
	 * 			"Swoole\Coroutine\Redis::sGetMembers",
	 * 			"Swoole\Coroutine\Redis::sRandMember",
	 * 			"Swoole\Coroutine\Redis::persist",
	 * 			"Swoole\Coroutine\Redis::ttl",
	 * 			"Swoole\Coroutine\Redis::pttl",
	 * 			"Swoole\Coroutine\Redis::zCard",
	 * 			"Swoole\Coroutine\Redis::zSize",
	 * 			"Swoole\Coroutine\Redis::hLen",
	 * 			"Swoole\Coroutine\Redis::hKeys",
	 * 			"Swoole\Coroutine\Redis::hVals",
	 * 			"Swoole\Coroutine\Redis::hGetAll",
	 * 			"Swoole\Coroutine\Redis::debug",
	 * 			"Swoole\Coroutine\Redis::restore",
	 * 			"Swoole\Coroutine\Redis::dump",
	 * 			"Swoole\Coroutine\Redis::renameKey",
	 * 			"Swoole\Coroutine\Redis::rename",
	 * 			"Swoole\Coroutine\Redis::renameNx",
	 * 			"Swoole\Coroutine\Redis::rpoplpush",
	 * 			"Swoole\Coroutine\Redis::randomKey",
	 * 			"Swoole\Coroutine\Redis::ping",
	 * 			"Swoole\Coroutine\Redis::auth",
	 * 			"Swoole\Coroutine\Redis::unwatch",
	 * 			"Swoole\Coroutine\Redis::watch",
	 * 			"Swoole\Coroutine\Redis::save",
	 * 			"Swoole\Coroutine\Redis::bgSave",
	 * 			"Swoole\Coroutine\Redis::lastSave",
	 * 			"Swoole\Coroutine\Redis::flushDB",
	 * 			"Swoole\Coroutine\Redis::flushAll",
	 * 			"Swoole\Coroutine\Redis::dbSize",
	 * 			"Swoole\Coroutine\Redis::bgrewriteaof",
	 * 			"Swoole\Coroutine\Redis::time",
	 * 			"Swoole\Coroutine\Redis::role",
	 * 			"Swoole\Coroutine\Redis::setRange",
	 * 			"Swoole\Coroutine\Redis::setNx",
	 * 			"Swoole\Coroutine\Redis::getSet",
	 * 			"Swoole\Coroutine\Redis::append",
	 * 			"Swoole\Coroutine\Redis::lPushx",
	 * 			"Swoole\Coroutine\Redis::lPush",
	 * 			"Swoole\Coroutine\Redis::rPush",
	 * 			"Swoole\Coroutine\Redis::rPushx",
	 * 			"Swoole\Coroutine\Redis::sContains",
	 * 			"Swoole\Coroutine\Redis::sismember",
	 * 			"Swoole\Coroutine\Redis::zScore",
	 * 			"Swoole\Coroutine\Redis::zRank",
	 * 			"Swoole\Coroutine\Redis::zRevRank",
	 * 			"Swoole\Coroutine\Redis::hGet",
	 * 			"Swoole\Coroutine\Redis::hMGet",
	 * 			"Swoole\Coroutine\Redis::hExists",
	 * 			"Swoole\Coroutine\Redis::publish",
	 * 			"Swoole\Coroutine\Redis::zIncrBy",
	 * 			"Swoole\Coroutine\Redis::zAdd",
	 * 			"Swoole\Coroutine\Redis::zDeleteRangeByScore",
	 * 			"Swoole\Coroutine\Redis::zRemRangeByScore",
	 * 			"Swoole\Coroutine\Redis::zCount",
	 * 			"Swoole\Coroutine\Redis::zRange",
	 * 			"Swoole\Coroutine\Redis::zRevRange",
	 * 			"Swoole\Coroutine\Redis::zRangeByScore",
	 * 			"Swoole\Coroutine\Redis::zRevRangeByScore",
	 * 			"Swoole\Coroutine\Redis::zRangeByLex",
	 * 			"Swoole\Coroutine\Redis::zRevRangeByLex",
	 * 			"Swoole\Coroutine\Redis::zInter",
	 * 			"Swoole\Coroutine\Redis::zinterstore",
	 * 			"Swoole\Coroutine\Redis::zUnion",
	 * 			"Swoole\Coroutine\Redis::zunionstore",
	 * 			"Swoole\Coroutine\Redis::incrBy",
	 * 			"Swoole\Coroutine\Redis::hIncrBy",
	 * 			"Swoole\Coroutine\Redis::incr",
	 * 			"Swoole\Coroutine\Redis::decrBy",
	 * 			"Swoole\Coroutine\Redis::decr",
	 * 			"Swoole\Coroutine\Redis::getBit",
	 * 			"Swoole\Coroutine\Redis::lInsert",
	 * 			"Swoole\Coroutine\Redis::lGet",
	 * 			"Swoole\Coroutine\Redis::lIndex",
	 * 			"Swoole\Coroutine\Redis::setTimeout",
	 * 			"Swoole\Coroutine\Redis::expire",
	 * 			"Swoole\Coroutine\Redis::pexpire",
	 * 			"Swoole\Coroutine\Redis::expireAt",
	 * 			"Swoole\Coroutine\Redis::pexpireAt",
	 * 			"Swoole\Coroutine\Redis::move",
	 * 			"Swoole\Coroutine\Redis::select",
	 * 			"Swoole\Coroutine\Redis::getRange",
	 * 			"Swoole\Coroutine\Redis::listTrim",
	 * 			"Swoole\Coroutine\Redis::ltrim",
	 * 			"Swoole\Coroutine\Redis::lGetRange",
	 * 			"Swoole\Coroutine\Redis::lRange",
	 * 			"Swoole\Coroutine\Redis::lRem",
	 * 			"Swoole\Coroutine\Redis::lRemove",
	 * 			"Swoole\Coroutine\Redis::zDeleteRangeByRank",
	 * 			"Swoole\Coroutine\Redis::zRemRangeByRank",
	 * 			"Swoole\Coroutine\Redis::incrByFloat",
	 * 			"Swoole\Coroutine\Redis::hIncrByFloat",
	 * 			"Swoole\Coroutine\Redis::bitCount",
	 * 			"Swoole\Coroutine\Redis::bitOp",
	 * 			"Swoole\Coroutine\Redis::sAdd",
	 * 			"Swoole\Coroutine\Redis::sMove",
	 * 			"Swoole\Coroutine\Redis::sDiff",
	 * 			"Swoole\Coroutine\Redis::sDiffStore",
	 * 			"Swoole\Coroutine\Redis::sUnion",
	 * 			"Swoole\Coroutine\Redis::sUnionStore",
	 * 			"Swoole\Coroutine\Redis::sInter",
	 * 			"Swoole\Coroutine\Redis::sInterStore",
	 * 			"Swoole\Coroutine\Redis::sRemove",
	 * 			"Swoole\Coroutine\Redis::srem",
	 * 			"Swoole\Coroutine\Redis::zDelete",
	 * 			"Swoole\Coroutine\Redis::zRemove",
	 * 			"Swoole\Coroutine\Redis::zRem",
	 * 			"Swoole\Coroutine\Redis::pSubscribe",
	 * 			"Swoole\Coroutine\Redis::subscribe",
	 * 			"Swoole\Coroutine\Redis::multi",
	 * 			"Swoole\Coroutine\Redis::exec",
	 * 			"Swoole\Coroutine\Redis::eval",
	 * 			"Swoole\Coroutine\Redis::evalSha",
	 * 			"Swoole\Coroutine\Redis::script"
	 * 		}
	 * )
	 * @Around
	 * @return mixed
	 */
	public function defer(AroundJoinPoint $joinPoint)
	{
		return $this->parseDefer($joinPoint);
	}
}<file_sep><?php
namespace Imi\Tool\Tools\Process;

use Imi\App;
use Imi\Util\Args;
use Imi\Tool\ArgType;
use Imi\Tool\Annotation\Arg;
use Imi\Tool\Annotation\Tool;
use Imi\Process\ProcessManager;
use Imi\Tool\Annotation\Operation;

/**
 * @Tool("process")
 */
class Process
{
	/**
	 * 开启一个进程，可以任意添加参数
	 * 
	 * @Operation("start")
	 *
	 * @Arg(name="name", type=ArgType::STRING, required=true, comments="进程名称，通过@Process注解定义")
	 * @Arg(name="redirectStdinStdout", type=ArgType::STRING, default=null, comments="重定向子进程的标准输入和输出。启用此选项后，在子进程内输出内容将不是打印屏幕，而是写入到主进程管道。读取键盘输入将变为从管道中读取数据。默认为阻塞读取。")
	 * @Arg(name="pipeType", type=ArgType::STRING, default=null, comments="管道类型，启用$redirectStdinStdout后，此选项将忽略用户参数，强制为1。如果子进程内没有进程间通信，可以设置为 0")
	 * 
	 * @return void
	 */
	public function start($name, $redirectStdinStdout, $pipeType)
	{
		App::initWorker();
		$args = Args::get();
		$process = ProcessManager::create($name, $args, $redirectStdinStdout, $pipeType);
		$process->start();
		$result = \swoole_process::wait(true);
		echo 'process exit! pid:', $result['pid'], ', code:', $result['code'], ', signal:', $result['signal'], PHP_EOL;
	}
}<file_sep><?php
namespace Imi\Util;

abstract class ArrayUtil
{
	/**
	 * 从数组中移除一个元素
	 * @param array $array
	 * @param mixed $value
	 * @return array
	 */
	public static function remove($array, $value)
	{
		$index = array_search($array, $value);
		if(false !== $index)
		{
			return array_splice($array, $index, 1);
		}
		else
		{
			return $array;
		}
	}

	/**
	 * 多维数组递归合并
	 * @param array ...$arrays
	 * @return array
	 */
	public static function recursiveMerge(...$arrays)
	{
		$merged = array ();
		foreach($arrays as $array)
		{
			if (!is_array($array))
			{
				continue;
			}
			foreach ( $array as $key => $value )
			{
				if (is_string ( $key ))
				{
					if (is_array ( $value ) && isset($merged[$key]) && is_array ( $merged [$key] ))
					{
						$merged [$key] = static::recursiveMerge ( $merged [$key], $value );
					}
					else
					{
						$merged [$key] = $value;
					}
				}
				else
				{
					$merged [] = $value;
				}
			}
		}
		return $merged;
	}

	/**
	 * 将二维数组第二纬某key变为一维的key
	 * @param array $array 原数组
	 * @param string $column 列名
	 * @param boolean $keepOld 是否保留列名，默认保留
	 */
	public static function columnToKey(&$array, $column, $keepOld = true)
	{
		$s = count($array);
		for($i = 0; $i < $s; ++$i)
		{
			$array[$array[$i][$column]] = $array[$i];
			if(!$keepOld)
			{
				unset($array[$i]);
			}
		}
	}

	/**
	 * 判断数组是否为关联数组
	 * @param array $array 
	 * @return bool 
	 */
	public static function isAssoc($array)
	{  
		return array_keys($array) !== range(0, count($array) - 1);  
	}
}<file_sep><?php
namespace Imi\Util;

use Imi\App;
use Imi\Main\Helper;
use Imi\Bean\BeanProxy;
use Imi\Bean\Parser\BeanParser;

/**
 * 框架里杂七杂八的各种工具方法
 */
abstract class Imi
{
	/**
	 * 处理规则，暂只支持通配符*
	 * @param string $rule
	 * @return string
	 */
	public static function parseRule($rule)
	{
		return \str_replace('\\*', '.*', \preg_quote($rule));
	}

	/**
	 * 检查规则是否匹配，支持通配符*
	 * @param string $rule
	 * @param string $string
	 * @return boolean
	 */
	public static function checkRuleMatch($rule, $string)
	{
		$rule = '/^' . static::parseRule($rule) . '$/';
		return \preg_match($rule, $string) > 0;
	}

	/**
	 * 检查类和方法是否匹配，支持通配符*
	 * @param string $rule
	 * @param string $className
	 * @param string $methodName
	 * @return boolean
	 */
	public static function checkClassMethodRule($rule, $className, $methodName)
	{
		list($classRule, $methodRule) = explode('::', $rule, 2);
		return static::checkRuleMatch($classRule, $className) && static::checkRuleMatch($methodRule, $methodName);
	}

	/**
	 * 检查类是否匹配，支持通配符*
	 * @param string $rule
	 * @param string $className
	 * @return boolean
	 */
	public static function checkClassRule($rule, $className)
	{
		list($classRule, ) = explode('::', $rule, 2);
		return static::checkRuleMatch($classRule, $className);
	}

	/**
	 * 检查验证比较规则集
	 * @param string|array $rules
	 * @param callable $valueCallback
	 * @return boolean
	 */
	public static function checkCompareRules($rules, $valueCallback)
	{
		foreach(is_array($rules) ? $rules : [$rules] as $fieldName => $rule)
		{
			if(is_numeric($fieldName))
			{
				if(!static::checkCompareRule($rule, $valueCallback))
				{
					return false;
				}
			}
			else if(preg_match('/^' . $rule . '$/', call_user_func($valueCallback, $fieldName)) <= 0)
			{
				return false;
			}
		}
		return true;
	}

	/**
	 * 检查验证比较规则，如果符合规则返回bool，不符合规则返回null
	 * id=1
	 * id!=1 id<>1
	 * id
	 * !id
	 * @param string $rule
	 * @param callable $valueCallback
	 * @return boolean
	 */
	public static function checkCompareRule($rule, $valueCallback)
	{
		if(isset($rule[0]) && '!' === $rule[0])
		{
			// 不应该存在参数支持
			return null === call_user_func($valueCallback, substr($rule, 1));
		}
		else if(preg_match('/([^!<=]+)(!=|<>|=)(.+)/', $rule, $matches) > 0)
		{
			$value = call_user_func($valueCallback, $matches[1]);
			switch($matches[2])
			{
				case '!=':
				case '<>':
					return null !== $value && $value != $matches[3];
				case '=':
					return $value == $matches[3];
				default:
					return false;
			}
		}
		else
		{
			return null !== call_user_func($valueCallback, $rule);
		}
	}

	/**
	 * 检查验证比较值集
	 * @param string|array $rules
	 * @param mixed $value
	 * @return boolean
	 */
	public static function checkCompareValues($rules, $value)
	{
		foreach(is_array($rules) ? $rules : [$rules] as $rule)
		{
			if(!static::checkCompareValue($rule, $value))
			{
				return false;
			}
		}
		return true;
	}

	/**
	 * 检查验证比较值
	 * @param string|array $rule
	 * @param mixed $value
	 * @return boolean
	 */
	public static function checkCompareValue($rule, $value)
	{
		if(isset($rule[0]) && '!' === $rule[0])
		{
			// 不等
			return $value !== $rule;
		}
		else
		{
			// 相等
			return $value === $rule;
		}
	}

	/**
	 * 处理按.分隔的规则文本，支持\.转义不分隔
	 * @param string $rule
	 */
	public static function parseDotRule($rule)
	{
		$result = preg_split('#(?<!\\\)\.#', $rule);
		array_walk($result, function(&$value, $key){
			if(false !== strpos($value,'\.'))
			{
				$value = str_replace('\.', '.', $value);
			}
		});
		return $result;
	}

	/**
	 * 获取类短名称
	 * @param string $className
	 * @return string
	 */
	public static function getClassShortName(string $className)
	{
		return implode('', array_slice(explode('\\', $className), -1));
	}

	/**
	 * 根据命名空间获取真实路径
	 * @param string $namespace
	 * @return string
	 */
	public static function getNamespacePath($namespace)
	{
		$appNamespace = App::getNamespace();
		$appMain = Helper::getMain($appNamespace);
		$refClass = new \ReflectionClass($appMain);
		$path = dirname($refClass->getFileName());
		$namespaceSubPath = substr($namespace, strlen($appNamespace));
		return File::path($path, str_replace('\\', DIRECTORY_SEPARATOR, $namespaceSubPath));
	}

	/**
	 * 获取类属性的值，支持传入Bean名称
	 *
	 * @param string $className
	 * @param string $propertyName
	 * @return mixed
	 */
	public static function getClassPropertyValue($className, $propertyName)
	{
		$value = BeanProxy::getInjectValue($className, $propertyName);
		if(null === $value)
		{
			if(!class_exists($className))
			{
				$className = BeanParser::getInstance()->getData()[$className]['className'];
			}
			$ref = new \ReflectionClass($className);
			$value = $ref->getDefaultProperties()[$propertyName] ?? null;
		}
		return $value;
	}
}