diff --git a/src/App.php b/src/App.php index 93bf786110..f31a015ee6 100644 --- a/src/App.php +++ b/src/App.php @@ -15,6 +15,7 @@ use Imi\Server\Http\Server; use Imi\Main\Helper as MainHelper; use Imi\Util\CoroutineChannelManager; +use Imi\Util\Imi; abstract class App { @@ -87,14 +88,10 @@ private static function initFramework() private static function clearBeanCache() { // 清除框架 Bean类 缓存 - $path = Config::get('@app.beanClassCache', sys_get_temp_dir()); - $path = File::path($path, 'imiBeanCache', 'imi'); + $path = Imi::getImiClassCachePath(); foreach (File::enum($path) as $file) { - if (is_file($file)) - { - unlink($file); - } + unlink($file); } } @@ -248,11 +245,15 @@ public static function initWorker() } } } + // 缓存初始化 - $caches = $main->getConfig()['caches'] ?? []; - foreach($caches as $name => $cache) + foreach($appMains as $main) { - CacheManager::addName($name, $cache['handlerClass'], $cache['option']); + $caches = $main->getConfig()['caches'] ?? []; + foreach($caches as $name => $cache) + { + CacheManager::addName($name, $cache['handlerClass'], $cache['option']); + } } } } \ No newline at end of file diff --git a/src/Bean/AnnotationLoader.php b/src/Bean/AnnotationLoader.php index a71e71a3c8..1d290553ea 100644 --- a/src/Bean/AnnotationLoader.php +++ b/src/Bean/AnnotationLoader.php @@ -23,6 +23,7 @@ public function loadModuleAnnotations($namespace, $callback) $pathLength = strlen($path); foreach(File::enumPHPFile($path) as $filePath) { + $filePath = $filePath[0]; $diffPath = substr($filePath, $pathLength); if(isset($diffPath[0]) && DIRECTORY_SEPARATOR === $diffPath[0]) { diff --git a/src/Bean/BeanFactory.php b/src/Bean/BeanFactory.php index 2dbcdaca31..57dbd541ee 100644 --- a/src/Bean/BeanFactory.php +++ b/src/Bean/BeanFactory.php @@ -7,6 +7,7 @@ use Imi\RequestContext; use Imi\Util\ClassObject; use Imi\Bean\Parser\BeanParser; +use Imi\Util\Imi; abstract class BeanFactory { @@ -29,7 +30,15 @@ public static function newInstance($class, ...$args) { File::createDir($path); } - File::writeFile($cacheFileName, ' '4.0.4') + { + File::writeFile($cacheFileName, 'name}({$paramsTpls['define']}){$methodReturnType} { - \$__args__ = [{$paramsTpls['args']}];{$paramsTpls['args_variadic']} + \$__args__ = func_get_args(); + {$paramsTpls['set_args']} return \$this->beanProxy->call( '{$method->name}', function({$paramsTpls['define']}){ - return parent::{$method->name}({$paramsTpls['call']}); + \$__args__ = func_get_args(); + {$paramsTpls['set_args']} + return parent::{$method->name}(...\$__args__); }, \$__args__ ); @@ -139,10 +158,11 @@ private static function getMethodParamTpls(\ReflectionMethod $method) 'args' => [], 'define' => [], 'call' => [], + 'set_args' => '', ]; - foreach($method->getParameters() as $param) + foreach($method->getParameters() as $i => $param) { - // 数组参数,支持引用传参 + // 数组参数,支持可变传参 if(!$param->isVariadic()) { $result['args'][] = static::getMethodParamArgsTpl($param); @@ -151,8 +171,13 @@ private static function getMethodParamTpls(\ReflectionMethod $method) $result['define'][] = static::getMethodParamDefineTpl($param); // 调用传参 $result['call'][] = static::getMethodParamCallTpl($param); + // 引用传参 + if($param->isPassedByReference()) + { + $result['set_args'] .= '$__args__[' . $i . '] = &$' . $param->name . ';'; + } } - foreach($result as &$item) + foreach($result as $key => &$item) { if(is_array($item)) { @@ -164,23 +189,6 @@ private static function getMethodParamTpls(\ReflectionMethod $method) { $result['call'] = '...func_get_args()'; } - // 可变参数 - if(isset($param) && $param->isVariadic()) - { - $result['args_variadic'] = static::getMethodArgsVariadicTpl($param); - } - else - { - $result['args_variadic'] = ''; - } - $result['args_variadic'] .= <<isVariadic() ? '...' : '') . '$' . $param->name; } - /** - * 获取方法可变参数模版 - * @param \ReflectionParameter $param - * @return string - */ - private static function getMethodArgsVariadicTpl(\ReflectionParameter $param) - { - return <<name} as \$__item__) - { - \$__args__[] = \$__item__; - } -TPL; - } - /** * 获取方法返回值模版 * @param \ReflectionMethod $method diff --git a/src/Db/Drivers/CoroutineMysql/Driver.php b/src/Db/Drivers/CoroutineMysql/Driver.php index 2142388ca5..18554cd562 100644 --- a/src/Db/Drivers/CoroutineMysql/Driver.php +++ b/src/Db/Drivers/CoroutineMysql/Driver.php @@ -44,7 +44,7 @@ class Driver implements IDb * 参数格式: * [ * 'host' => 'MySQL IP地址', - * 'user' => '数据用户', + * 'username' => '数据用户', * 'password' => '数据库密码', * 'database' => '数据库名', * 'port' => 'MySQL端口 默认3306 可选参数', @@ -57,9 +57,13 @@ class Driver implements IDb public function __construct($option = []) { $this->option = $option; - if(!isset($this->option['username'])) + if(isset($this->option['username'])) { - $this->option['username'] = 'root'; + $this->option['user'] = $this->option['username']; + } + else + { + $this->option['user'] = 'root'; } if(!isset($option['password'])) { diff --git a/src/Db/Drivers/PdoMysql/Driver.php b/src/Db/Drivers/PdoMysql/Driver.php index eb1159b6e6..251270286a 100644 --- a/src/Db/Drivers/PdoMysql/Driver.php +++ b/src/Db/Drivers/PdoMysql/Driver.php @@ -42,7 +42,7 @@ class Driver implements IDb * 参数格式: * [ * 'host' => 'MySQL IP地址', - * 'user' => '数据用户', + * 'username' => '数据用户', * 'password' => '数据库密码', * 'database' => '数据库名', * 'port' => 'MySQL端口 默认3306 可选参数', diff --git a/src/Db/Query/Interfaces/IResult.php b/src/Db/Query/Interfaces/IResult.php index 31bf99a53d..bec6fb2d7c 100644 --- a/src/Db/Query/Interfaces/IResult.php +++ b/src/Db/Query/Interfaces/IResult.php @@ -1,6 +1,8 @@ statement->fetch(); + if(false === $result) + { + return null; + } if(null === $className) { @@ -115,9 +119,9 @@ public function get($className = null) } /** - * 返回数组 + * 返回数组,失败返回null * @param string $className 实体类名,为null则数组每个成员为数组 - * @return array + * @return array|null */ public function getArray($className = null) { @@ -126,6 +130,10 @@ public function getArray($className = null) throw new \RuntimeException('Result is not success!'); } $result = $this->statement->fetchAll(); + if(false === $result) + { + return null; + } if(null === $className) { @@ -194,4 +202,24 @@ public function getRowCount() } return count($this->statement->fetchAll()); } + + /** + * 获取执行的SQL语句 + * + * @return string + */ + public function getSql() + { + return $this->statement->getSql(); + } + + /** + * 获取结果集对象 + * + * @return \Imi\Db\Interfaces\IStatement + */ + public function getStatement(): IStatement + { + return $this->statement; + } } \ No newline at end of file diff --git a/src/HotUpdate/HotUpdateProcess.php b/src/HotUpdate/HotUpdateProcess.php index 5d71247a14..fe3f9616bf 100644 --- a/src/HotUpdate/HotUpdateProcess.php +++ b/src/HotUpdate/HotUpdateProcess.php @@ -8,11 +8,10 @@ use Imi\Process\BaseProcess; use Imi\Bean\Annotation\Bean; use Imi\Process\Annotation\Process; -use Imi\Util\Args; /** * @Bean("hotUpdate") - * @Process("hotUpdate") + * @Process(name="hotUpdate", unique=true) */ class HotUpdateProcess extends BaseProcess { @@ -65,12 +64,10 @@ public function run(\Swoole\Process $process) ]; } go(function(){ + echo 'Process [hotUpdate] start', PHP_EOL; $monitor = BeanFactory::newInstance($this->monitorClass, array_merge($this->defaultPath, $this->includePaths), $this->excludePaths); - $reloadCmd = 'php ' . $_SERVER['argv'][0] . ' server/reload'; - if(null !== ($appNamespace = Args::get('appNamespace'))) - { - $reloadCmd .= ' -appNamespace "' . $appNamespace . '"'; - } + + $reloadCmd = Imi::getImiCmd('server', 'reload'); $time = 0; while(true) { diff --git a/src/Listener/OnManagerStart.php b/src/Listener/OnManagerStart.php index 5d1a914e94..ea34f47c72 100644 --- a/src/Listener/OnManagerStart.php +++ b/src/Listener/OnManagerStart.php @@ -27,15 +27,14 @@ class OnManagerStart implements IManagerStartEventListener */ public function handle(ManagerStartEventParam $e) { + // 随机数播种 + mt_srand(); + // 进程PID记录 $fileName = File::path(dirname($_SERVER['SCRIPT_NAME']), str_replace('\\', '-', App::getNamespace()) . '.pid'); File::writeFile($fileName, json_encode([ 'masterPID' => Swoole::getMasterPID(), 'managerPID' => Swoole::getManagerPID(), ])); - - // 热更新 - $process = ProcessManager::create('hotUpdate'); - $process->start(); } } \ No newline at end of file diff --git a/src/Listener/OnManagerStop.php b/src/Listener/OnManagerStop.php new file mode 100644 index 0000000000..f158ae2f45 --- /dev/null +++ b/src/Listener/OnManagerStop.php @@ -0,0 +1,24 @@ +save(); + } +} \ No newline at end of file diff --git a/src/Listener/OnServerCreateAfter.php b/src/Listener/OnServerCreateAfter.php new file mode 100644 index 0000000000..c60a52a7bf --- /dev/null +++ b/src/Listener/OnServerCreateAfter.php @@ -0,0 +1,24 @@ +save(); + } +} \ No newline at end of file diff --git a/src/Listener/WorkerInit.php b/src/Listener/WorkerInit.php index 87fc310baa..5280ce7cc6 100644 --- a/src/Listener/WorkerInit.php +++ b/src/Listener/WorkerInit.php @@ -15,6 +15,7 @@ use Imi\Util\CoroutineChannelManager; use Imi\Server\Event\Param\WorkerStartEventParam; use Imi\Server\Event\Listener\IWorkerStartEventListener; +use Imi\Util\Imi; /** * @Listener(eventName="IMI.MAIN_SERVER.WORKER.START",priority=PHP_INT_MAX) @@ -28,22 +29,31 @@ class WorkerInit implements IWorkerStartEventListener */ public function handle(WorkerStartEventParam $e) { - $GLOBALS['WORKER_START_END_RESUME_COIDS'] = []; + // 随机数播种 + mt_srand(); - // 清除当前 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(!$e->server->getSwooleServer()->taskworker) { - if (is_file($file)) + // swoole 4.1.0 一键协程化 + if(method_exists('\Swoole\Runtime', 'enableCoroutine') && (Helper::getMain(App::getNamespace())->getConfig()['enableCoroutine'] ?? true)) { - unlink($file); + \Swoole\Runtime::enableCoroutine(true); } } + $GLOBALS['WORKER_START_END_RESUME_COIDS'] = []; + // 当前进程的 WorkerID 设置 Worker::setWorkerID($e->server->getSwooleServer()->worker_id); + // 清除当前 worker 进程的 Bean 类缓存 + $path = Imi::getWorkerClassCachePathByWorkerID(Worker::getWorkerID()); + + foreach(File::enum($path) as $file) + { + unlink((string)$file); + } + // 初始化 worker App::initWorker(); } diff --git a/src/Log/ErrorLog.php b/src/Log/ErrorLog.php index 48d2202ced..0804580f63 100644 --- a/src/Log/ErrorLog.php +++ b/src/Log/ErrorLog.php @@ -9,6 +9,7 @@ use Imi\RequestContext; use Imi\Pool\PoolManager; use Imi\Bean\Annotation\Bean; +use Imi\Util\Imi; /** * @Bean("ErrorLog") @@ -30,8 +31,7 @@ class ErrorLog public function register() { error_reporting(0); - $path = Config::get('@app.beanClassCache', sys_get_temp_dir()); - $this->beanCacheFilePath = File::path($path, 'imiBeanCache', 'imi', str_replace('\\', DIRECTORY_SEPARATOR, __CLASS__) . '.php'); + $this->beanCacheFilePath = Imi::getImiClassCachePath(str_replace('\\', DIRECTORY_SEPARATOR, __CLASS__) . '.php'); register_shutdown_function([$this, 'onShutdown']); set_error_handler([$this, 'onError']); } @@ -55,32 +55,29 @@ public function onError($errno, $errstr, $errfile, $errline) case E_COMPILE_ERROR: case E_USER_ERROR: case E_RECOVERABLE_ERROR: - Log::error($errstr, [ - 'trace' => $this->getTrace(), - ]); + $method = 'error'; break; case E_WARNING: case E_CORE_WARNING: case E_COMPILE_WARNING: case E_USER_WARNING: - Log::warning($errstr, [ - 'trace' => $this->getTrace(), - ]); + $method = 'warning'; break; case E_NOTICE: case E_USER_NOTICE: - Log::notice($errstr, [ - 'trace' => $this->getTrace(), - ]); + $method = 'notice'; break; case E_STRICT: case E_DEPRECATED: case E_USER_DEPRECATED: - Log::info($errstr, [ - 'trace' => $this->getTrace(), - ]); + $method = 'info'; break; } + Log::$method($errstr, [ + 'trace' => $this->getTrace(), + 'errorFile' => $errfile, + 'errorLine' => $errline, + ]); } /** @@ -105,7 +102,6 @@ public function onShutdown() ]); } $logger = App::getBean('Logger'); - $logger->endRequest(); $logger->save(); exit; } @@ -119,9 +115,10 @@ public function onException(\Throwable $ex) { // 日志处理 Log::error($ex->getMessage(), [ - 'trace' => $ex->getTrace(), + 'trace' => $ex->getTrace(), + 'errorFile' => $ex->getFile(), + 'errorLine' => $ex->getLine(), ]); - App::getBean('Logger')->endRequest(); // 请求上下文处理 if(RequestContext::exsits()) { @@ -139,7 +136,7 @@ protected function getTrace() { $backtrace = debug_backtrace(); $index = null; - $hasNull = false; + $hasNull = false; foreach($backtrace as $i => $item) { if(isset($item['file'])) @@ -148,7 +145,7 @@ protected function getTrace() { if($this->beanCacheFilePath === $item['file']) { - $index = $i + 1; + $index = $i + 2; break; } } @@ -157,7 +154,7 @@ protected function getTrace() { $hasNull = true; } - } + } if(null === $index) { return []; diff --git a/src/Log/Handler/Base.php b/src/Log/Handler/Base.php index 6b28bb53ba..b33b382583 100644 --- a/src/Log/Handler/Base.php +++ b/src/Log/Handler/Base.php @@ -28,13 +28,7 @@ abstract class Base * 日志格式 * @var string */ - protected $format = '{Y}-{m}-{d} {H}:{i}:{s} [{level}] {message} {lastTrace}'; - - /** - * 最后一个调用跟踪格式 - * @var string - */ - protected $lastTraceFormat = '{file}: {line}, {call}'; + protected $format = '{Y}-{m}-{d} {H}:{i}:{s} [{level}] {message} {errorFile}:{errorLine}'; /** * 调用跟踪格式 @@ -169,15 +163,6 @@ public function getDateTime($time = null) */ public function parseMessage(\Imi\Log\Record $record): string { - $find = $replace = []; - foreach ($record->getContext() as $key => $value) - { - if(is_scalar($value)) - { - $find[] = '{' . $key . '}'; - $replace[] = $value; - } - } return str_replace($find, $replace, $record->getMessage()); } @@ -189,20 +174,31 @@ public function parseMessage(\Imi\Log\Record $record): string public function getLogString(\Imi\Log\Record $record) { $vars = [ - 'message' => $this->parseMessage($record), + 'message' => $record->getMessage(), 'level' => $record->getLevel(), 'timestamp' => $record->getLogTime(), 'trace' => $this->parseTrace($record), - 'lastTrace' => $this->parseLastTrace($record), ]; - $logContent = $this->format; - foreach($vars as $name => $value) + + $find = $replace = []; + foreach($vars as $key => $value) { if(is_scalar($value)) { - $logContent = str_replace('{' . $name . '}', (string)$value, $logContent); + $find[] = '{' . $key . '}'; + $replace[] = $value; } } + foreach ($record->getContext() as $key => $value) + { + if(is_scalar($value)) + { + $find[] = '{' . $key . '}'; + $replace[] = $value; + } + } + $logContent = str_replace($find, $replace, $this->format); + return $this->replaceDateTime($logContent, $record->getLogTime()); } @@ -231,31 +227,6 @@ public function parseTrace(\Imi\Log\Record $record) return implode(PHP_EOL, $result); } - /** - * 处理最后的代码调用跟踪 - * @param \Imi\Log\Record $record - * @return string - */ - public function parseLastTrace(\Imi\Log\Record $record) - { - $trace = $record->getTrace(); - if(!isset($trace[0])) - { - return ''; - } - $vars = $trace[0]; - $vars['call'] = $this->getTraceCall($vars); - $result = $this->lastTraceFormat; - foreach($vars as $name => $value) - { - if(is_scalar($value)) - { - $result = str_replace('{' . $name . '}', (string)$value, $result); - } - } - return $result; - } - /** * 获取调用跟踪的调用 * @return string diff --git a/src/Log/Handler/Console.php b/src/Log/Handler/Console.php index ba49ee3ed5..092f3a4b8c 100644 --- a/src/Log/Handler/Console.php +++ b/src/Log/Handler/Console.php @@ -8,6 +8,13 @@ */ class Console extends Base { + /** + * 要限制输出的字符数量,为null则不限制 + * + * @var int + */ + protected $length; + /** * 真正的保存操作实现 * @return void @@ -16,7 +23,12 @@ protected function __save() { foreach($this->records as $record) { - echo $this->getLogString($record), PHP_EOL; + $content = $this->getLogString($record); + if($this->length > 0) + { + $content = mb_substr($content, 0, $this->length) . '...'; + } + echo $content, PHP_EOL; } } } \ No newline at end of file diff --git a/src/Log/Log.php b/src/Log/Log.php index 8917cabd7d..3003e5ad77 100644 --- a/src/Log/Log.php +++ b/src/Log/Log.php @@ -145,7 +145,18 @@ public static function debug($message, array $context = array()) public static function getTrace() { $backtrace = debug_backtrace(); - return array_splice($backtrace, 2); + return array_splice($backtrace, 3); + } + + /** + * 获取错误文件位置 + * + * @return array + */ + public static function getErrorFile() + { + $backtrace = debug_backtrace(0, 3); + return [$backtrace[2]['file'] ?? '', $backtrace[2]['line'] ?? 0]; } /** @@ -160,6 +171,12 @@ private static function parseContext($context) { $context['trace'] = static::getTrace(); } + if(!isset($context['errorFile'])) + { + list($file, $line) = static::getErrorFile(); + $context['errorFile'] = $file; + $context['errorLine'] = $line; + } return $context; } } \ No newline at end of file diff --git a/src/Log/Logger.php b/src/Log/Logger.php index 10cc413ac1..d88a826f5f 100644 --- a/src/Log/Logger.php +++ b/src/Log/Logger.php @@ -10,6 +10,7 @@ use Imi\Bean\BeanFactory; use Psr\Log\AbstractLogger; use Imi\Bean\Annotation\Bean; +use Imi\Util\Imi; /** * @Bean("Logger") @@ -25,8 +26,16 @@ class Logger extends AbstractLogger 'class' => \Imi\Log\Handler\Console::class, 'options' => [ 'levels' => [ - LogLevel::DEBUG, LogLevel::INFO, + ], + 'format' => '{Y}-{m}-{d} {H}:{i}:{s} [{level}] {message}', + ], + ], + [ + 'class' => \Imi\Log\Handler\Console::class, + 'options' => [ + 'levels' => [ + LogLevel::DEBUG, LogLevel::NOTICE, LogLevel::WARNING, ], @@ -41,7 +50,8 @@ class Logger extends AbstractLogger LogLevel::EMERGENCY, LogLevel::ERROR, ], - 'format' => '{Y}-{m}-{d} {H}:{i}:{s} [{level}] {message} {lastTrace}' . PHP_EOL . 'Stack trace:' . PHP_EOL . '{trace}', + 'format' => '{Y}-{m}-{d} {H}:{i}:{s} [{level}] {message} {errorFile}:{errorLine}' . PHP_EOL . 'Stack trace:' . PHP_EOL . '{trace}', + 'length' => 1024, ], ] ]; @@ -72,13 +82,6 @@ class Logger extends AbstractLogger */ private $beanCacheFilePath; - /** - * 自动保存间隔,单位:秒 - * - * @var integer - */ - protected $autoSaveInterval = 10; - /** * 定时器ID * @@ -92,15 +95,7 @@ public function __init() { $this->handlers[] = BeanFactory::newInstance($handlerOption['class'], $handlerOption['options']); } - $path = Config::get('@app.beanClassCache', sys_get_temp_dir()); - $this->beanCacheFilePath = File::path($path, 'imiBeanCache', '%s', str_replace('\\', DIRECTORY_SEPARATOR, __CLASS__) . '.php'); - if($this->autoSaveInterval > 0) - { - $this->timerID = swoole_timer_tick($this->autoSaveInterval * 1000, function(){ - $this->endRequest(); - $this->save(); - }); - } + $this->beanCacheFilePath = Imi::getBeanClassCachePath('%s', str_replace('\\', DIRECTORY_SEPARATOR, __CLASS__) . '.php'); } public function __destruct() @@ -126,26 +121,10 @@ public function log($level, $message, array $context = array()) $context = $this->parseContext($context); $trace = $context['trace']; $logTime = time(); - $this->records[] = new Record($level, $message, $context, $trace, $logTime); - if(!Coroutine::isIn()) - { - $this->endRequest(); - } - } - - /** - * 当请求结束时调用 - * @return void - */ - public function endRequest() - { - if(isset($this->records[0])) + $record = new Record($level, $message, $context, $trace, $logTime); + foreach($this->handlers as $handler) { - foreach($this->handlers as $handler) - { - $handler->logBatch($this->records); - } - $this->records = []; + $handler->log($record); } } @@ -165,9 +144,8 @@ public function save() * 获取代码调用跟踪 * @return array */ - protected function getTrace() + protected function getTrace($backtrace) { - $backtrace = debug_backtrace(); $index = null; $hasNull = false; $beanCacheFilePath = sprintf($this->beanCacheFilePath, Worker::getWorkerID() ?? 'imi'); @@ -177,9 +155,9 @@ protected function getTrace() { if($hasNull) { - if($beanCacheFilePath === $item['file']) + if($beanCacheFilePath === $item['file'] && isset($backtrace[$i + 1]['file']) && 'AbstractLogger.php' !== basename($backtrace[$i + 1]['file'])) { - $index = $i + 1; + $index = $i + 2; break; } } @@ -196,6 +174,37 @@ protected function getTrace() return array_splice($backtrace, $index); } + /** + * 获取错误文件位置 + * + * @return array + */ + public function getErrorFile($backtrace) + { + $index = null; + $hasNull = false; + $beanCacheFilePath = sprintf($this->beanCacheFilePath, Worker::getWorkerID() ?? 'imi'); + foreach($backtrace as $i => $item) + { + if(isset($item['file'])) + { + if($hasNull) + { + if($beanCacheFilePath === $item['file'] && isset($backtrace[$i + 1]['file']) && 'AbstractLogger.php' !== basename($backtrace[$i + 1]['file'])) + { + $index = $i + 1; + break; + } + } + } + else + { + $hasNull = true; + } + } + return [$backtrace[$index]['file'] ?? '', $backtrace[$index]['line'] ?? 0]; + } + /** * 处理context * @@ -204,9 +213,16 @@ protected function getTrace() */ private function parseContext($context) { + $debugBackTrace = debug_backtrace(); if(!isset($context['trace'])) { - $context['trace'] = $this->getTrace(); + $context['trace'] = $this->getTrace($debugBackTrace); + } + if(!isset($context['errorFile'])) + { + list($file, $line) = $this->getErrorFile($debugBackTrace); + $context['errorFile'] = $file; + $context['errorLine'] = $line; } return $context; } diff --git a/src/Model/BaseModel.php b/src/Model/BaseModel.php index 644e4cce6f..3c21f29744 100644 --- a/src/Model/BaseModel.php +++ b/src/Model/BaseModel.php @@ -98,6 +98,20 @@ public function offsetGet($offset) public function offsetSet($offset, $value) { + // 数据库bit类型字段处理 + $column = ModelManager::getPropertyAnnotation($this, $offset, 'Column'); + if(null === $column) + { + $column = ModelManager::getPropertyAnnotation($this, $this->__getCamelName($offset), 'Column'); + } + if(null !== $column) + { + if('bit' === $column->type) + { + $value = (1 == $value || chr(1) == $value); + } + } + $methodName = 'set' . ucfirst($this->__getCamelName($offset)); if(!method_exists($this, $methodName)) { diff --git a/src/Model/Model.php b/src/Model/Model.php index c741839891..fee8ccd479 100644 --- a/src/Model/Model.php +++ b/src/Model/Model.php @@ -2,12 +2,13 @@ namespace Imi\Model; use Imi\Db\Db; +use Imi\Util\Text; +use Imi\Event\Event; use Imi\Bean\BeanFactory; +use Imi\Util\LazyArrayObject; use Imi\Model\Event\ModelEvents; use Imi\Db\Query\Interfaces\IQuery; use Imi\Db\Query\Interfaces\IResult; -use Imi\Util\LazyArrayObject; -use Imi\Event\Event; /** * 常用的数据库模型 @@ -416,8 +417,8 @@ private static function parseWhere(IQuery $query, $where) { if(is_array($v)) { - $operation = array_unshift($v); - $query->where($k, $operation, $v[1]); + $operation = array_shift($v); + $query->where($k, $operation, $v[0]); } else { @@ -439,11 +440,35 @@ private static function parseSaveData($data, $object = null) { $object = $data; } + if($data instanceof static) + { + $data = $data->toArray(); + } $class = BeanFactory::getObjectClass($object ?? static::class); $result = new LazyArrayObject; - foreach(ModelManager::getFieldNames($class) as $name) + foreach(ModelManager::getFields($class) as $name => $column) { - $result[$name] = $data[$name]; + if(array_key_exists($name, $data)) + { + $value = $data[$name]; + } + else + { + $fieldName = Text::toCamelName($name); + if(array_key_exists($fieldName, $data)) + { + $value = $data[$fieldName]; + } + else + { + $value = null; + } + } + if(null === $value && !$column->nullable) + { + continue; + } + $result[$name] = $value; } return $result; } diff --git a/src/Pool/BaseAsyncPool.php b/src/Pool/BaseAsyncPool.php index 374d6012f9..566d4fcb60 100644 --- a/src/Pool/BaseAsyncPool.php +++ b/src/Pool/BaseAsyncPool.php @@ -37,6 +37,7 @@ protected function initQueue() */ public function getResource(): IPoolResource { + $selectResult = true; if($this->getFree() <= 0) { if($this->getCount() < $this->config->getMaxResources()) @@ -46,17 +47,31 @@ public function getResource(): IPoolResource } else { - // 等待其他协程使用完成后释放连接 - $read = [$this->queue]; - $write = null; - $selectResult = Channel::select($read, $write, $this->config->getWaitTimeout() / 1000); + if(SWOOLE_VERSION < '4.0.3') + { + // 等待其他协程使用完成后释放连接 + $read = [$this->queue]; + $write = null; + $selectResult = Channel::select($read, $write, $this->config->getWaitTimeout() / 1000); + } + else + { + $selectResult = $this->queue->pop($this->config->getWaitTimeout() / 1000); + } if(false === $selectResult) { throw new \RuntimeException('AsyncPool getResource timeout'); } } } - $resource = $this->queue->pop(); + if(true === $selectResult) + { + $resource = $this->queue->pop(); + } + else + { + $resource = $selectResult; + } if(!$resource->checkState()) { $resource->open(); @@ -84,13 +99,27 @@ public function tryGetResource() } $read = [$this->queue]; $write = null; - // Coroutine\Channel::select() 最小超时时间1毫秒 - $result = Channel::select($read, $write, 0.001); + // Coroutine\Channel::select()/->pop() 最小超时时间1毫秒 + if(SWOOLE_VERSION < '4.0.3') + { + $result = Channel::select($read, $write, 0.001); + } + else + { + $result = $this->queue->pop(0.001); + } if(false === $result) { return false; } - $resource = $this->queue->pop(); + if(true === $result) + { + $resource = $this->queue->pop(); + } + else + { + $resource = $result; + } if(!$resource->checkState()) { $resource->open(); diff --git a/src/Process/Annotation/Process.php b/src/Process/Annotation/Process.php index b1107ef6b6..ae5977b824 100644 --- a/src/Process/Annotation/Process.php +++ b/src/Process/Annotation/Process.php @@ -36,4 +36,11 @@ class Process extends Base * @var int */ public $pipeType = 2; + + /** + * 该进程是否只允许存在一个实例 + * + * @var boolean + */ + public $unique = false; } \ No newline at end of file diff --git a/src/Process/Annotation/ProcessPool.php b/src/Process/Annotation/ProcessPool.php new file mode 100644 index 0000000000..5421663fc7 --- /dev/null +++ b/src/Process/Annotation/ProcessPool.php @@ -0,0 +1,48 @@ +data = $data; + foreach($data as $k => $v) + { + $this->$k = $v; + } + } +} \ No newline at end of file diff --git a/src/Process/Exception/ProcessAlreadyRunException.php b/src/Process/Exception/ProcessAlreadyRunException.php new file mode 100644 index 0000000000..4337e340d3 --- /dev/null +++ b/src/Process/Exception/ProcessAlreadyRunException.php @@ -0,0 +1,7 @@ +data[$annotation->name])) + { + new \RuntimeException(sprintf('process pool %s is exists', $annotation->name)); + } + $this->data[$annotation->name] = [ + 'className' => $className, + 'ProcessPool' => $annotation, + ]; + } + } + + /** + * 获取processPool信息 + * @param string $name processPool名称 + * @return array + */ + public function getProcessPool($name) + { + return $this->data[$name] ?? null; + } +} \ No newline at end of file diff --git a/src/Process/ProcessManager.php b/src/Process/ProcessManager.php index 76123610d3..b4a373871b 100644 --- a/src/Process/ProcessManager.php +++ b/src/Process/ProcessManager.php @@ -1,15 +1,27 @@ getProcess($name); + if(null === $processOption) + { + return null; + } + if($processOption['Process']->unique && static::isRunning($name)) + { + throw new ProcessAlreadyRunException(sprintf('process %s already run', $name)); + } if(null === $redirectStdinStdout) { $redirectStdinStdout = $processOption['Process']->redirectStdinStdout; @@ -33,9 +53,19 @@ public static function create($name, $args = [], $redirectStdinStdout = null, $p $pipeType = $processOption['Process']->pipeType; } $processInstance = BeanFactory::newInstance($processOption['className'], $args); - $process = new \Swoole\Process(function(\Swoole\Process $swooleProcess) use($processInstance, $name){ + $process = new \Swoole\Process(function(\Swoole\Process $swooleProcess) use($processInstance, $name, $processOption){ // 设置进程名称 $swooleProcess->name($name); + // 随机数播种 + mt_srand(); + if($processOption['Process']->unique) + { + if(!static::lockProcess($name)) + { + throw new \RuntimeException('lock process lock file error'); + } + } + App::initWorker(); // 进程开始事件 Event::trigger('IMI.PROCESS.BEGIN', [ 'name' => $name, @@ -43,6 +73,11 @@ public static function create($name, $args = [], $redirectStdinStdout = null, $p ]); // 执行任务 call_user_func([$processInstance, 'run'], $swooleProcess); + swoole_event_wait(); + if($processOption['Process']->unique) + { + static::unlockProcess($name); + } // 进程结束事件 Event::trigger('IMI.PROCESS.END', [ 'name' => $name, @@ -52,6 +87,44 @@ public static function create($name, $args = [], $redirectStdinStdout = null, $p return $process; } + /** + * 进程是否已在运行,只有unique为true时有效 + * + * @param string $name + * @return boolean + */ + public static function isRunning($name) + { + $processOption = ProcessParser::getInstance()->getProcess($name); + if(null === $processOption) + { + return false; + } + if(!$processOption['Process']->unique) + { + return false; + } + $fileName = static::getLockFileName($name); + if(!is_file($fileName)) + { + return false; + } + $fp = fopen($fileName, 'w+'); + if(false === $fp) + { + return false; + } + if(!flock($fp, LOCK_EX | LOCK_NB)) + { + fclose($fp); + return true; + } + flock($fp, LOCK_UN); + fclose($fp); + unlink($fileName); + return false; + } + /** * 运行进程,同步阻塞等待进程执行返回 * 不返回\Swoole\Process对象实例 @@ -70,7 +143,7 @@ public static function create($name, $args = [], $redirectStdinStdout = null, $p */ public static function run($name, $args = [], $redirectStdinStdout = null, $pipeType = null) { - $cmd = 'php ' . $_SERVER['argv'][0] . ' process/start -name ' . $name; + $cmd = Imi::getImiCmd('process', 'start') . ' -name ' . $name; if(null !== $redirectStdinStdout) { $cmd .= ' -redirectStdinStdout ' . $redirectStdinStdout; @@ -103,4 +176,83 @@ public static function coRun($name, $args = [], $redirectStdinStdout = null, $pi static::run($name, $args, $redirectStdinStdout, $pipeType); }); } + + /** + * 挂靠Manager进程运行进程 + * + * @param string $name + * @param array $args + * @param boolean $redirectStdinStdout + * @param int $pipeType + * @return void + */ + public static function runWithManager($name, $args = [], $redirectStdinStdout = null, $pipeType = null) + { + $process = static::create($name, $args, $redirectStdinStdout, $pipeType); + $server = ServerManage::getServer('main')->getSwooleServer(); + $server->addProcess($process); + } + + /** + * 锁定进程,实现unique + * + * @param string $name + * @return boolean + */ + private static function lockProcess($name) + { + $fileName = static::getLockFileName($name); + $fp = fopen($fileName, 'w+'); + if(false === $fp) + { + return false; + } + if(!flock($fp, LOCK_EX | LOCK_NB)) + { + fclose($fp); + return false; + } + static::$lockMap[$name] = [ + 'fileName' => $fileName, + 'fp' => $fp, + ]; + return true; + } + + /** + * 解锁进程,实现unique + * + * @param string $name + * @return boolean + */ + private static function unlockProcess($name) + { + if(!isset(static::$lockMap[$name])) + { + return false; + } + if(flock(static::$lockMap[$name]['fp'], LOCK_UN) && fclose(static::$lockMap[$name]['fp'])) + { + unlink(static::$lockMap[$name]['fileName']); + unset(static::$lockMap[$name]); + return true; + } + return false; + } + + /** + * 获取文件锁的文件名 + * + * @param string $name + * @return string + */ + private static function getLockFileName($name) + { + $path = File::path(sys_get_temp_dir(), str_replace('\\', '-', App::getNamespace()), 'processLock'); + if(!is_dir($path)) + { + File::createDir($path); + } + return File::path($path, $name . '.lock'); + } } \ No newline at end of file diff --git a/src/Process/ProcessPoolManager.php b/src/Process/ProcessPoolManager.php new file mode 100644 index 0000000000..037bec4092 --- /dev/null +++ b/src/Process/ProcessPoolManager.php @@ -0,0 +1,89 @@ +getProcessPool($name); + if(null === $processPoolOption) + { + return null; + } + if(null === $workerNum) + { + $workerNum = $processPoolOption['ProcessPool']->workerNum; + } + if(null === $ipcType) + { + $ipcType = $processPoolOption['ProcessPool']->ipcType; + } + if(null === $msgQueueKey) + { + $msgQueueKey = $processPoolOption['ProcessPool']->msgQueueKey; + } + + $pool = new \Swoole\Process\Pool($workerNum, $ipcType, $msgQueueKey); + + $pool->on('WorkerStart', function ($pool, $workerId) use($name, $workerNum, $args, $ipcType, $msgQueueKey, $processPoolOption) { + // 随机数播种 + mt_srand(); + $processInstance = BeanFactory::newInstance($processPoolOption['className'], $args); + App::initWorker(); + // 进程开始事件 + Event::trigger('IMI.PROCESS_POOL.PROCESS.BEGIN', [ + 'name' => $name, + 'pool' => $pool, + 'workerId' => $workerId, + 'workerNum' => $workerNum, + 'args' => $args, + 'ipcType' => $ipcType, + 'msgQueueKey' => $msgQueueKey, + ]); + // 执行任务 + call_user_func([$processInstance, 'run'], $pool, $workerId, $name, $workerNum, $args, $ipcType, $msgQueueKey); + swoole_event_wait(); + }); + + $pool->on('WorkerStop', function ($pool, $workerId) use($name, $workerNum, $args, $ipcType, $msgQueueKey) { + // 进程结束事件 + Event::trigger('IMI.PROCESS_POOL.PROCESS.END', [ + 'name' => $name, + 'pool' => $pool, + 'workerId' => $workerId, + 'workerNum' => $workerNum, + 'args' => $args, + 'ipcType' => $ipcType, + 'msgQueueKey' => $msgQueueKey, + ]); + }); + + return $pool; + + } + +} \ No newline at end of file diff --git a/src/Server/Base.php b/src/Server/Base.php index b48d1ea35e..5de6e80684 100644 --- a/src/Server/Base.php +++ b/src/Server/Base.php @@ -13,6 +13,7 @@ use Imi\Server\Event\Param\ShutdownEventParam; use Imi\Server\Event\Param\WorkerStopEventParam; use Doctrine\Common\Annotations\AnnotationReader; +use Imi\Server\Event\Param\ManagerStopEventParam; use Imi\Server\Event\Param\PipeMessageEventParam; use Imi\Server\Event\Param\WorkerErrorEventParam; use Imi\Server\Event\Param\WorkerStartEventParam; diff --git a/src/Server/ConnectContext/StoreHandler/Redis.php b/src/Server/ConnectContext/StoreHandler/Redis.php index 12ebc21974..e225f4a140 100644 --- a/src/Server/ConnectContext/StoreHandler/Redis.php +++ b/src/Server/ConnectContext/StoreHandler/Redis.php @@ -1,7 +1,10 @@ useRedis(function($resource, $redis){ - // 判断master进程pid - $this->masterPID = Swoole::getMasterPID(); - $hasPing = $this->hasPing($redis); - $storeMasterPID = $redis->get($this->key); - if(null === $storeMasterPID) - { - // 没有存储master进程pid - $this->initRedis($redis, $storeMasterPID); - } - else if($this->masterPID != $storeMasterPID) - { - if($hasPing) + if(0 === Worker::getWorkerID()) + { + $this->useRedis(function($resource, $redis){ + // 判断master进程pid + $this->masterPID = Swoole::getMasterPID(); + $storeMasterPID = $redis->get($this->key); + if(null === $storeMasterPID) { - // 与master进程ID不等 - throw new \RuntimeException('ConnectContextRedis repeat'); + // 没有存储master进程pid + $this->initRedis($redis, $storeMasterPID); } - else + else if($this->masterPID != $storeMasterPID) { - $this->initRedis($redis, $storeMasterPID); + $hasPing = $this->hasPing($redis); + if($hasPing) + { + Log::warning('ConnectContextRedis key has been used, waiting...'); + sleep($this->heartbeatTtl); + $hasPing = $this->hasPing($redis); + } + if($hasPing) + { + // 与master进程ID不等 + Log::emergency('ConnectContextRedis key has been used'); + ServerManage::getServer('main')->getSwooleServer()->shutdown(); + } + else + { + $this->initRedis($redis, $storeMasterPID); + Log::info('ConnectContextRedis key init'); + } } - } - $this->startPing($redis); - }); + $this->startPing($redis); + }); + } } /** @@ -210,7 +224,8 @@ public function read(string $key): array { return $this->useRedis(function($resource, $redis) use($key){ $redisKey = $this->getRedisKey($key); - return $redis->get($redisKey) ?? []; + $result = $redis->get($redisKey); + return $result ? $result : []; }); } diff --git a/src/Server/Group/Handler/Redis.php b/src/Server/Group/Handler/Redis.php index 7ab456ddcd..b37109c05f 100644 --- a/src/Server/Group/Handler/Redis.php +++ b/src/Server/Group/Handler/Redis.php @@ -7,6 +7,9 @@ use Imi\Pool\PoolManager; use Imi\Bean\Annotation\Bean; use Swoole\Coroutine\Redis as CoRedis; +use Imi\Worker; +use Imi\Log\Log; +use Imi\ServerManage; /** * @Bean("GroupRedis") @@ -74,30 +77,41 @@ public function __init() { return; } - $this->useRedis(function($resource, $redis){ - // 判断master进程pid - $this->masterPID = Swoole::getMasterPID(); - $hasPing = $this->hasPing($redis); - $storeMasterPID = $redis->get($this->key); - if(null === $storeMasterPID) - { - // 没有存储master进程pid - $this->initRedis($redis, $storeMasterPID); - } - else if($this->masterPID != $storeMasterPID) - { - if($hasPing) + if(0 === Worker::getWorkerID()) + { + $this->useRedis(function($resource, $redis){ + // 判断master进程pid + $this->masterPID = Swoole::getMasterPID(); + $storeMasterPID = $redis->get($this->key); + if(null === $storeMasterPID) { - // 与master进程ID不等 - throw new \RuntimeException('Server Group Redis repeat'); + // 没有存储master进程pid + $this->initRedis($redis, $storeMasterPID); } - else + else if($this->masterPID != $storeMasterPID) { - $this->initRedis($redis, $storeMasterPID); + $hasPing = $this->hasPing($redis); + if($hasPing) + { + Log::warning('Redis server group key has been used, waiting...'); + sleep($this->heartbeatTtl); + $hasPing = $this->hasPing($redis); + } + if($hasPing) + { + // 与master进程ID不等 + Log::emergency('Redis server group key has been used'); + ServerManage::getServer('main')->getSwooleServer()->shutdown(); + } + else + { + $this->initRedis($redis, $storeMasterPID); + Log::info('Redis server group key init'); + } } - } - $this->startPing($redis); - }); + $this->startPing($redis); + }); + } } /** diff --git a/src/Server/Http/Listener/AfterRequest.php b/src/Server/Http/Listener/AfterRequest.php index 1c68ff68e1..f0b758535c 100644 --- a/src/Server/Http/Listener/AfterRequest.php +++ b/src/Server/Http/Listener/AfterRequest.php @@ -22,8 +22,6 @@ class AfterRequest implements IRequestEventListener */ public function handle(RequestEventParam $e) { - // 日志处理 - App::getBean('Logger')->endRequest(); // 释放请求的进程池资源 PoolManager::destroyCurrentContext(); // 销毁请求上下文 diff --git a/src/Server/TcpServer/Listener/AfterReceive.php b/src/Server/TcpServer/Listener/AfterReceive.php index c77debac26..b672702e63 100644 --- a/src/Server/TcpServer/Listener/AfterReceive.php +++ b/src/Server/TcpServer/Listener/AfterReceive.php @@ -22,8 +22,6 @@ class AfterReceive implements IReceiveEventListener */ public function handle(ReceiveEventParam $e) { - // 日志处理 - App::getBean('Logger')->endRequest(); // 释放请求的进程池资源 PoolManager::destroyCurrentContext(); // 销毁请求上下文 diff --git a/src/Server/WebSocket/Listener/AfterMessage.php b/src/Server/WebSocket/Listener/AfterMessage.php index b3a897d072..9549065516 100644 --- a/src/Server/WebSocket/Listener/AfterMessage.php +++ b/src/Server/WebSocket/Listener/AfterMessage.php @@ -22,8 +22,6 @@ class AfterMessage implements IMessageEventListener */ public function handle(MessageEventParam $e) { - // 日志处理 - App::getBean('Logger')->endRequest(); // 释放请求的进程池资源 PoolManager::destroyCurrentContext(); // 销毁请求上下文 diff --git a/src/Tool/Listener/Init.php b/src/Tool/Listener/Init.php index 67464c39f2..1661ec35ee 100644 --- a/src/Tool/Listener/Init.php +++ b/src/Tool/Listener/Init.php @@ -45,11 +45,6 @@ public function handle(EventParam $e) // 获取回调 $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])); } @@ -111,6 +106,7 @@ public function handle(EventParam $e) */ private function init() { + Annotation::getInstance()->init([Helper::getMain(App::getNamespace())]); RequestContext::create(); // 获取配置 $pools = $caches = []; diff --git a/src/Tool/Tools/Generate/Model/ModelGenerate.php b/src/Tool/Tools/Generate/Model/ModelGenerate.php index 7abe916ed9..dc4f36469a 100644 --- a/src/Tool/Tools/Generate/Model/ModelGenerate.php +++ b/src/Tool/Tools/Generate/Model/ModelGenerate.php @@ -40,19 +40,21 @@ public function generate($namespace, $database, $poolName, $prefix, $include, $e $database = $query->execute('select database()')->getScalar(); } // 表 - $tables = $query->tableRaw('information_schema.TABLES') + $list = $query->tableRaw('information_schema.TABLES') ->where('TABLE_SCHEMA', '=', $database) ->whereIn('TABLE_TYPE', [ 'BASE TABLE', 'VIEW', ]) - ->field('TABLE_NAME') + ->field('TABLE_NAME', 'TABLE_TYPE') ->select() - ->getColumn(); + ->getArray(); // model保存路径 $modelPath = Imi::getNamespacePath($namespace); - foreach($tables as $table) + File::createDir($modelPath); + foreach($list as $item) { + $table = $item['TABLE_NAME']; if(!$this->checkTable($table, $include, $exclude)) { // 不符合$include和$exclude @@ -76,7 +78,7 @@ public function generate($namespace, $database, $poolName, $prefix, $include, $e 'fields' => [], ]; $fields = $query->bindValue(':table', $table)->execute('show full columns from ' . $table)->getArray(); - $this->parseFields($fields, $data); + $this->parseFields($fields, $data, 'VIEW' === $item['TABLE_TYPE']); $content = $this->renderTemplate($data); File::writeFile($fileName, $content); } @@ -120,15 +122,23 @@ private function getClassName($table, $prefix) * 处理字段信息 * @param array $fields * @param array $data + * @param boolean $isView * @return void */ - private function parseFields($fields, &$data) + private function parseFields($fields, &$data, $isView) { $idCount = 0; - foreach($fields as $field) + foreach($fields as $i => $field) { $this->parseFieldType($field['Type'], $typeName, $length, $accuracy); - $isPk = 'PRI' === $field['Key']; + if($isView && 0 === $i) + { + $isPk = true; + } + else + { + $isPk = 'PRI' === $field['Key']; + } $data['fields'][] = [ 'name' => $field['Field'], 'varName' => Text::toCamelName($field['Field']), @@ -136,7 +146,7 @@ private function parseFields($fields, &$data) 'phpType' => $this->dbFieldTypeToPhp($typeName), 'length' => $length, 'accuracy' => $accuracy, - 'nullable' => $field['Null'] !== 'YES', + 'nullable' => $field['Null'] === 'YES', 'default' => $field['Default'], 'isPrimaryKey' => $isPk, 'primaryKeyIndex' => $isPk ? $idCount : -1, diff --git a/src/Tool/Tools/Process/Process.php b/src/Tool/Tools/Process/Process.php index 5a6a865f02..d6cc92c4dd 100644 --- a/src/Tool/Tools/Process/Process.php +++ b/src/Tool/Tools/Process/Process.php @@ -8,6 +8,7 @@ use Imi\Tool\Annotation\Tool; use Imi\Process\ProcessManager; use Imi\Tool\Annotation\Operation; +use Imi\Process\ProcessPoolManager; /** * @Tool("process") @@ -34,4 +35,24 @@ public function start($name, $redirectStdinStdout, $pipeType) $result = \swoole_process::wait(true); echo 'process exit! pid:', $result['pid'], ', code:', $result['code'], ', signal:', $result['signal'], PHP_EOL; } + + /** + * 开启一个进程池,可以任意添加参数 + * + * @Operation("pool") + * + * @Arg(name="name", type=ArgType::STRING, required=true, comments="进程池名称,通过@ProcessPool注解定义") + * @Arg(name="worker", type=ArgType::INT, default=null, comments="进程数量,不传则根据注解配置设定") + * @Arg(name="ipcType", type=ArgType::INT, default=null, comments="进程间通信的模式,默认为0表示不使用任何进程间通信特性,不传则根据注解配置设定") + * @Arg(name="msgQueueKey", type=ArgType::STRING, default=null, comments="消息队列键,不传则根据注解配置设定") + * + * @return void + */ + public function pool($name, $worker, $ipcType, $msgQueueKey) + { + App::initWorker(); + $args = Args::get(); + $processPool = ProcessPoolManager::create($name, $worker, $args, $ipcType, $msgQueueKey); + $processPool->start(); + } } \ No newline at end of file diff --git a/src/Util/CoroutineChannelManager.php b/src/Util/CoroutineChannelManager.php index ecaa69b4e6..3057ff1ae8 100644 --- a/src/Util/CoroutineChannelManager.php +++ b/src/Util/CoroutineChannelManager.php @@ -67,11 +67,12 @@ public static function push(string $name, $data) * 当通道内有数据时自动将数据弹出并还原为PHP变量 * 当通道内没有任何数据时pop会失败并返回false * @param string $name + * @param float $timeout * @return mixed */ - public static function pop(string $name) + public static function pop(string $name, $timeout = 0) { - return static::getInstance($name)->pop(); + return static::getInstance($name)->pop($timeout); } /** @@ -86,7 +87,7 @@ public static function pop(string $name) */ public static function stats(string $name): array { - return parent::stats($name); + return static::getInstance($name)->stats(); } /** diff --git a/src/Util/File.php b/src/Util/File.php index 678eacd422..2a46f949e1 100644 --- a/src/Util/File.php +++ b/src/Util/File.php @@ -7,36 +7,32 @@ abstract class File /** * 枚举文件 * @param string $dirPath - * @return \RecursiveIterator + * @return \RecursiveIteratorIterator|\ArrayIterator */ public static function enum($dirPath) { if (!is_dir($dirPath)) { - return; + return new \ArrayIterator(); } - $iterator = new \RecursiveDirectoryIterator($dirPath); + $iterator = new \RecursiveDirectoryIterator($dirPath, \FilesystemIterator::KEY_AS_PATHNAME | \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS); $files = new \RecursiveIteratorIterator($iterator); - foreach ($files as $file) { - yield $file; - } + return $files; } /** * 枚举php文件 * @param string $dirPath - * @return \RegexIterator + * @return \RegexIterator|ArrayIterator */ public static function enumPHPFile($dirPath) { if (!is_dir($dirPath)) { - return; + return new \ArrayIterator(); } $directory = new \RecursiveDirectoryIterator($dirPath); $iterator = new \RecursiveIteratorIterator($directory); $regex = new \RegexIterator($iterator, '/^.+\.php$/i', \RecursiveRegexIterator::GET_MATCH); - foreach ($regex as $item) { - yield $item[0]; - } + return $regex; } /** diff --git a/src/Util/Imi.php b/src/Util/Imi.php index 1d3c2f6d21..0b3753156b 100644 --- a/src/Util/Imi.php +++ b/src/Util/Imi.php @@ -2,6 +2,9 @@ namespace Imi\Util; use Imi\App; +use Imi\Config; +use Imi\Worker; +use Imi\Util\Args; use Imi\Main\Helper; use Imi\Bean\BeanProxy; use Imi\Bean\Parser\BeanParser; @@ -220,4 +223,72 @@ public static function getClassPropertyValue($className, $propertyName) } return $value; } + + /** + * 获取Bean类缓存根目录 + * + * @param string ...$paths + * @return string + */ + public static function getBeanClassCachePath(...$paths) + { + $main = Helper::getMain(App::getNamespace()); + $beanClassCache = $main->getConfig()['beanClassCache'] ?? null; + if(null === $beanClassCache) + { + $beanClassCache = sys_get_temp_dir(); + } + return File::path($beanClassCache, 'imiBeanCache', str_replace('\\', '-', App::getNamespace()), ...$paths); + } + + /** + * 获取IMI框架Bean类缓存目录 + * + * @param string ...$paths + * @return string + */ + public static function getImiClassCachePath(...$paths) + { + return File::path(static::getBeanClassCachePath(), 'imi', ...$paths); + } + + /** + * 获取Worker进程Bean类缓存目录 + * + * @param string ...$paths + * @return string + */ + public static function getWorkerClassCachePath(...$paths) + { + return static::getWorkerClassCachePathByWorkerID(Worker::getWorkerID(), ...$paths); + } + + /** + * 获取Worker进程Bean类缓存目录,手动传入workerID + * + * @param int $workerID + * @param string ...$paths + * @return string + */ + public static function getWorkerClassCachePathByWorkerID($workerID, ...$paths) + { + return File::path(static::getBeanClassCachePath(), $workerID, ...$paths); + } + + /** + * 获取imi命令行 + * + * @param string $toolName 工具名,如server + * @param string $operation 操作名,如start + * @return string + */ + public static function getImiCmd($toolName, $operation) + { + $cmd = 'php ' . $_SERVER['argv'][0] . ' ' . $toolName . '/' . $operation; + if(null !== ($appNamespace = Args::get('appNamespace'))) + { + $cmd .= ' -appNamespace "' . $appNamespace . '"'; + } + return $cmd; + } } \ No newline at end of file diff --git a/src/Util/KVStorage.php b/src/Util/KVStorage.php index 639ae963de..2a53490707 100644 --- a/src/Util/KVStorage.php +++ b/src/Util/KVStorage.php @@ -104,7 +104,7 @@ private static function parseObject($object, $isStore = true) else { // 其它 - if(false !== ($index = array_search($object, static::$otherMap))) + if(false !== ($index = array_search($object, static::$otherMap, true))) { return static::$otherToObjectMap[$index]; }