From beb1f5f0631789b25e7034b4b66c85bc059fe2fd Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 31 Aug 2020 10:11:06 +0800 Subject: [PATCH 01/32] initial 2.0 commit --- bin/phar-build | 60 ---- bin/start | 144 ++------- composer.json | 18 +- config/console_color.json | 20 ++ config/global.php | 6 +- phar-starter.php | 85 ----- src/Custom/Annotation/Example.php | 5 +- src/Custom/Connection/CustomConnection.php | 14 - src/Framework/Console.php | 277 ----------------- src/Framework/FrameworkLoader.php | 186 ----------- src/Framework/GlobalConfig.php | 37 --- src/Module/Example/Hello.php | 18 +- src/Module/Middleware/TimerMiddleware.php | 2 +- src/Scheduler/MessageEvent.php | 29 -- src/Scheduler/Scheduler.php | 140 --------- src/ZM/API/CQ.php | 2 +- src/ZM/API/CQAPI.php | 143 +-------- src/ZM/{Utils => API}/ZMRobot.php | 97 +++--- src/ZM/{Utils => API}/ZMRobotExperiment.php | 24 +- src/ZM/Annotation/AnnotationParser.php | 288 ++++++++++------- src/ZM/Annotation/Http/Controller.php | 5 +- src/ZM/Annotation/Http/Middleware.php | 3 +- src/ZM/Annotation/Http/RequestMapping.php | 4 +- src/ZM/Annotation/Http/RequestMethod.php | 3 +- .../Interfaces/ErgodicAnnotation.php | 10 + .../Annotation/Swoole/OnTaskWorkerStart.php | 15 - src/ZM/Command/BuildCommand.php | 73 +++++ src/ZM/Command/InitCommand.php | 101 ++++++ src/ZM/Command/RunServerCommand.php | 51 +++ src/ZM/Command/SystemdCommand.php | 20 ++ src/ZM/Connection/CQConnection.php | 23 -- src/ZM/Connection/ConnectionManager.php | 76 ----- src/ZM/Connection/ProxyConnection.php | 13 - src/ZM/Connection/TerminalConnection.php | 13 - src/ZM/Connection/UnknownConnection.php | 13 - src/ZM/Connection/WCConnection.php | 10 - src/ZM/Connection/WSConnection.php | 51 --- src/ZM/ConsoleApplication.php | 42 +++ src/ZM/Context/Context.php | 30 +- src/ZM/Context/ContextInterface.php | 6 +- src/ZM/DB/DB.php | 11 +- src/ZM/DB/SelectBody.php | 2 +- src/ZM/Event/CQ/MessageEvent.php | 31 +- src/ZM/Event/CQ/MetaEvent.php | 12 +- src/ZM/Event/CQ/NoticeEvent.php | 12 +- src/ZM/Event/CQ/RequestEvent.php | 13 +- src/ZM/Event/EventHandler.php | 35 ++- .../Event}/ServerEventHandler.php | 16 +- src/ZM/Event/Swoole/MessageEvent.php | 22 +- src/ZM/Event/Swoole/RequestEvent.php | 13 +- src/ZM/Event/Swoole/WSCloseEvent.php | 12 +- src/ZM/Event/Swoole/WSOpenEvent.php | 56 ++-- src/ZM/Event/Swoole/WorkerStartEvent.php | 101 +++--- src/ZM/Exception/WaitTimeoutException.php | 4 +- src/ZM/Framework.php | 292 ++++++++++++++++++ src/ZM/Http/StaticFileHandler.php | 2 +- src/ZM/ModBase.php | 170 ---------- src/ZM/ModHandleType.php | 20 -- src/{Framework => ZM/Store}/ZMBuf.php | 17 +- src/{Framework => ZM/Utils}/DataProvider.php | 11 +- src/{Framework => ZM/Utils}/RemoteShell.php | 3 +- src/ZM/Utils/SQLPool.php | 122 -------- src/ZM/Utils/ScheduleManager.php | 10 - src/ZM/Utils/Terminal.php | 126 ++++++++ src/ZM/Utils/ZMRequest.php | 11 +- src/ZM/Utils/ZMUtil.php | 22 +- src/ZM/Utils/ZMWebSocket.php | 2 +- .../Utils}/terminal_listener.php | 0 src/{Framework => ZM}/global_functions.php | 22 +- 69 files changed, 1237 insertions(+), 2090 deletions(-) delete mode 100755 bin/phar-build create mode 100644 config/console_color.json delete mode 100644 phar-starter.php delete mode 100644 src/Custom/Connection/CustomConnection.php delete mode 100755 src/Framework/Console.php delete mode 100644 src/Framework/FrameworkLoader.php delete mode 100644 src/Framework/GlobalConfig.php delete mode 100644 src/Scheduler/MessageEvent.php delete mode 100644 src/Scheduler/Scheduler.php rename src/ZM/{Utils => API}/ZMRobot.php (81%) rename src/ZM/{Utils => API}/ZMRobotExperiment.php (82%) create mode 100644 src/ZM/Annotation/Interfaces/ErgodicAnnotation.php delete mode 100644 src/ZM/Annotation/Swoole/OnTaskWorkerStart.php create mode 100644 src/ZM/Command/BuildCommand.php create mode 100644 src/ZM/Command/InitCommand.php create mode 100644 src/ZM/Command/RunServerCommand.php create mode 100644 src/ZM/Command/SystemdCommand.php delete mode 100644 src/ZM/Connection/CQConnection.php delete mode 100644 src/ZM/Connection/ConnectionManager.php delete mode 100644 src/ZM/Connection/ProxyConnection.php delete mode 100644 src/ZM/Connection/TerminalConnection.php delete mode 100644 src/ZM/Connection/UnknownConnection.php delete mode 100644 src/ZM/Connection/WCConnection.php delete mode 100644 src/ZM/Connection/WSConnection.php create mode 100644 src/ZM/ConsoleApplication.php rename src/{Framework => ZM/Event}/ServerEventHandler.php (80%) create mode 100644 src/ZM/Framework.php delete mode 100644 src/ZM/ModBase.php delete mode 100644 src/ZM/ModHandleType.php rename src/{Framework => ZM/Store}/ZMBuf.php (87%) rename src/{Framework => ZM/Utils}/DataProvider.php (91%) rename src/{Framework => ZM/Utils}/RemoteShell.php (98%) delete mode 100644 src/ZM/Utils/SQLPool.php delete mode 100644 src/ZM/Utils/ScheduleManager.php create mode 100644 src/ZM/Utils/Terminal.php rename src/{Framework => ZM/Utils}/terminal_listener.php (100%) rename src/{Framework => ZM}/global_functions.php (94%) diff --git a/bin/phar-build b/bin/phar-build deleted file mode 100755 index 9a5e437c..00000000 --- a/bin/phar-build +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env php -startBuffering(); - $src = realpath(__DIR__ . '/../'); - $hello = file_get_contents($src . '/src/Module/Example/Hello.php'); - $middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php'); - unlink($src . '/src/Module/Example/Hello.php'); - unlink($src . '/src/Module/Middleware/TimerMiddleware.php'); - if ($with_wechat_patch) { - global $wechat_patch; - $wechat = base64_decode($wechat_patch); - } else { - $wechat = false; - } - if ($wechat !== false) { - echo "Using wechat patch.\n"; - $modbase = file_get_contents($src . '/src/ZM/ModBase.php'); - unlink($src . '/src/ZM/ModBase.php'); - } - $phar->buildFromDirectory($src); - $phar->addFromString('tmp/Hello.php.bak', $hello); - $phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware); - if ($wechat !== false) { - $phar->addFromString('src/ZM/ModBase.php', $wechat); - file_put_contents($src . '/src/ZM/ModBase.php', $modbase); - } - //$phar->compressFiles(Phar::GZ); - $phar->setStub($phar->createDefaultStub('phar-starter.php')); - $phar->stopBuffering(); - file_put_contents($src . '/src/Module/Example/Hello.php', $hello); - file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware); - echo "Successfully built. Location: " . $src . "/resources/$filename\n"; -} - diff --git a/bin/start b/bin/start index 1a8d310b..78c7a2b8 100755 --- a/bin/start +++ b/bin/start @@ -1,135 +1,29 @@ #!/usr/bin/env php 30000, -]); -global $vendor_mode; -$vendor_mode = false; -if (mb_strpos(__DIR__, getcwd()) !== false && substr(str_replace(getcwd(), "", __DIR__), 0, 8) == "/vendor/") { +$symbol = sha1(is_file("/flag") ? file_get_contents("/flag") : '1') == '6252c0ec7fcbd544c3d6f5f0a162f60407d7a896' || mb_strpos(getcwd(), "/private/tmp"); + +// 首先得判断是直接从library模式启动的框架还是从composer引入library启动的框架 +// 判断方法:判断当前目录上面有没有 /vendor 目录,如果没有 /vendor 目录说明是从 composer 引入的 +// 否则就是直接从 framework 项目启动的 +if (!is_dir(__DIR__ . '/../vendor') || $symbol) { define("LOAD_MODE", 1); //composer项目模式 define("LOAD_MODE_COMPOSER_PATH", getcwd()); + /** @noinspection PhpIncludeInspection */ + require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php"; +} elseif (substr(__DIR__, 0, 7) != 'phar://') { + define("LOAD_MODE", 2); //phar模式 + // 会废弃phar启动的方式,在2.0 } else { - define("LOAD_MODE", 0); //正常模式 -} - -date_default_timezone_set("Asia/Shanghai"); - -switch ($argv[1] ?? '') { - case 'scheduler': - case 'timer': - go(function () { - try { - new Scheduler(Scheduler::REMOTE); - } catch (Exception $e) { - die($e->getMessage()); - } - }); - break; - case 'phar-build': - array_shift($argv); - require_once 'phar-build'; - break; - case 'systemd': - array_shift($argv); - require_once 'systemd'; - break; - case 'init': - array_shift($argv); - if (LOAD_MODE != 1) { - echo "initialization must be started with composer-project mode!\n"; - exit(1); - } - $cwd = LOAD_MODE_COMPOSER_PATH; - echo "Copying default module file ..."; - @mkdir($cwd . "/config"); - @mkdir($cwd . "/src"); - @mkdir($cwd . "/src/Custom"); - @mkdir($cwd . "/src/Module"); - @mkdir($cwd . "/src/Module/Example"); - @mkdir($cwd . "/src/Module/Middleware"); - $ls = [ - "/config/global.php", - "/.gitignore", - "/config/file_header.json", - "/config/motd.txt", - "/src/Module/Example/Hello.php", - "/src/Module/Middleware/TimerMiddleware.php", - "/src/Custom/global_function.php" - ]; - foreach($ls as $v) { - if(!file_exists($cwd.$v)) { - echo "Copying ".$v.PHP_EOL; - copy($cwd."/vendor/zhamao/framework".$v, $cwd.$v); - } - } - $autoload = [ - "psr-4" => [ - "Module\\" => "src/Module", - "Custom\\" => "src/Custom" - ], - "files" => [ - "src/Custom/global_function.php" - ] - ]; - $scripts = [ - "server" => "vendor/bin/start server", - "server:log-debug" => "vendor/bin/start server --log-debug", - "server:log-verbose" => "vendor/bin/start server --log-verbose", - "server:log-info" => "vendor/bin/start server --log-info", - "server:log-warning" => "vendor/bin/start server --log-warning", - "server:debug-mode" => "vendor/bin/start server --debug-mode", - "systemd" => "vendor/bin/start systemd" - ]; - echo PHP_EOL; - if (file_exists($cwd . "/composer.json")) { - echo "Updating composer.json ..."; - $composer = json_decode(file_get_contents($cwd . "/composer.json"), true); - if (!isset($composer["autoload"])) { - $composer["autoload"] = $autoload; - } - if (!isset($composer["scripts"])) { - $composer["scripts"] = $scripts; - } - file_put_contents($cwd . "/composer.json", json_encode($composer, 64 | 128 | 256)); - echo PHP_EOL; - } else { - echo("Error occurred. Please check your updates.\n"); - exit(1); - } - echo "success!\n"; - break; - case '': - case 'framework': - case 'server': - if (!is_dir(__DIR__ . '/../vendor/') && LOAD_MODE == 0) { - echo "Warning: you have not update composer!\n"; - exec("composer update", $out, $var); - if ($var != 0) { - echo "You need to run \"composer update\" at root of zhamao-framework!\n"; - die; - } - } - $loader = new FrameworkLoader($argv); - break; - case '--help': - case '-h': - echo "\nUsage: " . $argv[0] . " [OPTION]\n"; - echo "\nzhamao-framework start script, provides several startup arguments."; - echo "\n\n -h, --help\t\tShow this help menu"; - echo "\n framework, server\tstart main framework, this is default option"; - echo "\n phar-build\t\tbuild a new phar archive"; - echo "\n init\t\t\tinitialize framework structure in this directory"; - echo "\n systemd\t\tgenerate a new systemd \".service\" file to use\n\n"; - break; - default: - echo "Unknown option \"{$argv[1]}\"!\n\"--help\" for more information\n"; - break; + define("LOAD_MODE", 0); + require_once __DIR__ . "/../vendor/autoload.php"; } +// 终端的命令行功能启动!! +$application = new ConsoleApplication("zhamao-framework"); +$application->initEnv(); +$application->run(); diff --git a/composer.json b/composer.json index 7c47d87b..c63eeedc 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "1.6.2", + "version": "2.0.0-a1", "authors": [ { "name": "whale", @@ -28,14 +28,24 @@ "ext-posix": "*", "ext-ctype": "*", "ext-pdo": "*", - "psy/psysh": "@stable" + "psy/psysh": "@stable", + "symfony/console": "^5.1", + "symfony/polyfill-ctype": "^1.18", + "zhamao/connection-manager": "^1.0", + "zhamao/console": "^1.0", + "zhamao/config": "^1.0" }, "autoload": { "psr-4": { "Custom\\": "src/Custom", - "Framework\\": "src/Framework", "ZM\\": "src/ZM", "Module\\": "src/Module" - } + }, + "files": [ + "src/ZM/global_functions.php" + ] + }, + "require-dev": { + "phpunit/phpunit": "^9.3" } } diff --git a/config/console_color.json b/config/console_color.json new file mode 100644 index 00000000..e398759f --- /dev/null +++ b/config/console_color.json @@ -0,0 +1,20 @@ +{ + "default": { + "success": "green", + "info": "lightblue", + "warning": "yellow", + "error": "red", + "verbose": "blue", + "debug": "gray", + "trace": "gray" + }, + "white-term": { + "success": "green", + "info": "black", + "warning": "yellow", + "error": "red", + "verbose": "blue", + "debug": "gray", + "trace": "gray" + } +} diff --git a/config/global.php b/config/global.php index 2a3627ea..14ebf7cd 100644 --- a/config/global.php +++ b/config/global.php @@ -27,6 +27,7 @@ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', 'worker_num' => 1, 'dispatch_mode' => 2, + 'max_coroutine' => 30000, //'task_worker_num' => 1, //'task_enable_coroutine' => true ]; @@ -68,9 +69,10 @@ $config['init_atomics'] = [ 'out_count' => 0, //消息发送(调用send_*_msg的统计数量) 'reload_time' => 0, //调用reload功能统计数量 'wait_msg_id' => 0, //协程挂起id自增 - 'info_level' => 2, //终端显示的log等级 ]; +$config["info_level"] = 2; + /** 自动保存的缓存保存时间(秒) */ $config['auto_save_interval'] = 900; @@ -88,7 +90,7 @@ $config['static_file_server'] = [ /** 注册 Swoole Server 事件注解的类列表 */ $config['server_event_handler_class'] = [ - \Framework\ServerEventHandler::class, + \ZM\Event\ServerEventHandler::class, ]; return $config; diff --git a/phar-starter.php b/phar-starter.php deleted file mode 100644 index ba2fc9a0..00000000 --- a/phar-starter.php +++ /dev/null @@ -1,85 +0,0 @@ - 30000, -]); - -date_default_timezone_set("Asia/Shanghai"); - -define('WORKING_DIR', __DIR__); -define('FRAMEWORK_DIR', __DIR__); -define('LOAD_MODE', 2); - -$s = new FrameworkLoader($argv); - -function loadPhp($dir) { - $dirs = scandir($dir); - foreach ($dirs as $v) { - $path = $dir . '/' . $v; - if (is_dir($path)) { - loadPhp($path); - } else { - if (pathinfo($dir . '/' . $v)['extension'] == 'php') { - if(pathinfo($dir . '/' . $v)['basename'] == 'terminal_listener.php') continue; - //echo 'loading '.$path.PHP_EOL; - require_once $path; - } - } - } -} - -function testEnvironment() { - $current_dir = realpath('.'); - @mkdir($current_dir . '/config/'); - if (!is_file($current_dir . '/config/global.php')) { - echo "Exporting default global config...\n"; - $global = file_get_contents(__DIR__ . '/config/global.php'); - $global = str_replace("WORKING_DIR", 'realpath(__DIR__ . "/../")', $global); - file_put_contents($current_dir . '/config/global.php', $global); - } - if (!is_file($current_dir . '/config/file_header.json')) { - echo "Exporting default file_header config...\n"; - $global = file_get_contents(__DIR__ . '/config/file_header.json'); - file_put_contents($current_dir . '/config/file_header.json', $global); - } - if (!is_dir($current_dir . '/resources')) mkdir($current_dir . '/resources'); - if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src'); - if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src'); - if (!is_dir($current_dir . '/src/Module')) { - mkdir($current_dir . '/src/Module'); - mkdir($current_dir . '/src/Module/Example'); - file_put_contents($current_dir . '/src/Module/Example/Hello.php', file_get_contents(__DIR__ . '/tmp/Hello.php.bak')); - mkdir($current_dir . '/src/Module/Middleware'); - file_put_contents($current_dir . '/src/Module/Middleware/TimerMiddleware.php', file_get_contents(__DIR__ . '/tmp/TimerMiddleware.php.bak')); - } - if (!is_dir($current_dir . '/src/Custom')) { - mkdir($current_dir . '/src/Custom'); - mkdir($current_dir . '/src/Custom/Annotation'); - mkdir($current_dir . '/src/Custom/Connection'); - file_put_contents($current_dir . '/src/Custom/global_function.php', "get(), [1, 2])) { - $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; - $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; - } - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; - } - echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n"); - } - - static function warning($obj, $head = null) { - if ($head === null) $head = date("[H:i:s]") . " [W] "; - if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { - $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; - $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; - } - if (ZMBuf::$atomics["info_level"]->get() >= 1) { - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; - } - echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "blue" : "yellow") . "\n"); - } - } - - static function info($obj, $head = null) { - if ($head === null) $head = date("[H:i:s] ") . "[I] "; - if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { - $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; - $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; - } - if (ZMBuf::$atomics["info_level"]->get() >= 2) { - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; - } - echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "black" : "lightblue") . "\n"); - } - } - - static function success($obj, $head = null) { - if ($head === null) $head = date("[H:i:s] ") . "[S] "; - if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { - $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; - $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; - } - if (ZMBuf::$atomics["info_level"]->get() >= 2) { - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; - } - echo(self::setColor($head . ($trace ?? "") . $obj, "green") . "\n"); - } - } - - static function verbose($obj, $head = null) { - if ($head === null) $head = date("[H:i:s] ") . "[V] "; - if (ZMBuf::$atomics["info_level"]->get() >= 3) { - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; - } - echo(self::setColor($head . ($trace ?? "") . $obj, "blue") . "\n"); - } - } - - static function debug($msg) { - if (ZMBuf::$atomics["info_level"]->get() >= 4) Console::log(date("[H:i:s] ") . "[D] " . $msg, 'gray'); - } - - static function log($obj, $color = "") { - if (!is_string($obj)) var_dump($obj); - else echo(self::setColor($obj, $color) . "\n"); - } - - static function stackTrace() { - $log = "Stack trace:\n"; - $trace = debug_backtrace(); - //array_shift($trace); - foreach ($trace as $i => $t) { - if (!isset($t['file'])) { - $t['file'] = 'unknown'; - } - if (!isset($t['line'])) { - $t['line'] = 0; - } - if (!isset($t['function'])) { - $t['function'] = 'unknown'; - } - $log .= "#$i {$t['file']}({$t['line']}): "; - if (isset($t['object']) and is_object($t['object'])) { - $log .= get_class($t['object']) . '->'; - } - $log .= "{$t['function']}()\n"; - } - $log = Console::setColor($log, "gray"); - echo $log; - } - - static function listenConsole() { - if (in_array('--disable-console-input', FrameworkLoader::$argv) || in_array('--debug-mode', FrameworkLoader::$argv)) { - self::info("ConsoleCommand disabled."); - return; - } - global $terminal_id; - global $port; - $port = ZMBuf::globals("port"); - $vss = new SwooleEventAt(); - $vss->type = "open"; - $vss->level = 256; - $vss->rule = "connectType:terminal"; - $terminal_id = call_user_func(function () { - try { - $data = random_bytes(16); - } catch (Exception $e) { - return ""; - } - $data[6] = chr(ord($data[6]) & 0x0f | 0x40); - $data[8] = chr(ord($data[8]) & 0x3f | 0x80); - return strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))); - }); - $vss->callback = function(?WSConnection $conn) use ($terminal_id){ - $req = ctx()->getRequest(); - if($conn->getType() != "terminal") return false; - Console::debug("Terminal fd: ".$conn->fd); - if(($req->header["x-terminal-id"] ?? "") != $terminal_id) { - $conn->close(); - return false; - } - return false; - }; - ZMBuf::$events[SwooleEventAt::class][] = $vss; - $vss2 = new SwooleEventAt(); - $vss2->type = "message"; - $vss2->rule = "connectType:terminal"; - $vss2->callback = function(?WSConnection $conn){ - if ($conn === null) return false; - if($conn->getType() != "terminal") return false; - $cmd = ctx()->getFrame()->data; - self::executeCommand($cmd); - return false; - }; - ZMBuf::$events[SwooleEventAt::class][] = $vss2; - go(function () { - global $terminal_id, $port; - $descriptorspec = array( - 0 => STDIN, - 1 => STDOUT, - 2 => STDERR - ); - self::$console_proc = proc_open('php -r \'$terminal_id = "'.$terminal_id.'";$port = '.$port.';require "'.__DIR__.'/terminal_listener.php";\'', $descriptorspec, $pipes); - }); - } - - /** - * @param string $cmd - * @return bool - */ - private static function executeCommand(string $cmd) { - $it = explodeMsg($cmd); - switch ($it[0] ?? '') { - case 'logtest': - Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)"); - Console::error("This is error msg. (0)"); - Console::warning("This is warning msg. (1)"); - Console::info("This is info msg. (2)"); - Console::success("This is success msg. (2)"); - Console::verbose("This is verbose msg. (3)"); - Console::debug("This is debug msg. (4)"); - return true; - case 'call': - $class_name = $it[1]; - $function_name = $it[2]; - $class = new $class_name([]); - call_user_func_array([$class, $function_name], []); - return true; - case 'bc': - $code = base64_decode($it[1] ?? '', true); - try { - eval($code); - } catch (Exception $e) { - } - return true; - case 'echo': - Console::info($it[1]); - return true; - case 'color': - Console::log($it[2], $it[1]); - return true; - case 'stop': - ZMUtil::stop(); - return false; - case 'reload': - case 'r': - ZMUtil::reload(); - return false; - case 'save': - $origin = ZMBuf::$atomics["info_level"]->get(); - //ZMBuf::$atomics["info_level"]->set(3); - DataProvider::saveBuffer(); - //ZMBuf::$atomics["info_level"]->set($origin); - return true; - case '': - return true; - default: - Console::info("Command not found: " . $cmd); - return true; - } - } - - public static function withSleep(string $string, int $int) { - self::info($string); - sleep($int); - } -} diff --git a/src/Framework/FrameworkLoader.php b/src/Framework/FrameworkLoader.php deleted file mode 100644 index 3a93219d..00000000 --- a/src/Framework/FrameworkLoader.php +++ /dev/null @@ -1,186 +0,0 @@ -requireGlobalFunctions(); - if (LOAD_MODE == 0) define("WORKING_DIR", getcwd()); - elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); - elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL; - //$this->registerAutoloader('classLoader'); - require_once "DataProvider.php"; - if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) { - /** @noinspection PhpIncludeInspection */ - require_once DataProvider::getWorkingDir() . "/vendor/autoload.php"; - } - if (LOAD_MODE == 2) { - require_once FRAMEWORK_DIR . "/vendor/autoload.php"; - spl_autoload_register('phar_classloader'); - } - - - self::$settings = new GlobalConfig(); - if (self::$settings->get("debug_mode") === true) { - $args[] = "--debug-mode"; - $args[] = "--disable-console-input"; - } - self::$argv = $args; - if (!in_array("--debug-mode", self::$argv)) { - Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); - } - self::$settings = new GlobalConfig(); - ZMBuf::$globals = self::$settings; - if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file"); - $this->defineProperties(); - - //start swoole Framework - $this->selfCheck(); - try { - $this->server = new Server(self::$settings->get("host"), self::$settings->get("port")); - $settings = self::$settings->get("swoole"); - if (in_array("--daemon", $args)) { - $settings["daemonize"] = 1; - Console::log("已启用守护进程,输出重定向到 " . $settings["log_file"]); - self::$argv[] = "--disable-console-input"; - } - $this->server->set($settings); - $all_event_class = self::$settings->get("server_event_handler_class") ?? []; - if (!in_array(ServerEventHandler::class, $all_event_class)) { - $all_event_class[] = ServerEventHandler::class; - } - $event_list = []; - foreach ($all_event_class as $v) { - $reader = new AnnotationReader(); - $reflection_class = new ReflectionClass($v); - $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); - foreach ($methods as $vs) { - $method_annotations = $reader->getMethodAnnotations($vs); - if ($method_annotations != []) { - $annotation = $method_annotations[0]; - if ($annotation instanceof OnEvent) { - $annotation->class = $v; - $annotation->method = $vs->getName(); - $event_list[strtolower($annotation->event)] = $annotation; - } - } - } - } - foreach ($event_list as $k => $v) { - $this->server->on($k, function (...$param) use ($v) { - $c = $v->class; - //echo $c.PHP_EOL; - $c = new $c(); - call_user_func_array([$c, $v->method], $param); - }); - } - - ZMBuf::initAtomic(); - if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1"); - if (in_array("--log-error", $args)) ZMBuf::$atomics["info_level"]->set(0); - if (in_array("--log-warning", $args)) ZMBuf::$atomics["info_level"]->set(1); - if (in_array("--log-info", $args)) ZMBuf::$atomics["info_level"]->set(2); - if (in_array("--log-verbose", $args)) ZMBuf::$atomics["info_level"]->set(3); - if (in_array("--log-debug", $args)) ZMBuf::$atomics["info_level"]->set(4); - Console::log( - "host: " . self::$settings->get("host") . - ", port: " . self::$settings->get("port") . - ", log_level: " . ZMBuf::$atomics["info_level"]->get() . - ", version: " . ZM_VERSION . - "\nworking_dir: " . DataProvider::getWorkingDir() - ); - global $motd; - if (!file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { - echo $motd; - } else { - echo file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); - } - if (in_array("--debug-mode", self::$argv)) - Console::warning("You are in debug mode, do not use in production!"); - $this->server->start(); - } catch (Exception $e) { - Console::error("Framework初始化出现错误,请检查!"); - Console::error($e->getMessage()); - die; - } - } - - private function requireGlobalFunctions() { - require_once __DIR__ . '/global_functions.php'; - } - - private function defineProperties() { - define("ZM_START_TIME", microtime(true)); - define("ZM_DATA", self::$settings->get("zm_data")); - define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown"); - define("CONFIG_DIR", self::$settings->get("config_dir")); - define("CRASH_DIR", self::$settings->get("crash_dir")); - @mkdir(ZM_DATA); - @mkdir(CONFIG_DIR); - @mkdir(CRASH_DIR); - define("ZM_MATCH_ALL", 0); - define("ZM_MATCH_FIRST", 1); - define("ZM_MATCH_NUMBER", 2); - define("ZM_MATCH_SECOND", 3); - define("ZM_BREAKPOINT", 'if(in_array("--debug-mode", \Framework\FrameworkLoader::$argv)) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));'); - define("BP", ZM_BREAKPOINT); - define("ZM_DEFAULT_FETCH_MODE", self::$settings->get("sql_config")["sql_default_fetch_mode"] ?? 4); - } - - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); - if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); - //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (!extension_loaded("ctype")) die("Can not find ctype extension.\n"); - if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); - //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); - //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); - //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); - return true; - } -} - -global $motd; -$motd = <<success = true; - $this->config = $config; - } - - public function get($key) { - $r = $this->config[$key] ?? null; - if ($r === null) return null; - return $r; - } - - public function getAll() { - return $this->config; - } -} diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index b87b350c..0539f93f 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -4,12 +4,12 @@ namespace Module\Example; -use Framework\Console; +use ZM\ConnectionManager\ConnectionObject; +use ZM\Console\Console; use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\Middleware; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Swoole\SwooleEventAt; -use ZM\Connection\CQConnection; use ZM\Utils\ZMUtil; /** @@ -24,8 +24,8 @@ class Hello * @SwooleEventAt("open",rule="connectType:qq") * @param $conn */ - public function onConnect(CQConnection $conn) { - Console::info("机器人 " . $conn->getQQ() . " 已连接!"); + public function onConnect(ConnectionObject $conn) { + Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!"); } /** @@ -34,7 +34,7 @@ class Hello */ public function onDisconnect() { $conn = ctx()->getConnection(); - Console::info("机器人 " . $conn->getQQ() . " 已断开连接!"); + Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); } /** @@ -60,13 +60,13 @@ class Hello */ public function randNum($arg) { // 获取第一个数字类型的参数 - $num1 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第一个数字"); + $num1 = ctx()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第一个数字"); // 获取第二个数字类型的参数 - $num2 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第二个数字"); + $num2 = ctx()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第二个数字"); $a = min(intval($num1), intval($num2)); $b = max(intval($num1), intval($num2)); // 回复用户结果 - context()->reply("随机数是:".mt_rand($a, $b)); + ctx()->reply("随机数是:".mt_rand($a, $b)); } /** @@ -94,6 +94,6 @@ class Hello */ public function closeUnknownConn() { Console::info("Unknown connection , I will close it."); - context()->getConnection()->close(); + server()->close(ctx()->getConnection()->getFd()); } } diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php index e78e8b67..8968c4ea 100644 --- a/src/Module/Middleware/TimerMiddleware.php +++ b/src/Module/Middleware/TimerMiddleware.php @@ -2,10 +2,10 @@ namespace Module\Middleware; -use Framework\Console; use ZM\Annotation\Http\After; use ZM\Annotation\Http\Before; use ZM\Annotation\Http\MiddlewareClass; +use ZM\Console\Console; use ZM\Http\MiddlewareInterface; /** diff --git a/src/Scheduler/MessageEvent.php b/src/Scheduler/MessageEvent.php deleted file mode 100644 index b3e67f6e..00000000 --- a/src/Scheduler/MessageEvent.php +++ /dev/null @@ -1,29 +0,0 @@ -client = $client; - $this->frame = $frame; - } - - public function onActivate() { - //TODO: 写Scheduler计时器内的处理逻辑 - } -} \ No newline at end of file diff --git a/src/Scheduler/Scheduler.php b/src/Scheduler/Scheduler.php deleted file mode 100644 index 3d8a1deb..00000000 --- a/src/Scheduler/Scheduler.php +++ /dev/null @@ -1,140 +0,0 @@ -initProcess(); - elseif ($method == self::REMOTE) $this->initRemote(); - } - - private function initProcess() { //TODO: 完成Process模式的代码 - $m_pid = posix_getpid(); - $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true); - $this->pid = $this->process->start(); - while (1) { - $ret = Process::wait(); - if ($ret) { - $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true); - $this->pid = $this->process->start(); - echo "Reboot done.\n"; - } - } - } - - private function initRemote() { - define('WORKING_DIR', __DIR__ . '../..'); - $this->requireGlobalFunctions(); - $this->registerAutoloader('classLoader'); - $this->settings = new GlobalConfig(); - if (!$this->settings->success) die("Failed to load global config. Please check config/global.php file"); - $this->defineProperties(); - - //start swoole Framework - $this->selfCheck(); - try { - $host = $this->settings->get("scheduler")["host"]; - $port = $this->settings->get("scheduler")["port"]; - $token = $this->settings->get("scheduler")["token"]; - $this->client = new Client($host, $port); - $path = "/" . ($token != "" ? ("?token=" . urlencode($token)) : ""); - while (true) { - if ($this->client->upgrade($path)) { - while (true) { - $recv = $this->client->recv(); - if ($recv instanceof Frame) { - (new MessageEvent($this->client, $recv))->onActivate(); - } else { - break; - } - } - } else { - Console::warning("无法连接Framework,将在5秒后重连..."); - Coroutine::sleep(5); - } - } - } catch (Exception $e) { - Console::error($e); - } - } - - private function requireGlobalFunctions() { - /** @noinspection PhpIncludeInspection */ - require WORKING_DIR . '/src/Framework/global_functions.php'; - } - - private function registerAutoloader(string $string) { - if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !"); - } - - private function defineProperties() { - define("ZM_START_TIME", microtime(true)); - define("ZM_DATA", $this->settings->get("zm_data")); - //define("CONFIG_DIR", $this->settings->get("config_dir")); - define("CRASH_DIR", $this->settings->get("crash_dir")); - } - - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); - //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); - if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); - return true; - } - - private static function onWork(Process $worker, $m_pid) { - swoole_set_process_name('php-scheduler'); - for ($j = 0; $j < 16000; $j++) { - self::checkMpid($worker, $m_pid); - echo "msg: {$j}\n"; - sleep(1); - } - } - - private static function checkMpid(Process $worker, $m_pid) { - if (!Process::kill($m_pid, 0)) { - $worker->exit(); //主进程死了我也死 - // 这句提示,实际是看不到的.需要写到日志中 - echo "Master process exited, I [{$worker['pid']}] also quit\n"; - } - } -} \ No newline at end of file diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php index d8e172d4..346fa8ce 100644 --- a/src/ZM/API/CQ.php +++ b/src/ZM/API/CQ.php @@ -4,7 +4,7 @@ namespace ZM\API; -use Framework\Console; +use ZM\Console\Console; use ZM\Utils\ZMUtil; class CQ diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index b715a595..605c00b3 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -3,14 +3,11 @@ namespace ZM\API; - use Co; -use Framework\Console; -use Framework\ZMBuf; -use ZM\Connection\ConnectionManager; -use ZM\Connection\CQConnection; +use ZM\ConnectionManager\ConnectionObject; +use ZM\Console\Console; use ZM\Event\EventHandler; -use ZM\Utils\ZMRobot; +use ZM\Store\ZMBuf; /** * @method static send_private_msg($self_id, $params, $function = null) @@ -67,143 +64,15 @@ use ZM\Utils\ZMRobot; * @method static set_friend_add_request_async($self_id, $params, $function = null) * @method static set_group_add_request_async($self_id, $params, $function = null) */ -class CQAPI +trait CQAPI { - public static function quick_reply(CQConnection $conn, $data, $msg, $yield = null) { - switch ($data["message_type"]) { - case "group": - return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg); - case "private": - return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg); - case "discuss": - return (new ZMRobot($conn))->setCallback($yield)->sendDiscussMsg($data["discuss_id"], $msg); - } - return null; - } - /** - * @param $name - * @param $arg - * @return bool - * @deprecated - */ - public static function __callStatic($name, $arg) { - trigger_error("This dynamic CQAPI calling method will be removed after 2.0 version.", E_USER_DEPRECATED); - $all = self::getSupportedAPIs(); - $find = null; - if (in_array($name, $all)) $find = $name; - else { - foreach ($all as $v) { - if (strtolower($name) == strtolower(str_replace("_", "", $v))) { - $find = $v; - break; - } - } - } - if ($find === null) { - Console::warning("Unknown API " . $name); - return false; - } - $reply = ["action" => $find]; - if (!is_array($arg[1])) { - Console::warning("Error when parsing params. Please make sure your params is an array."); - return false; - } - if ($arg[1] != []) { - $reply["params"] = $arg[1]; - } - if (!($arg[0] instanceof CQConnection)) { - $robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]); - if ($robot == []) { - Console::warning("发送错误,机器人连接不存在!"); - return false; - } - $arg[0] = $robot[0]; - } - return self::processAPI($arg[0], $reply, $arg[2] ?? null); - } - - /********************** non-API Part **********************/ - - private static function getSupportedAPIs() { - return [ - "send_private_msg", - "send_group_msg", - "send_discuss_msg", - "send_msg", - "delete_msg", - "send_like", - "set_group_kick", - "set_group_ban", - "set_group_anonymous_ban", - "set_group_whole_ban", - "set_group_admin", - "set_group_anonymous", - "set_group_card", - "set_group_leave", - "set_group_special_title", - "set_discuss_leave", - "set_friend_add_request", - "set_group_add_request", - "get_login_info", - "get_stranger_info", - "get_group_list", - "get_group_member_info", - "get_group_member_list", - "get_cookies", - "get_csrf_token", - "get_credentials", - "get_record", - "get_status", - "get_version_info", - "set_restart", - "set_restart_plugin", - "clean_data_dir", - "clean_plugin_log", - "_get_friend_list", - "_get_group_info", - "_get_vip_info", - //异步API - "send_private_msg_async", - "send_group_msg_async", - "send_discuss_msg_async", - "send_msg_async", - "delete_msg_async", - "set_group_kick_async", - "set_group_ban_async", - "set_group_anonymous_ban_async", - "set_group_whole_ban_async", - "set_group_admin_async", - "set_group_anonymous_async", - "set_group_card_async", - "set_group_leave_async", - "set_group_special_title_async", - "set_discuss_leave_async", - "set_friend_add_request_async", - "set_group_add_request_async" - ]; - } - - public static function getLoggedAPIs() { - return [ - "send_private_msg", - "send_group_msg", - "send_discuss_msg", - "send_msg", - "send_private_msg_async", - "send_group_msg_async", - "send_discuss_msg_async", - "send_msg_async" - ]; - } - - /** - * @param CQConnection $connection + * @param ConnectionObject $connection * @param $reply * @param |null $function * @return bool|array */ - public static function processAPI($connection, $reply, $function = null) { + private function processAPI($connection, $reply, $function = null) { $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); $reply["echo"] = $api_id; ZMBuf::$atomics["wait_msg_id"]->add(1); diff --git a/src/ZM/Utils/ZMRobot.php b/src/ZM/API/ZMRobot.php similarity index 81% rename from src/ZM/Utils/ZMRobot.php rename to src/ZM/API/ZMRobot.php index 13184072..ad823906 100644 --- a/src/ZM/Utils/ZMRobot.php +++ b/src/ZM/API/ZMRobot.php @@ -1,11 +1,10 @@ - $robot_id]); - if ($r == []) throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); - return new ZMRobot($r[0]); + $r = ManagerGM::getAllByName('qq'); + foreach($r as $v) { + if($v->getOption('connect_id') == $robot_id) return new ZMRobot($v); + } + throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); } /** @@ -40,12 +43,12 @@ class ZMRobot * @return ZMRobot */ public static function getRandom() { - $r = ConnectionManager::getByType("qq"); + $r = ManagerGM::getAllByName('qq'); if($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); return new ZMRobot($r[array_rand($r)]); } - public function __construct(CQConnection $connection) { + public function __construct(ConnectionObject $connection) { $this->connection = $connection; } @@ -60,7 +63,7 @@ class ZMRobot } public function sendPrivateMsg($user_id, $message, $auto_escape = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'user_id' => $user_id, @@ -71,7 +74,7 @@ class ZMRobot } public function sendGroupMsg($group_id, $message, $auto_escape = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -82,7 +85,7 @@ class ZMRobot } public function sendDiscussMsg($discuss_id, $message, $auto_escape = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'discuss_id' => $discuss_id, @@ -93,7 +96,7 @@ class ZMRobot } public function sendMsg($message_type, $target_id, $message, $auto_escape = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'message_type' => $message_type, @@ -105,7 +108,7 @@ class ZMRobot } public function deleteMsg($message_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'message_id' => $message_id @@ -114,7 +117,7 @@ class ZMRobot } public function sendLike($user_id, $times = 1) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'user_id' => $user_id, @@ -124,7 +127,7 @@ class ZMRobot } public function setGroupKick($group_id, $user_id, $reject_add_request = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -135,7 +138,7 @@ class ZMRobot } public function setGroupBan($group_id, $user_id, $duration = 1800) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -146,7 +149,7 @@ class ZMRobot } public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -157,7 +160,7 @@ class ZMRobot } public function setGroupWholeBan($group_id, $enable = true) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -167,7 +170,7 @@ class ZMRobot } public function setGroupAdmin($group_id, $user_id, $enable = true) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -178,7 +181,7 @@ class ZMRobot } public function setGroupAnonymous($group_id, $enable = true) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -188,7 +191,7 @@ class ZMRobot } public function setGroupCard($group_id, $user_id, $card = "") { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -199,7 +202,7 @@ class ZMRobot } public function setGroupLeave($group_id, $is_dismiss = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -209,7 +212,7 @@ class ZMRobot } public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -221,7 +224,7 @@ class ZMRobot } public function setDiscussLeave($discuss_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'discuss_id' => $discuss_id @@ -230,7 +233,7 @@ class ZMRobot } public function setFriendAddRequest($flag, $approve = true, $remark = "") { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'flag' => $flag, @@ -241,7 +244,7 @@ class ZMRobot } public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'flag' => $flag, @@ -253,11 +256,11 @@ class ZMRobot } public function getLoginInfo() { - return CQAPI::processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); + return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); } public function getStrangerInfo($user_id, $no_cache = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'user_id' => $user_id, @@ -267,19 +270,19 @@ class ZMRobot } public function getFriendList() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function getGroupList() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function getGroupInfo($group_id, $no_cache = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -289,7 +292,7 @@ class ZMRobot } public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -300,7 +303,7 @@ class ZMRobot } public function getGroupMemberList($group_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id @@ -309,7 +312,7 @@ class ZMRobot } public function getCookies($domain = "") { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'domain' => $domain @@ -318,13 +321,13 @@ class ZMRobot } public function getCsrfToken() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function getCredentials($domain = "") { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'domain' => $domain @@ -333,7 +336,7 @@ class ZMRobot } public function getRecord($file, $out_format, $full_path = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'file' => $file, @@ -344,7 +347,7 @@ class ZMRobot } public function getImage($file) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'file' => $file @@ -353,31 +356,31 @@ class ZMRobot } public function canSendImage() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function canSendRecord() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function getStatus() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function getVersionInfo() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } public function setRestartPlugin($delay = 0) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'delay' => $delay @@ -386,7 +389,7 @@ class ZMRobot } public function cleanDataDir($data_dir) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'data_dir' => $data_dir @@ -395,7 +398,7 @@ class ZMRobot } public function cleanPluginLog() { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } diff --git a/src/ZM/Utils/ZMRobotExperiment.php b/src/ZM/API/ZMRobotExperiment.php similarity index 82% rename from src/ZM/Utils/ZMRobotExperiment.php rename to src/ZM/API/ZMRobotExperiment.php index 2043fc67..f7b6a565 100644 --- a/src/ZM/Utils/ZMRobotExperiment.php +++ b/src/ZM/API/ZMRobotExperiment.php @@ -1,11 +1,10 @@ connection = $connection; $this->callback = $callback; $this->prefix = $prefix; @@ -39,7 +37,7 @@ class ZMRobotExperiment } public function getFriendList($flat = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'flat' => $flat @@ -48,7 +46,7 @@ class ZMRobotExperiment } public function getGroupInfo($group_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id @@ -57,7 +55,7 @@ class ZMRobotExperiment } public function getVipInfo($user_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'user_id' => $user_id @@ -66,7 +64,7 @@ class ZMRobotExperiment } public function getGroupNotice($group_id) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id @@ -75,7 +73,7 @@ class ZMRobotExperiment } public function sendGroupNotice($group_id, $title, $content) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id, @@ -86,7 +84,7 @@ class ZMRobotExperiment } public function setRestart($clean_log = false, $clean_cache = false, $clean_event = false) { - return CQAPI::processAPI($this->connection, [ + return $this->processAPI($this->connection, [ 'action' => '_' . $this->getActionName(__FUNCTION__), 'params' => [ 'clean_log' => $clean_log, diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index e4b6dbce..af175e6f 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -3,144 +3,205 @@ namespace ZM\Annotation; -use Doctrine\Common\Annotations\{AnnotationException, AnnotationReader}; +use Doctrine\Common\Annotations\AnnotationReader; use Co; -use Framework\{Console, ZMBuf}; +use ZM\ConnectionManager\ConnectionObject; +use ZM\Console\Console; +use ZM\Store\ZMBuf; use Error; use Exception; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use ZM\Annotation\CQ\{CQAfter, - CQAPIResponse, - CQAPISend, - CQBefore, - CQCommand, - CQMessage, - CQMetaEvent, - CQNotice, - CQRequest -}; -use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; +use ZM\Annotation\CQ\CQAfter; +use ZM\Annotation\CQ\CQAPIResponse; +use ZM\Annotation\CQ\CQAPISend; +use ZM\Annotation\CQ\CQBefore; +use ZM\Annotation\CQ\CQCommand; +use ZM\Annotation\CQ\CQMessage; +use ZM\Annotation\CQ\CQMetaEvent; +use ZM\Annotation\CQ\CQNotice; +use ZM\Annotation\CQ\CQRequest; +use ZM\Annotation\Http\After; +use ZM\Annotation\Http\Before; +use ZM\Annotation\Http\Controller; +use ZM\Annotation\Http\HandleException; +use ZM\Annotation\Http\Middleware; +use ZM\Annotation\Http\MiddlewareClass; +use ZM\Annotation\Http\RequestMapping; use Swoole\Timer; use ZM\Annotation\Interfaces\CustomAnnotation; use ZM\Annotation\Interfaces\Level; -use ZM\Annotation\Module\{Closed, InitBuffer, LoadBuffer, SaveBuffer}; -use ZM\Annotation\Swoole\{OnSave, OnStart, OnTick, SwooleEventAfter, SwooleEventAt}; +use ZM\Annotation\Module\Closed; +use ZM\Annotation\Module\InitBuffer; +use ZM\Annotation\Module\LoadBuffer; +use ZM\Annotation\Module\SaveBuffer; +use ZM\Annotation\Swoole\OnSave; +use ZM\Annotation\Swoole\OnStart; +use ZM\Annotation\Swoole\OnTick; +use ZM\Annotation\Swoole\SwooleEventAfter; +use ZM\Annotation\Swoole\SwooleEventAt; use ZM\Annotation\Interfaces\Rule; -use ZM\Connection\WSConnection; use ZM\Event\EventHandler; use ZM\Http\MiddlewareInterface; -use Framework\DataProvider; +use ZM\Utils\DataProvider; use ZM\Utils\ZMUtil; class AnnotationParser { - /** - * 注册各个模块类的注解和模块level的排序 - * @throws ReflectionException - * @throws AnnotationException - */ - public static function registerMods() { - self::loadAnnotationClasses(); - $all_class = getAllClasses(DataProvider::getWorkingDir() . "/src/Module/", "Module"); + private $path_list = []; + + private $start_time; + + private $annotation_map = []; + + public function __construct() { + $this->start_time = microtime(true); + $this->loadAnnotationClasses(); + ZMBuf::$req_mapping = []; ZMBuf::$req_mapping[0] = [ 'id' => 0, 'pid' => -1, 'name' => '/' ]; - $reader = new AnnotationReader(); - foreach ($all_class as $v) { - Console::debug("正在检索 " . $v); - $reflection_class = new ReflectionClass($v); - $class_prefix = ''; - $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); - $class_annotations = $reader->getClassAnnotations($reflection_class); - $middleware_addon = []; - foreach ($class_annotations as $vs) { - if ($vs instanceof Closed) { - continue 2; - } elseif ($vs instanceof Controller) { - Console::debug("找到 Controller 中间件: " . $vs->class); - $class_prefix = $vs->prefix; - } elseif ($vs instanceof SaveBuffer) { - Console::debug("注册自动保存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); - DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder); - } elseif ($vs instanceof LoadBuffer) { - Console::debug("注册到内存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); - ZMBuf::set($vs->buf_name, DataProvider::getJsonData(($vs->sub_folder ?? "") . "/" . $vs->buf_name . ".json")); - } elseif ($vs instanceof InitBuffer) { - ZMBuf::set($vs->buf_name, []); - } elseif ($vs instanceof MiddlewareClass) { - Console::verbose("正在注册中间件 " . $reflection_class->getName()); - $result = [ - "class" => "\\" . $reflection_class->getName() - ]; - foreach ($methods as $vss) { - if ($vss->getName() == "getName") { - /** @var MiddlewareInterface $tmp */ - $tmp = new $v(); - $result["name"] = $tmp->getName(); - continue; + } + + /** + * 注册各个模块类的注解和模块level的排序 + * @throws ReflectionException + */ + public function registerMods() { + foreach($this->path_list as $path) { + $all_class = getAllClasses($path[0], $path[1]); + $reader = new AnnotationReader(); + foreach ($all_class as $v) { + Console::debug("正在检索 " . $v); + $reflection_class = new ReflectionClass($v); + $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); + $class_annotations = $reader->getClassAnnotations($reflection_class); + + //这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析 + /* + $annotation_map: { + Module\Example\Hello: { + class_annotations: [ + 注解对象1, 注解对象2, ... + ], + methods: [ + ReflectionMethod, ReflectionMethod, ... + ], + methods_annotations: { + foo: [ 注解对象1, 注解对象2, ... ], + bar: [ 注解对象1, 注解对象2, ... ], } - $method_annotations = $reader->getMethodAnnotations($vss); - foreach ($method_annotations as $vsss) { - if ($vss instanceof Rule) $vss = self::registerRuleEvent($vsss, $vss, $reflection_class); - else $vss = self::registerMethod($vsss, $vss, $reflection_class); - //echo get_class($vsss) . PHP_EOL; - if ($vsss instanceof Before) $result["before"] = $vsss->method; - if ($vsss instanceof After) $result["after"] = $vsss->method; - if ($vsss instanceof HandleException) { - $result["exceptions"][$vsss->class_name] = $vsss->method; + } + } + */ + $this->annotation_map[$v]["class_annotations"] = $class_annotations; + $this->annotation_map[$v]["methods"] = $methods; + foreach ($methods as $method) { + $this->annotation_map[$v]["methods_annotations"][$method->getName()] = $reader->getMethodAnnotations($method); + } + + /* + $middleware_addon = []; + foreach ($class_annotations as $vs) { + if ($vs instanceof Closed) { + continue 2; + } elseif ($vs instanceof Controller) { + Console::debug("找到 Controller 中间件: " . $vs->class); + $class_prefix = $vs->prefix; + } elseif ($vs instanceof SaveBuffer) { + Console::debug("注册自动保存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); + DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder); + } elseif ($vs instanceof LoadBuffer) { + Console::debug("注册到内存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); + ZMBuf::set($vs->buf_name, DataProvider::getJsonData(($vs->sub_folder ?? "") . "/" . $vs->buf_name . ".json")); + } elseif ($vs instanceof InitBuffer) { + ZMBuf::set($vs->buf_name, []); + } elseif ($vs instanceof MiddlewareClass) { + Console::verbose("正在注册中间件 " . $reflection_class->getName()); + $result = [ + "class" => "\\" . $reflection_class->getName() + ]; + foreach ($methods as $vss) { + if ($vss->getName() == "getName") { + /** @var MiddlewareInterface $tmp * + $tmp = new $v(); + $result["name"] = $tmp->getName(); + continue; + } + $method_annotations = $reader->getMethodAnnotations($vss); + foreach ($method_annotations as $vsss) { + if ($vss instanceof Rule) $vss = self::registerRuleEvent($vsss, $vss, $reflection_class); + else $vss = self::registerMethod($vsss, $vss, $reflection_class); + //echo get_class($vsss) . PHP_EOL; + if ($vsss instanceof Before) $result["before"] = $vsss->method; + if ($vsss instanceof After) $result["after"] = $vsss->method; + if ($vsss instanceof HandleException) { + $result["exceptions"][$vsss->class_name] = $vsss->method; + } } } - } - ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result; - continue 2; - } elseif ($vs instanceof Middleware) { - $middleware_addon[] = $vs; - } elseif ($vs instanceof CustomAnnotation) { - $vs->class = $reflection_class->getName(); - ZMBuf::$events[get_class($vs)][] = $vs; - } - } - foreach ($methods as $vs) { - if ($middleware_addon !== []) { - foreach($middleware_addon as $value){ - Console::debug("Added middleware " . $value->middleware . " to $v -> " . $vs->getName()); - ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $value->middleware; + ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result; + continue 2; + } elseif ($vs instanceof Middleware) { + $middleware_addon[] = $vs; + } elseif ($vs instanceof CustomAnnotation) { + $vs->class = $reflection_class->getName(); + ZMBuf::$events[get_class($vs)][] = $vs; } } - $method_annotations = $reader->getMethodAnnotations($vs); - foreach ($method_annotations as $vss) { - if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class); - else $vss = self::registerMethod($vss, $vs, $reflection_class); - Console::debug("寻找 " . $vs->getName() . " -> " . get_class($vss)); + foreach ($methods as $vs) { + if ($middleware_addon !== []) { + foreach ($middleware_addon as $value) { + Console::debug("Added middleware " . $value->middleware . " to $v -> " . $vs->getName()); + ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $value->middleware; + } + } + $method_annotations = $reader->getMethodAnnotations($vs); + foreach ($method_annotations as $vss) { + if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class); + else $vss = self::registerMethod($vss, $vs, $reflection_class); + Console::debug("寻找 " . $vs->getName() . " -> " . get_class($vss)); - if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss; - elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss; - elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss; - elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss; - elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss; - elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss; - elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss; - elseif ($vss instanceof RequestMapping) { - self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix); - } elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss; - elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss; - elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss; - elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss; - elseif ($vss instanceof OnSave) ZMBuf::$events[OnSave::class][] = $vss; - elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method][] = $vss->middleware; - elseif ($vss instanceof OnTick) self::addTimerTick($vss); - elseif ($vss instanceof CQAPISend) ZMBuf::$events[CQAPISend::class][] = $vss; - elseif ($vss instanceof CQAPIResponse) ZMBuf::$events[CQAPIResponse::class][$vss->retcode] = [$vss->class, $vss->method]; - } + if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss; + elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss; + elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss; + elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss; + elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss; + elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss; + elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss; + elseif ($vss instanceof RequestMapping) { + self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix); + } elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss; + elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss; + elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss; + elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss; + elseif ($vss instanceof OnSave) ZMBuf::$events[OnSave::class][] = $vss; + elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method][] = $vss->middleware; + elseif ($vss instanceof OnTick) self::addTimerTick($vss); + elseif ($vss instanceof CQAPISend) ZMBuf::$events[CQAPISend::class][] = $vss; + elseif ($vss instanceof CQAPIResponse) ZMBuf::$events[CQAPIResponse::class][$vss->retcode] = [$vss->class, $vss->method]; + } + }*/ } } + + + $tree = self::genTree(ZMBuf::$req_mapping); ZMBuf::$req_mapping = $tree[0]; //给支持level的排个序 + + Console::debug("解析注解完毕!"); + if (ZMBuf::isset("timer_count")) { + Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!"); + ZMBuf::unsetCache("timer_count"); + } + } + + public function sortLevels() { foreach (ZMBuf::$events as $class_name => $v) { if (is_a($class_name, Level::class, true)) { for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) { @@ -156,11 +217,6 @@ class AnnotationParser } } } - Console::debug("解析注解完毕!"); - if (ZMBuf::isset("timer_count")) { - Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!"); - ZMBuf::unsetCache("timer_count"); - } } public static function getRuleCallback($rule_str) { @@ -173,9 +229,9 @@ class AnnotationParser //Swoole 事件时走此switch switch ($asp_name) { case "connectType": //websocket连接类型 - $func = function (?WSConnection $connection) use ($rest) { + $func = function (?ConnectionObject $connection) use ($rest) { if ($connection === null) return false; - return $connection->getType() == $rest ? true : false; + return $connection->getName() == $rest ? true : false; }; break; case "containsGet": //handle http request事件时才能用 @@ -323,7 +379,7 @@ class AnnotationParser ZMBuf::$req_mapping = $array; } - private static function loadAnnotationClasses() { + private function loadAnnotationClasses() { $class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation"); foreach ($class as $v) { $s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php"; @@ -387,4 +443,8 @@ class AnnotationParser }); ZMBuf::append("paused_tick", $cid); } + + public function addRegisterPath($path, $indoor_name) { + $this->path_list[] = [$path, $indoor_name]; + } } diff --git a/src/ZM/Annotation/Http/Controller.php b/src/ZM/Annotation/Http/Controller.php index 9556a6bd..347c1caf 100644 --- a/src/ZM/Annotation/Http/Controller.php +++ b/src/ZM/Annotation/Http/Controller.php @@ -6,6 +6,7 @@ namespace ZM\Annotation\Http; use Doctrine\Common\Annotations\Annotation\Required; use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; +use ZM\Annotation\Interfaces\ErgodicAnnotation; /** * Class Controller @@ -13,11 +14,11 @@ use ZM\Annotation\AnnotationBase; * @Target("CLASS") * @package ZM\Annotation\Http */ -class Controller extends AnnotationBase +class Controller extends AnnotationBase implements ErgodicAnnotation { /** * @var string * @Required() */ public $prefix = ''; -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Http/Middleware.php b/src/ZM/Annotation/Http/Middleware.php index 60a928c3..cb83ba63 100644 --- a/src/ZM/Annotation/Http/Middleware.php +++ b/src/ZM/Annotation/Http/Middleware.php @@ -7,6 +7,7 @@ namespace ZM\Annotation\Http; use Doctrine\Common\Annotations\Annotation\Required; use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; +use ZM\Annotation\Interfaces\ErgodicAnnotation; /** * Class Middleware @@ -14,7 +15,7 @@ use ZM\Annotation\AnnotationBase; * @Annotation * @Target("ALL") */ -class Middleware extends AnnotationBase +class Middleware extends AnnotationBase implements ErgodicAnnotation { /** * @var string diff --git a/src/ZM/Annotation/Http/RequestMapping.php b/src/ZM/Annotation/Http/RequestMapping.php index 966ad84d..7a8538dd 100644 --- a/src/ZM/Annotation/Http/RequestMapping.php +++ b/src/ZM/Annotation/Http/RequestMapping.php @@ -10,7 +10,7 @@ use ZM\Annotation\AnnotationBase; /** * Class RequestMapping * @Annotation - * @Target("ALL") + * @Target("METHOD") * @package ZM\Annotation\Http */ class RequestMapping extends AnnotationBase @@ -36,4 +36,4 @@ class RequestMapping extends AnnotationBase * @var array */ public $params = []; -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Http/RequestMethod.php b/src/ZM/Annotation/Http/RequestMethod.php index 2c54129e..6332b331 100644 --- a/src/ZM/Annotation/Http/RequestMethod.php +++ b/src/ZM/Annotation/Http/RequestMethod.php @@ -9,7 +9,6 @@ use ZM\Annotation\AnnotationBase; /** * Class RequestMethod * @Annotation - * * @package ZM\Annotation\Http */ class RequestMethod extends AnnotationBase @@ -27,4 +26,4 @@ class RequestMethod extends AnnotationBase public const DELETE = 'DELETE'; public const OPTIONS = 'OPTIONS'; public const HEAD = 'HEAD'; -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php b/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php new file mode 100644 index 00000000..b6b3be2f --- /dev/null +++ b/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php @@ -0,0 +1,10 @@ +setDescription("Build an \".phar\" file | 将项目构建一个phar包"); + $this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。"); + $this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录"); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $this->output = $output; + $target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/'); + if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir); + if ($target_dir === false) { + $output->writeln(TermColor::color8(31) . "Error: No such file or directory (".__DIR__ . '/../../../resources/'.")" . TermColor::RESET); + return Command::FAILURE; + } + $output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"])); + if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/"; + if (ini_get('phar.readonly') == 1) { + $output->writeln(TermColor::color8(31) . "You need to set \"phar.readonly\" to \"Off\"!"); + $output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing"); + return Command::FAILURE; + } + if (!is_dir($target_dir)) { + $output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET); + return Command::FAILURE; + } + $filename = "server.phar"; + $this->build($target_dir, $filename); + + return Command::SUCCESS; + } + + private function build ($target_dir, $filename) { + @unlink($target_dir . $filename); + $phar = new Phar($target_dir . $filename); + $phar->startBuffering(); + $src = realpath(__DIR__ . '/../../zhamao-framework/'); + $hello = file_get_contents($src . '/src/Module/Example/Hello.php'); + $middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php'); + unlink($src . '/src/Module/Example/Hello.php'); + unlink($src . '/src/Module/Middleware/TimerMiddleware.php'); + $phar->buildFromDirectory($src); + $phar->addFromString('tmp/Hello.php.bak', $hello); + $phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware); + //$phar->compressFiles(Phar::GZ); + $phar->setStub($phar->createDefaultStub('phar-starter.php')); + $phar->stopBuffering(); + file_put_contents($src . '/src/Module/Example/Hello.php', $hello); + file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware); + $this->output->writeln("Successfully built. Location: " . $target_dir . "$filename"); + } +} diff --git a/src/ZM/Command/InitCommand.php b/src/ZM/Command/InitCommand.php new file mode 100644 index 00000000..19090668 --- /dev/null +++ b/src/ZM/Command/InitCommand.php @@ -0,0 +1,101 @@ +setDescription("Initialize framework starter | 初始化框架运行的基础文件"); + $this->setHelp("此命令将会解压以下文件到项目的根目录:\n" . implode("\n", $this->getExtractFiles())); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式,最基本的需要初始化的模式 + $output->writeln("Initializing files"); + $base_path = LOAD_MODE_COMPOSER_PATH; + foreach ($this->extract_files as $file) { + if (!file_exists($base_path . $file)) { + $info = pathinfo($file); + @mkdir($base_path . $info["dirname"], 0777, true); + echo "Copying " . $file . PHP_EOL; + $package_name = ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["name"]); + copy($base_path . "/vendor/" . $package_name . $file, $base_path . $file); + } else { + echo "Skipping " . $file . " , file exists." . PHP_EOL; + } + } + $autoload = [ + "psr-4" => [ + "Module\\" => "src/Module", + "Custom\\" => "src/Custom" + ], + "files" => [ + "src/Custom/global_function.php" + ] + ]; + if (file_exists($base_path . "/composer.json")) { + $composer = json_decode(file_get_contents($base_path . "/composer.json"), true); + if (!isset($composer["autoload"])) { + $composer["autoload"] = $autoload; + } else { + foreach ($autoload["psr-4"] as $k => $v) { + if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v; + } + foreach ($autoload["files"] as $k => $v) { + if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v; + } + } + file_put_contents($base_path . "/composer.json", json_encode($composer, 64 | 128 | 256)); + $output->writeln("Executing composer update command"); + exec("composer update"); + echo PHP_EOL; + } else { + echo("Error occurred. Please check your updates.\n"); + return Command::FAILURE; + } + return Command::SUCCESS; + } elseif (LOAD_MODE === 2) { //从phar启动的框架包,初始化的模式 + $phar_link = new Phar(__DIR__); + $current_dir = pathinfo($phar_link->getPath())["dirname"]; + chdir($current_dir); + $phar_link = "phar://" . $phar_link->getPath(); + foreach ($this->extract_files as $file) { + if (!file_exists($current_dir . $file)) { + $info = pathinfo($file); + @mkdir($current_dir . $info["dirname"], 0777, true); + echo "Copying " . $file . PHP_EOL; + file_put_contents($current_dir . $file, file_get_contents($phar_link . $file)); + } else { + echo "Skipping " . $file . " , file exists." . PHP_EOL; + } + } + } + $output->writeln("initialization must be started with composer-project mode!"); + return Command::FAILURE; + } + + private function getExtractFiles() { + return $this->extract_files; + } +} diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php new file mode 100644 index 00000000..2dac62fe --- /dev/null +++ b/src/ZM/Command/RunServerCommand.php @@ -0,0 +1,51 @@ +setDescription("Run zhamao-framework | 启动框架"); + $this->setHelp("直接运行可以启动"); + $this->addOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"); + $this->addOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"); + $this->addOption("log-verbose", null, null, "调整消息等级到verbose (log-level=3)"); + $this->addOption("log-info", null, null, "调整消息等级到info (log-level=2)"); + $this->addOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"); + $this->addOption("log-error", null, null, "调整消息等级到error (log-level=0)"); + $this->addOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"); + $this->addOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"); + $this->addOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if(($opt = $input->getOption("env")) !== null) { + if(!in_array($opt, ["production", "staging", "development"])) { + $output->writeln(" \"--env\" option only accept production, development and staging ! "); + } + } + // ... put here the code to run in your command + // this method must return an integer number with the "exit status code" + // of the command. You can also use these constants to make code more readable + new Framework($input->getOptions()); + // return this if there was no problem running the command + // (it's equivalent to returning int(0)) + return Command::SUCCESS; + + // or return this if some error happened during the execution + // (it's equivalent to returning int(1)) + // return Command::FAILURE; + } +} diff --git a/src/ZM/Command/SystemdCommand.php b/src/ZM/Command/SystemdCommand.php new file mode 100644 index 00000000..8cea8607 --- /dev/null +++ b/src/ZM/Command/SystemdCommand.php @@ -0,0 +1,20 @@ +self_id = $self_id; - } - - public function getQQ(){ - return $this->self_id; - } - - public function getType() { - return "qq"; - } -} diff --git a/src/ZM/Connection/ConnectionManager.php b/src/ZM/Connection/ConnectionManager.php deleted file mode 100644 index 16e01fb4..00000000 --- a/src/ZM/Connection/ConnectionManager.php +++ /dev/null @@ -1,76 +0,0 @@ -fd == $fd) return $v; - } - return null; - } - - /** - * @param string $type - * @param array $option - * @return WSConnection[]|CQConnection[] - */ - public static function getByType(string $type, $option = []) { - $conn = []; - foreach (ZMBuf::$connect as $v) { - foreach ($option as $ks => $vs) { - if (($v->$ks ?? "") == $vs) continue; - else continue 2; - } - if ($v->getType() == $type) $conn[] = $v; - } - return $conn; - } - - public static function getTypeClassName(string $type) { - switch (strtolower($type)) { - case "qq": - case "universal": - return CQConnection::class; - case "webconsole": - return WCConnection::class; - case "proxy": - return ProxyConnection::class; - case "terminal": - return TerminalConnection::class; - default: - foreach (ZMBuf::$custom_connection_class as $v) { - /** @var WSConnection $r */ - $r = new $v(ZMBuf::$server, -1); - if ($r->getType() == strtolower($type)) return $v; - } - return UnknownConnection::class; - } - } - - public static function close($fd) { - foreach (ZMBuf::$connect as $k => $v) { - if ($v->fd == $fd) { - ZMBuf::$server->close($fd); - unset(ZMBuf::$connect[$k]); - break; - } - } - } - - public static function registerCustomClass() { - $classes = getAllClasses(DataProvider::getWorkingDir(). "/src/Custom/Connection/", "Custom\\Connection"); - ZMBuf::$custom_connection_class = $classes; - } -} diff --git a/src/ZM/Connection/ProxyConnection.php b/src/ZM/Connection/ProxyConnection.php deleted file mode 100644 index f43083f0..00000000 --- a/src/ZM/Connection/ProxyConnection.php +++ /dev/null @@ -1,13 +0,0 @@ -server = $server; - $this->fd = $fd; - } - - public abstract function getType(); - - public function exists() { - return $this->available = $this->server->exist($this->fd); - } - - public function close() { - ConnectionManager::close($this->fd); - } - - public function push($data, $push_error_record = true) { - if ($data === null || $data == "") { - Console::warning("推送了空消息"); - return false; - } - if (!$this->server->exist($this->fd)) { - Console::warning("Swoole 原生 websocket连接池中无此连接"); - return false; - } - if ($this->server->push($this->fd, $data) === false) { - $data = unicode_decode($data); - if ($push_error_record) Console::warning("API push failed. Data: " . $data); - Console::warning("websocket数据未成功推送,长度:" . strlen($data)); - return false; - } - return true; - } -} diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php new file mode 100644 index 00000000..9c81b63d --- /dev/null +++ b/src/ZM/ConsoleApplication.php @@ -0,0 +1,42 @@ +addCommands([ + new RunServerCommand(), //运行主服务的指令控制器 + new InitCommand() //初始化用的,用于项目初始化和phar初始化 + ]); + if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 + } + + /** + * @param InputInterface|null $input + * @param OutputInterface|null $output + * @return int + */ + public function run(InputInterface $input = null, OutputInterface $output = null) { + try { + return parent::run($input, $output); + } catch (Exception $e) { + die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); + } + } +} diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 83c5b600..503b7526 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -5,18 +5,16 @@ namespace ZM\Context; use Co; -use Framework\ZMBuf; use Swoole\Http\Request; use Swoole\WebSocket\Frame; use swoole_server; -use ZM\API\CQAPI; -use ZM\Connection\ConnectionManager; -use ZM\Connection\CQConnection; -use ZM\Connection\WSConnection; +use ZM\ConnectionManager\ConnectionObject; +use ZM\ConnectionManager\ManagerGM; use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; -use ZM\Utils\ZMRobot; +use ZM\API\ZMRobot; +use ZM\Store\ZMBuf; class Context implements ContextInterface { @@ -53,8 +51,8 @@ class Context implements ContextInterface */ public function getResponse() { return ZMBuf::$context[$this->cid]["response"] ?? null; } - /** @return WSConnection */ - public function getConnection() { return ConnectionManager::get($this->getFd()); } + /** @return ConnectionObject|null */ + public function getConnection() { return ManagerGM::get($this->getFd()); } /** * @return int|null @@ -65,8 +63,8 @@ class Context implements ContextInterface * @return ZMRobot|null */ public function getRobot() { - $conn = ConnectionManager::get($this->getFrame()->fd); - return $conn instanceof CQConnection ? new ZMRobot($conn) : null; + $conn = ManagerGM::get($this->getFrame()->fd); + return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null; } public function getMessage() { return ZMBuf::$context[$this->cid]["data"]["message"] ?? null; } @@ -109,7 +107,17 @@ class Context implements ContextInterface case "private": case "discuss": $this->setCache("has_reply", true); - return CQAPI::quick_reply(ConnectionManager::get($this->getFrame()->fd), $this->getData(), $msg, $yield); + $data = $this->getData(); + $conn = $this->getConnection(); + switch ($data["message_type"]) { + case "group": + return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg); + case "private": + return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg); + case "discuss": + return (new ZMRobot($conn))->setCallback($yield)->sendDiscussMsg($data["discuss_id"], $msg); + } + return null; } return false; } diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index d736331d..93d81542 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -7,9 +7,9 @@ namespace ZM\Context; use Swoole\Http\Request; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; -use ZM\Connection\WSConnection; +use ZM\ConnectionManager\ConnectionObject; use ZM\Http\Response; -use ZM\Utils\ZMRobot; +use ZM\API\ZMRobot; interface ContextInterface { @@ -26,7 +26,7 @@ interface ContextInterface public function setData($data); - /** @return WSConnection */ + /** @return ConnectionObject */ public function getConnection(); /** @return int|null */ diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index a2da49a0..6c288471 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -5,8 +5,9 @@ namespace ZM\DB; use Exception; -use framework\Console; -use framework\ZMBuf; +use ZM\Config\ZMConfig; +use ZM\Console\Console; +use ZM\Store\ZMBuf; use PDOException; use PDOStatement; use Swoole\Coroutine; @@ -23,7 +24,7 @@ class DB */ public static function initTableList() { if (!extension_loaded("mysqlnd")) throw new Exception("Can not find mysqlnd PHP extension."); - $result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []); + $result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMConfig::get("global", "sql_config")["sql_database"] . "';", []); foreach ($result as $v) { self::$table_list[] = $v['TABLE_NAME']; } @@ -38,7 +39,7 @@ class DB public static function table($table_name, $enable_cache = null) { if (Table::getTableInstance($table_name) === null) { if (in_array($table_name, self::$table_list)) - return new Table($table_name, $enable_cache ?? ZMBuf::globals("sql_config")["sql_enable_cache"]); + return new Table($table_name, $enable_cache ?? ZMConfig::get("global", "sql_config")["sql_enable_cache"]); elseif(ZMBuf::$sql_pool !== null){ throw new DbException("Table " . $table_name . " not exist in database."); } else { @@ -98,7 +99,7 @@ class DB if (ZMBuf::get("sql_log") === true) { $starttime = microtime(true); } - Console::debug("MySQL: ".$line); + Console::debug("MySQL: ".$line." | ". implode(", ", $params)); try { $conn = ZMBuf::$sql_pool->get(); if ($conn === false) { diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php index 7d0f8995..a7c882af 100644 --- a/src/ZM/DB/SelectBody.php +++ b/src/ZM/DB/SelectBody.php @@ -4,7 +4,7 @@ namespace ZM\DB; -use Framework\Console; +use ZM\Console\Console; use ZM\Exception\DbException; class SelectBody diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php index 11f9492a..ca5d2eb1 100644 --- a/src/ZM/Event/CQ/MessageEvent.php +++ b/src/ZM/Event/CQ/MessageEvent.php @@ -6,25 +6,23 @@ namespace ZM\Event\CQ; use Co; use Doctrine\Common\Annotations\AnnotationException; -use Framework\Console; -use Framework\ZMBuf; +use ZM\ConnectionManager\ConnectionObject; +use ZM\Console\Console; +use ZM\Store\ZMBuf; use ZM\Annotation\CQ\CQAfter; use ZM\Annotation\CQ\CQBefore; use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\CQ\CQMessage; -use ZM\Connection\WSConnection; use ZM\Event\EventHandler; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; -use ZM\ModBase; -use ZM\ModHandleType; class MessageEvent { private $function_call = false; private $data; private $circle; - /** @var WSConnection|Response */ + /** @var ConnectionObject|Response */ private $connection; public function __construct($data, $conn_or_response, $circle = 0) { @@ -84,6 +82,7 @@ class MessageEvent /** * @throws AnnotationException + * @noinspection PhpRedundantCatchClauseInspection */ public function onActivate() { try { @@ -96,7 +95,6 @@ class MessageEvent $word[$k] = trim($word[$k]); } } - /** @var ModBase[] $obj */ $obj = []; foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) { /** @var CQCommand $v */ @@ -107,30 +105,26 @@ class MessageEvent ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) ) { $c = $v->class; - $class_construct = [ - "data" => context()->getData(), - "connection" => context()->getConnection() - ]; if (!isset($obj[$c])) { - $obj[$c] = new $c($class_construct); + $obj[$c] = new $c(); } if ($word[0] != "" && $v->match == $word[0]) { Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) { + $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$word], function ($r) { if (is_string($r)) context()->reply($r); return true; }); return; } elseif (in_array($word[0], $v->alias)) { Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) { + $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$word], function ($r) { if (is_string($r)) context()->reply($r); return true; }); return; } elseif ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, context()->getMessage())) !== false) { Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) { + $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$args], function ($r) { if (is_string($r)) context()->reply($r); return true; }); @@ -138,7 +132,7 @@ class MessageEvent } elseif ($v->fullMatch != "" && (preg_match("/".$v->fullMatch."/u", ctx()->getMessage(), $args)) != 0) { Console::debug("Calling $c -> {$v->method}"); array_shift($args); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) { + $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$args], function ($r) { if (is_string($r)) context()->reply($r); return true; }); @@ -158,10 +152,7 @@ class MessageEvent $c = $v->class; Console::debug("Calling CQMessage: $c -> {$v->method}"); if (!isset($obj[$c])) - $obj[$c] = new $c([ - "data" => context()->getData(), - "connection" => $this->connection - ], ModHandleType::CQ_MESSAGE); + $obj[$c] = new $c(); EventHandler::callWithMiddleware($obj[$c], $v->method, [], [context()->getData()["message"]], function ($r) { if (is_string($r)) context()->reply($r); }); diff --git a/src/ZM/Event/CQ/MetaEvent.php b/src/ZM/Event/CQ/MetaEvent.php index bd418917..52ef7507 100644 --- a/src/ZM/Event/CQ/MetaEvent.php +++ b/src/ZM/Event/CQ/MetaEvent.php @@ -5,19 +5,15 @@ namespace ZM\Event\CQ; use Doctrine\Common\Annotations\AnnotationException; -use Framework\ZMBuf; use ZM\Annotation\CQ\CQBefore; use ZM\Annotation\CQ\CQMetaEvent; -use ZM\Connection\CQConnection; use ZM\Event\EventHandler; use ZM\Exception\WaitTimeoutException; -use ZM\ModBase; -use ZM\ModHandleType; +use ZM\Store\ZMBuf; class MetaEvent { private $data; - /** @var CQConnection */ private $connection; private $circle; @@ -53,7 +49,6 @@ class MetaEvent */ public function onActivate() { try { - /** @var ModBase[] $obj */ $obj = []; foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) { /** @var CQMetaEvent $v */ @@ -62,10 +57,7 @@ class MetaEvent ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) { $c = $v->class; if (!isset($obj[$c])) - $obj[$c] = new $c([ - "data" => $this->data, - "connection" => $this->connection - ], ModHandleType::CQ_META_EVENT); + $obj[$c] = new $c(); EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { if (is_string($r)) context()->reply($r); }); diff --git a/src/ZM/Event/CQ/NoticeEvent.php b/src/ZM/Event/CQ/NoticeEvent.php index 0973f826..4ff05dcf 100644 --- a/src/ZM/Event/CQ/NoticeEvent.php +++ b/src/ZM/Event/CQ/NoticeEvent.php @@ -5,20 +5,16 @@ namespace ZM\Event\CQ; use Doctrine\Common\Annotations\AnnotationException; -use Framework\ZMBuf; use ZM\Annotation\CQ\CQAfter; use ZM\Annotation\CQ\CQBefore; use ZM\Annotation\CQ\CQNotice; -use ZM\Connection\CQConnection; use ZM\Event\EventHandler; use ZM\Exception\WaitTimeoutException; -use ZM\ModBase; -use ZM\ModHandleType; +use ZM\Store\ZMBuf; class NoticeEvent { private $data; - /** @var CQConnection */ private $connection; private $circle; @@ -54,7 +50,6 @@ class NoticeEvent */ public function onActivate() { try { - /** @var ModBase[] $obj */ $obj = []; foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) { /** @var CQNotice $v */ @@ -65,10 +60,7 @@ class NoticeEvent ($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) { $c = $v->class; if (!isset($obj[$c])) - $obj[$c] = new $c([ - "data" => $this->data, - "connection" => $this->connection - ], ModHandleType::CQ_NOTICE); + $obj[$c] = new $c(); EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { if (is_string($r)) context()->reply($r); }); diff --git a/src/ZM/Event/CQ/RequestEvent.php b/src/ZM/Event/CQ/RequestEvent.php index 84f9cf7f..c600fb77 100644 --- a/src/ZM/Event/CQ/RequestEvent.php +++ b/src/ZM/Event/CQ/RequestEvent.php @@ -5,20 +5,16 @@ namespace ZM\Event\CQ; use Doctrine\Common\Annotations\AnnotationException; -use Framework\ZMBuf; use ZM\Annotation\CQ\CQAfter; use ZM\Annotation\CQ\CQBefore; use ZM\Annotation\CQ\CQRequest; -use ZM\Connection\CQConnection; use ZM\Event\EventHandler; use ZM\Exception\WaitTimeoutException; -use ZM\ModBase; -use ZM\ModHandleType; +use ZM\Store\ZMBuf; class RequestEvent { private $data; - /** @var CQConnection */ private $connection; private $circle; @@ -51,10 +47,10 @@ class RequestEvent /** * @throws AnnotationException + * @noinspection PhpRedundantCatchClauseInspection */ public function onActivate() { try { - /** @var ModBase[] $obj */ $obj = []; foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) { /** @var CQRequest $v */ @@ -65,10 +61,7 @@ class RequestEvent ($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) { $c = $v->class; if (!isset($obj[$c])) - $obj[$c] = new $c([ - "data" => $this->data, - "connection" => $this->connection - ], ModHandleType::CQ_REQUEST); + $obj[$c] = new $c(); EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { if (is_string($r)) context()->reply($r); }); diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php index 7d7dce31..84631539 100644 --- a/src/ZM/Event/EventHandler.php +++ b/src/ZM/Event/EventHandler.php @@ -8,8 +8,9 @@ use Co; use Doctrine\Common\Annotations\AnnotationException; use Error; use Exception; -use Framework\Console; -use Framework\ZMBuf; +use ZM\ConnectionManager\ConnectionObject; +use ZM\ConnectionManager\ManagerGM; +use ZM\Console\Console; use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent}; use Swoole\Http\Request; use Swoole\Server; @@ -17,11 +18,10 @@ use Swoole\WebSocket\Frame; use ZM\Annotation\CQ\CQAPIResponse; use ZM\Annotation\CQ\CQAPISend; use ZM\Annotation\Http\MiddlewareClass; -use ZM\Connection\ConnectionManager; -use ZM\Connection\CQConnection; use ZM\Http\MiddlewareInterface; use ZM\Http\Response; -use Framework\DataProvider; +use ZM\Store\ZMBuf; +use ZM\Utils\DataProvider; use ZM\Utils\ZMUtil; class EventHandler @@ -51,7 +51,7 @@ class EventHandler }); ZMBuf::$server = $param0; $r = (new WorkerStartEvent($param0, $param1))->onActivate(); - Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold"); + Console::success("Worker #" . $param0->worker_id . " 已启动"); $r->onAfter(); self::startTick(); } catch (Exception $e) { @@ -69,14 +69,14 @@ class EventHandler case "message": /** @var Frame $param1 */ /** @var Server $param0 */ - $conn = ConnectionManager::get($param1->fd); + $conn = ManagerGM::get($param1->fd); set_coroutine_params(["server" => $param0, "frame" => $param1, "connection" => $conn]); try { (new MessageEvent($param0, $param1))->onActivate()->onAfter(); } catch (Error $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::stackTrace(); + Console::trace(); } break; case "request": @@ -89,7 +89,12 @@ class EventHandler Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] . " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"] ); - if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage()); + if (!$param1->isEnd()) { + if (\ZM\Config\ZMConfig::get("global", "debug_mode")) + $param1->end("Internal server error: " . $e->getMessage()); + else + $param1->end("Internal server error."); + } Console::error("Internal server exception (500), caused by " . get_class($e)); Console::log($e->getTraceAsString(), "gray"); } catch (Error $e) { @@ -100,7 +105,7 @@ class EventHandler ); $doc = "Internal server error
"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - if (ZMBuf::$atomics["info_level"]->get() >= 4) $doc .= $error_msg; + if (Console::getLevel() >= 4) $doc .= $error_msg; if (!$param1->isEnd()) $param1->end($doc); Console::error("Internal server error (500): " . $error_msg); Console::log($e->getTraceAsString(), "gray"); @@ -114,7 +119,7 @@ class EventHandler } catch (Error $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::stackTrace(); + Console::trace(); } break; case "close": @@ -124,7 +129,7 @@ class EventHandler } catch (Error $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::stackTrace(); + Console::trace(); } break; } @@ -142,7 +147,7 @@ class EventHandler ctx()->setCache("level", $level); if ($level >= 5) { Console::warning("Recursive call reached " . $level . " times"); - Console::stackTrace(); + Console::trace(); return false; } $starttime = microtime(true); @@ -220,7 +225,7 @@ class EventHandler } } - public static function callCQAPISend($reply, ?CQConnection $connection) { + public static function callCQAPISend($reply, ?ConnectionObject $connection) { $action = $reply["action"] ?? null; if ($action === null) { Console::warning("API 激活事件异常!"); @@ -235,7 +240,7 @@ class EventHandler foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) { if (($v->action == "" || $v->action == $action) && !$v->with_result) { $c = $v->class; - self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getQQ()]); + self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getOption('connect_id')]); if (context()->getCache("block_continue") === true) break; } } diff --git a/src/Framework/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php similarity index 80% rename from src/Framework/ServerEventHandler.php rename to src/ZM/Event/ServerEventHandler.php index b4cd762b..e7f757de 100644 --- a/src/Framework/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -1,19 +1,18 @@ taskworker === false) { - FrameworkLoader::$run_time = microtime(true); EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id); } else { ob_start(); - AnnotationParser::registerMods(); - //加载Custom目录下的自定义的内部类 - ConnectionManager::registerCustomClass(); + //AnnotationParser::registerMods(); ob_get_clean(); } } @@ -44,7 +39,8 @@ class ServerEventHandler * @throws AnnotationException */ public function onMessage($server, Frame $frame) { - Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd); + if ($frame->fd !== ZMBuf::get("terminal_fd")) + Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd); EventHandler::callSwooleEvent("message", $server, $frame); } diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php index 0c6f9298..fc2b8db7 100644 --- a/src/ZM/Event/Swoole/MessageEvent.php +++ b/src/ZM/Event/Swoole/MessageEvent.php @@ -5,17 +5,15 @@ namespace ZM\Event\Swoole; use Closure; -use Framework\Console; -use Framework\ZMBuf; +use ZM\ConnectionManager\ManagerGM; +use ZM\Console\Console; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; use ZM\Annotation\Swoole\SwooleEventAfter; use ZM\Annotation\Swoole\SwooleEventAt; -use ZM\Connection\ConnectionManager; use Exception; use ZM\Event\EventHandler; -use ZM\ModBase; -use ZM\ModHandleType; +use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; class MessageEvent implements SwooleEvent @@ -39,15 +37,15 @@ class MessageEvent implements SwooleEvent */ public function onActivate() { ZMUtil::checkWait(); - $conn = ConnectionManager::get(context()->getFrame()->fd); + $conn = ManagerGM::get(context()->getFrame()->fd); try { - if ($conn->getType() == "qq") { + if ($conn->getName() == "qq") { $data = json_decode(context()->getFrame()->data, true); if (isset($data["post_type"])) { set_coroutine_params(["data" => $data, "connection" => $conn]); ctx()->setCache("level", 0); - Console::debug("Calling CQ Event from fd=" . $conn->fd); - EventHandler::callCQEvent($data, ConnectionManager::get(context()->getFrame()->fd), 0); + Console::debug("Calling CQ Event from fd=" . $conn->getFd()); + EventHandler::callCQEvent($data, ManagerGM::get(context()->getFrame()->fd), 0); } else{ set_coroutine_params(["connection" => $conn]); EventHandler::callCQResponse($data); @@ -78,10 +76,8 @@ class MessageEvent implements SwooleEvent public function onAfter() { foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) { - $conn = ConnectionManager::get($this->frame->fd); $c = $v->class; - /** @var ModBase $class */ - $class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE); + $class = new $c(); call_user_func_array([$class, $v->method], []); if (context()->getCache("block_continue") === true) break; } @@ -92,7 +88,7 @@ class MessageEvent implements SwooleEvent private function parseSwooleRule($v) { switch (explode(":", $v->rule)[0]) { case "connectType": //websocket连接类型 - if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->frame->fd)); + if ($v->callback instanceof Closure) return call_user_func($v->callback, ManagerGM::get($this->frame->fd)); break; case "dataEqual": //handle websocket message事件时才能用 if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data); diff --git a/src/ZM/Event/Swoole/RequestEvent.php b/src/ZM/Event/Swoole/RequestEvent.php index 83b38552..54c1fbeb 100644 --- a/src/ZM/Event/Swoole/RequestEvent.php +++ b/src/ZM/Event/Swoole/RequestEvent.php @@ -6,14 +6,14 @@ namespace ZM\Event\Swoole; use Closure; use Exception; -use Framework\Console; +use ZM\Console\Console; use Framework\ZMBuf; use Swoole\Http\Request; use ZM\Annotation\Swoole\SwooleEventAfter; use ZM\Annotation\Swoole\SwooleEventAt; use ZM\Event\EventHandler; use ZM\Http\Response; -use Framework\DataProvider; +use ZM\Utils\DataProvider; use ZM\Utils\ZMUtil; class RequestEvent implements SwooleEvent @@ -38,7 +38,7 @@ class RequestEvent implements SwooleEvent */ public function onActivate() { ZMUtil::checkWait(); - foreach (ZMBuf::globals("http_header") as $k => $v) { + foreach (\ZM\Config\ZMConfig::get("global", "http_header") as $k => $v) { $this->response->setHeader($k, $v); } $uri = $this->request->server["request_uri"]; @@ -81,9 +81,9 @@ class RequestEvent implements SwooleEvent } } - if (ZMBuf::globals("static_file_server")["status"]) { - $base_dir = ZMBuf::globals("static_file_server")["document_root"]; - $base_index = ZMBuf::globals("static_file_server")["document_index"]; + if (\ZM\Config\ZMConfig::get("global", "static_file_server")["status"]) { + $base_dir = \ZM\Config\ZMConfig::get("global", "static_file_server")["document_root"]; + $base_index = \ZM\Config\ZMConfig::get("global", "static_file_server")["document_index"]; $uri = $this->request->server["request_uri"]; $path = realpath($base_dir . urldecode($uri)); if ($path !== false) { @@ -116,7 +116,6 @@ class RequestEvent implements SwooleEvent $this->response->end(ZMUtil::getHttpCodePage(404)); return $this; } - context()->setCache("params", $params); if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面 diff --git a/src/ZM/Event/Swoole/WSCloseEvent.php b/src/ZM/Event/Swoole/WSCloseEvent.php index 8a6e418d..0bd6d214 100644 --- a/src/ZM/Event/Swoole/WSCloseEvent.php +++ b/src/ZM/Event/Swoole/WSCloseEvent.php @@ -6,12 +6,13 @@ namespace ZM\Event\Swoole; use Closure; use Doctrine\Common\Annotations\AnnotationException; -use Framework\ZMBuf; +use ZM\ConnectionManager\ManagerGM; +use ZM\Console\Console; use Swoole\Server; use ZM\Annotation\Swoole\SwooleEventAfter; use ZM\Annotation\Swoole\SwooleEventAt; -use ZM\Connection\ConnectionManager; use ZM\Event\EventHandler; +use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; class WSCloseEvent implements SwooleEvent @@ -30,8 +31,9 @@ class WSCloseEvent implements SwooleEvent * @throws AnnotationException */ public function onActivate() { + Console::info("Closed #{$this->fd}"); ZMUtil::checkWait(); - set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ConnectionManager::get($this->fd)]); + set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ManagerGM::get($this->fd)]); foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) { $c = $v->class; @@ -39,7 +41,7 @@ class WSCloseEvent implements SwooleEvent if(context()->getCache("block_continue") === true) break; } } - ConnectionManager::close($this->fd); + ManagerGM::popConnect($this->fd); return $this; } @@ -61,7 +63,7 @@ class WSCloseEvent implements SwooleEvent private function parseSwooleRule($v) { switch (explode(":", $v->rule)[0]) { case "connectType": //websocket连接类型 - if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->fd)); + if ($v->callback instanceof Closure) return call_user_func($v->callback, ManagerGM::get($this->fd)); break; } return true; diff --git a/src/ZM/Event/Swoole/WSOpenEvent.php b/src/ZM/Event/Swoole/WSOpenEvent.php index 833749e1..1524fa3e 100644 --- a/src/ZM/Event/Swoole/WSOpenEvent.php +++ b/src/ZM/Event/Swoole/WSOpenEvent.php @@ -6,19 +6,16 @@ namespace ZM\Event\Swoole; use Closure; use Doctrine\Common\Annotations\AnnotationException; -use Framework\Console; -use Framework\ZMBuf; +use ZM\Config\ZMConfig; +use ZM\ConnectionManager\ConnectionObject; +use ZM\ConnectionManager\ManagerGM; +use ZM\Console\Console; use Swoole\Http\Request; use Swoole\WebSocket\Server; use ZM\Annotation\Swoole\SwooleEventAfter; use ZM\Annotation\Swoole\SwooleEventAt; -use ZM\Connection\ConnectionManager; -use ZM\Connection\CQConnection; -use ZM\Connection\UnknownConnection; -use ZM\Connection\WSConnection; use ZM\Event\EventHandler; -use ZM\ModBase; -use ZM\ModHandleType; +use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; class WSOpenEvent implements SwooleEvent @@ -31,9 +28,7 @@ class WSOpenEvent implements SwooleEvent * @var Request */ private $request; - /** - * @var WSConnection - */ + /** @var ConnectionObject */ private $conn; public function __construct(Server $server, Request $request) { @@ -47,25 +42,28 @@ class WSOpenEvent implements SwooleEvent */ public function onActivate() { ZMUtil::checkWait(); + ManagerGM::pushConnect($this->request->fd); $type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? ""); - $type_conn = ConnectionManager::getTypeClassName($type); - if ($type_conn == CQConnection::class) { + $type_conn = $this->getTypeClassName($type); + if ($type_conn == "qq") { + ManagerGM::setName($this->request->fd, "qq"); $qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? ""; - $self_token = ZMBuf::globals("access_token") ?? ""; - if(isset($this->request->header["authorization"])) { + $self_token = ZMConfig::get("global", "access_token") ?? ""; + if (isset($this->request->header["authorization"])) { Console::debug($this->request->header["authorization"]); } $remote_token = $this->request->get["token"] ?? (isset($this->request->header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : ""); - if ($qq != "" && ($self_token == $remote_token)) $this->conn = new CQConnection($this->server, $this->request->fd, $qq); - else { - $this->conn = new UnknownConnection($this->server, $this->request->fd); + if ($qq != "" && ($self_token == $remote_token)) { + ManagerGM::setOption($this->request->fd, "connect_id", $qq); + $this->conn = ManagerGM::get($this->request->fd); + } else { + $this->conn = ManagerGM::get($this->request->fd); Console::warning("connection of CQ has invalid QQ or token!"); - Console::debug("Remote token: ".$remote_token); + Console::debug("Remote token: " . $remote_token); } } else { - $this->conn = new $type_conn($this->server, $this->request->fd); + $this->conn = ManagerGM::get($this->request->fd); } - ZMBuf::$connect[$this->request->fd] = $this->conn; set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]); foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { @@ -86,11 +84,10 @@ class WSOpenEvent implements SwooleEvent * @inheritDoc */ public function onAfter() { - if (!$this->conn->exists()) return $this; + if (!$this->server->exists($this->conn->getFd())) return $this; foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { - /** @var ModBase $class */ - $class = new $v["class"](["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN); + $class = new $v["class"](); call_user_func_array([$class, $v["method"]], [$this->conn]); if (context()->getCache("block_continue") === true) break; } @@ -106,4 +103,15 @@ class WSOpenEvent implements SwooleEvent } return true; } + + private function getTypeClassName(string $type) { + $map = [ + "qq" => "qq", + "universal" => "qq", + "webconsole" => "webconsole", + "proxy" => "proxy", + "terminal" => "terminal" + ]; + return $map[$type] ?? "default"; + } } diff --git a/src/ZM/Event/Swoole/WorkerStartEvent.php b/src/ZM/Event/Swoole/WorkerStartEvent.php index 5eb11223..d5b5911b 100644 --- a/src/ZM/Event/Swoole/WorkerStartEvent.php +++ b/src/ZM/Event/Swoole/WorkerStartEvent.php @@ -4,7 +4,6 @@ namespace ZM\Event\Swoole; -use Co; use Doctrine\Common\Annotations\AnnotationException; use Exception; use PDO; @@ -14,20 +13,18 @@ use Swoole\Database\PDOConfig; use Swoole\Database\PDOPool; use Swoole\Process; use Swoole\Timer; -use ZM\Annotation\AnnotationBase; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Swoole\OnStart; -use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Connection\ConnectionManager; +use ZM\Config\ZMConfig; use ZM\Context\ContextInterface; use ZM\DB\DB; -use Framework\Console; -use Framework\GlobalConfig; -use Framework\ZMBuf; +use ZM\Console\Console; use Swoole\Server; use ZM\Event\EventHandler; use ZM\Exception\DbException; -use Framework\DataProvider; +use ZM\Store\ZMBuf; +use ZM\Utils\DataProvider; +use ZM\Utils\Terminal; use ZM\Utils\ZMUtil; class WorkerStartEvent implements SwooleEvent @@ -50,50 +47,46 @@ class WorkerStartEvent implements SwooleEvent * @throws DbException */ public function onActivate(): WorkerStartEvent { - Console::info("Worker启动中"); - ZMBuf::$server = $this->server; - Console::listenConsole(); //这个方法只能在这里调用,且如果worker_num不为1的话,此功能不可用 - Process::signal(SIGINT, function () { - Console::warning("Server interrupted by keyboard."); - ZMUtil::stop(true); - }); + Console::info("Worker #{$this->server->worker_id} 启动中"); + ZMBuf::$server = $this->server; ZMBuf::resetCache(); //清空变量缓存 ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 $this->resetConnections();//释放所有与framework的连接 - //设置炸毛buf中储存的对象 - ZMBuf::$globals = new GlobalConfig(); - ZMBuf::$config = []; - $file = scandir(DataProvider::getWorkingDir() . '/config/'); - unset($file[0], $file[1]); - foreach ($file as $k => $v) { - if ($v == "global.php") continue; - $name = explode(".", $v); - if (($prefix = end($name)) == "json") { - ZMBuf::$config[$name[0]] = json_decode(Co::readFile(DataProvider::getWorkingDir() . '/config/' . $v), true); - Console::info("已读取配置文件:" . $v); - } elseif ($prefix == "php") { - ZMBuf::$config[$name[0]] = include_once DataProvider::getWorkingDir() . '/config/' . $v; - if (is_array(ZMBuf::$config[$name[0]])) - Console::info("已读取配置文件:" . $v); - } + global $terminal_id; + + Terminal::listenConsole($terminal_id); //这个方法只能在这里调用,且如果worker_num不为1的话,此功能不可用 + // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器 + if ($this->server->worker_id === 0) { + if($terminal_id !== null) Console::info("监听console输入"); + Process::signal(SIGINT, function () { + echo PHP_EOL; + Console::warning("Server interrupted by keyboard."); + ZMUtil::stop(); + }); + ZMBuf::$atomics['reload_time']->add(1); + $this->setAutosaveTimer(ZMConfig::get("global", "auto_save_interval")); + } else { + Process::signal(SIGINT, function () { + // Do Nothing + }); } - if (ZMBuf::globals("sql_config")["sql_host"] != "") { + if (ZMConfig::get("global", "sql_config")["sql_host"] != "") { Console::info("新建SQL连接池中"); ob_start(); phpinfo(); $str = ob_get_clean(); $str = explode("\n", $str); - foreach($str as $k => $v) { + foreach ($str as $k => $v) { $v = trim($v); - if($v == "") continue; - if(mb_strpos($v, "API Extensions") === false) continue; - if(mb_strpos($v, "pdo_mysql") === false) { + if ($v == "") continue; + if (mb_strpos($v, "API Extensions") === false) continue; + if (mb_strpos($v, "pdo_mysql") === false) { throw new DbException("未安装 mysqlnd php-mysql扩展。"); } } - $sql = ZMBuf::globals("sql_config"); + $sql = ZMConfig::get("global", "sql_config"); ZMBuf::$sql_pool = new PDOPool((new PDOConfig()) ->withHost($sql["sql_host"]) ->withPort($sql["sql_port"]) @@ -107,11 +100,6 @@ class WorkerStartEvent implements SwooleEvent DB::initTableList(); } - ZMBuf::$atomics['reload_time']->add(1); - - Console::info("监听console输入"); - - $this->setAutosaveTimer(ZMBuf::globals("auto_save_interval")); $this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等 return $this; } @@ -126,22 +114,14 @@ class WorkerStartEvent implements SwooleEvent } ZMBuf::unsetCache("wait_start"); set_coroutine_params(["server" => $this->server, "worker_id" => $this->worker_id]); - - foreach (ZMBuf::$events[OnStart::class] ?? [] as $v) { - $class_name = $v->class; - Console::debug("正在调用启动时函数: " . $class_name . " -> " . $v->method); - EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []); - } - foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { - /** @var AnnotationBase $v */ - if (strtolower($v->type) == "workerstart") { + if($this->server->worker_id === 0) { + foreach (ZMBuf::$events[OnStart::class] ?? [] as $v) { $class_name = $v->class; - Console::debug("正在调用启动时函数after: " . $class_name . " -> " . $v->method); + Console::debug("正在调用启动时函数: " . $class_name . " -> " . $v->method); EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []); - if (context()->getCache("block_continue") === true) break; } + Console::debug("@OnStart 执行完毕"); } - Console::debug("调用完毕!"); return $this; } @@ -156,7 +136,6 @@ class WorkerStartEvent implements SwooleEvent } /** - * @throws AnnotationException * @throws ReflectionException * @throws Exception */ @@ -181,15 +160,13 @@ class WorkerStartEvent implements SwooleEvent //加载各个模块的注解类,以及反射 Console::info("检索Module中"); - AnnotationParser::registerMods(); - - //加载Custom目录下的自定义的内部类 - ConnectionManager::registerCustomClass(); + $parser = new AnnotationParser(); + $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module"); + $parser->registerMods(); + $parser->sortLevels(); //加载自定义的全局函数 Console::debug("加载自定义的全局函数中"); - if (file_exists(DataProvider::getWorkingDir() . "/src/Custom/global_function.php")) - require_once DataProvider::getWorkingDir() . "/src/Custom/global_function.php"; $this->afterCheck(); } @@ -204,7 +181,7 @@ class WorkerStartEvent implements SwooleEvent * @throws Exception */ private function afterCheck() { - $context_class = ZMBuf::globals("context_class"); + $context_class = ZMConfig::get("global", "context_class"); if (!is_a($context_class, ContextInterface::class, true)) { throw new Exception("Context class must implemented from ContextInterface!"); } diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index 35906de9..561453d6 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -6,15 +6,13 @@ namespace ZM\Exception; use Exception; use Throwable; -use ZM\ModBase; class WaitTimeoutException extends Exception { - /** @var ModBase */ public $module; public function __construct($module, $message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->module = $module; } -} \ No newline at end of file +} diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php new file mode 100644 index 00000000..1cfbe828 --- /dev/null +++ b/src/ZM/Framework.php @@ -0,0 +1,292 @@ +getTtyWidth(); + if (LOAD_MODE == 0) define("WORKING_DIR", getcwd()); + elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); + elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL; + require_once "Utils/DataProvider.php"; + if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) { + /** @noinspection PhpIncludeInspection */ + require_once DataProvider::getWorkingDir() . "/vendor/autoload.php"; + } + if (LOAD_MODE == 2) { + // Phar 模式,2.0 不提供哦 + //require_once FRAMEWORK_DIR . "/vendor/autoload.php"; + spl_autoload_register('phar_classloader'); + } elseif (LOAD_MODE == 0) { + /** @noinspection PhpIncludeInspection + * @noinspection RedundantSuppression + */ + require_once WORKING_DIR . "/vendor/autoload.php"; + } + + if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { + die("Unable to find source directory.\nMaybe you need to run \"init\"?"); + } + ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config'); + ZMConfig::env($args["env"] ?? ""); + if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error); + + self::$argv = $args; + + $this->defineProperties(); + ZMBuf::initAtomic(); + ManagerGM::init(1024, 0.2, [ + [ + "key" => "connect_id", + "type" => "string", + "size" => 30 + ] + ]); + //start swoole Framework + $this->selfCheck(); + try { + self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); + $this->server_set = ZMConfig::get("global", "swoole"); + Console::init( + ZMConfig::get("global", "info_level"), + self::$server, + $args["log-theme"] ?? "default", + ($o = ZMConfig::get("console_color")) === false ? [] : $o + ); + // 注册 Swoole Server 的事件 + $this->registerServerEvents(); + + $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; + date_default_timezone_set($timezone); + + $this->parseCliArgs(self::$argv); + + $out = [ + "host" => ZMConfig::get("global", "host"), + "port" => ZMConfig::get("global", "port"), + "log_level" => Console::getLevel(), + "version" => ZM_VERSION, + "config" => $args["env"] === null ? 'global.php' : $args["env"], + "working_dir" => DataProvider::getWorkingDir() + ]; + if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) { + $out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"]; + } + if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) { + $out["worker_num"] = $num; + } + $store = ""; + foreach ($out as $k => $v) { + $line = $k . ": " . $v; + if (strlen($line) > 19 && $store == "" || $tty_width < 53) { + Console::log($line); + } else { + if ($store === "") $store = str_pad($line, 19, " ", STR_PAD_RIGHT); + else { + $store .= (" | " . $line); + Console::log($store); + $store = ""; + } + } + } + if ($store != "") Console::log($store); + + self::$server->set($this->server_set); + if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { + $motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); + } else { + $motd = file_get_contents(__DIR__."/../../config/motd.txt"); + } + $motd = explode("\n", $motd); + foreach ($motd as $k => $v) { + $motd[$k] = substr($v, 0, $tty_width); + } + $motd = implode("\n", $motd); + echo $motd; + global $asd; + $asd = get_included_files(); + self::$server->start(); + } catch (Exception $e) { + Console::error("Framework初始化出现错误,请检查!"); + Console::error($e->getMessage()); + die; + } + } + + private function defineProperties() { + define("ZM_START_TIME", microtime(true)); + define("ZM_DATA", ZMConfig::get("global", "zm_data")); + define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown"); + define("CONFIG_DIR", ZMConfig::get("global", "config_dir")); + define("CRASH_DIR", ZMConfig::get("global", "crash_dir")); + @mkdir(ZM_DATA); + @mkdir(CONFIG_DIR); + @mkdir(CRASH_DIR); + define("ZM_MATCH_ALL", 0); + define("ZM_MATCH_FIRST", 1); + define("ZM_MATCH_NUMBER", 2); + define("ZM_MATCH_SECOND", 3); + define("ZM_BREAKPOINT", 'if(Framework::$argv["debug-mode"]) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));'); + define("BP", ZM_BREAKPOINT); + define("ZM_DEFAULT_FETCH_MODE", 4); + } + + private function selfCheck() { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); + //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); + if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); + //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); + //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); + return true; + } + + /** + * 从全局配置文件里读取注入系统事件的类 + * @throws ReflectionException + * @throws ReflectionException + */ + private function registerServerEvents() { + $all_event_class = ZMConfig::get("global", "server_event_handler_class") ?? []; + if (!in_array(ServerEventHandler::class, $all_event_class)) { + $all_event_class[] = ServerEventHandler::class; + } + $event_list = []; + foreach ($all_event_class as $v) { + $reader = new AnnotationReader(); + $reflection_class = new ReflectionClass($v); + $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); + foreach ($methods as $vs) { + $method_annotations = $reader->getMethodAnnotations($vs); + if ($method_annotations != []) { + $annotation = $method_annotations[0]; + if ($annotation instanceof OnEvent) { + $annotation->class = $v; + $annotation->method = $vs->getName(); + $event_list[strtolower($annotation->event)] = $annotation; + } + } + } + } + foreach ($event_list as $k => $v) { + self::$server->on($k, function (...$param) use ($v) { + $c = $v->class; + //echo $c.PHP_EOL; + $c = new $c(); + call_user_func_array([$c, $v->method], $param); + }); + } + } + + /** + * 解析命令行的 $argv 参数们 + * @param $args + * @throws Exception + */ + private function parseCliArgs($args) { + $coroutine_mode = true; + global $terminal_id; + $terminal_id = call_user_func(function () { + try { + $data = random_bytes(16); + } catch (Exception $e) { + return ""; + } + $data[6] = chr(ord($data[6]) & 0x0f | 0x40); + $data[8] = chr(ord($data[8]) & 0x3f | 0x80); + return strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))); + }); + foreach ($args as $x => $y) { + switch ($x) { + case 'debug-mode': + if ($y) { + $coroutine_mode = false; + $terminal_id = null; + Console::warning("You are in debug mode, do not use in production!"); + } + break; + case 'daemon': + if ($y) { + $this->server_set["daemonize"] = 1; + Console::log("已启用守护进程,输出重定向到 " . $this->server_set["log_file"]); + $terminal_id = null; + } + break; + case 'disable-console-input': + if ($y) $terminal_id = null; + break; + case 'remote-shell': + if ($y) { + $host = "127.0.0.1"; + $port = 9599; + RemoteShell::listen(self::$server, $host, $port); + Console::log(Console::setColor("正在监听" . $host . ":" . strval($port) . "的调试接口,请注意安全", "yellow")); + } + break; + case 'log-error': + if ($y) Console::setLevel(0); + break; + case 'log-warning': + if ($y) Console::setLevel(1); + break; + case 'log-info': + if ($y) Console::setLevel(2); + break; + case 'log-verbose': + if ($y) Console::setLevel(3); + break; + case 'log-debug': + if ($y) Console::setLevel(4); + break; + case 'log-theme': + if($y !== null) { + Console::$theme = $y; + } + break; + } + } + if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); + } + + private function getTtyWidth() { + return explode(" ", trim(exec("stty size")))[1]; + } + + public static function getServer() { + return self::$server; + } +} diff --git a/src/ZM/Http/StaticFileHandler.php b/src/ZM/Http/StaticFileHandler.php index fec943bc..f1163405 100644 --- a/src/ZM/Http/StaticFileHandler.php +++ b/src/ZM/Http/StaticFileHandler.php @@ -4,7 +4,7 @@ namespace ZM\Http; -use Framework\Console; +use ZM\Console\Console; use Framework\ZMBuf; use ZM\Utils\ZMUtil; diff --git a/src/ZM/ModBase.php b/src/ZM/ModBase.php deleted file mode 100644 index a6097a89..00000000 --- a/src/ZM/ModBase.php +++ /dev/null @@ -1,170 +0,0 @@ -server = $param0["server"]; - if (isset($param0["frame"])) $this->frame = $param0["frame"]; - if (isset($param0["data"])) $this->data = $param0["data"]; - if (isset($param0["request"])) $this->request = $param0["request"]; - if (isset($param0["response"])) $this->response = $param0["response"]; - if (isset($param0["fd"])) $this->fd = $param0["fd"]; - if (isset($param0["worker_id"])) $this->worker_id = $param0["worker_id"]; - if (isset($param0["connection"])) $this->connection = $param0["connection"]; - $this->handle_type = $handle_type; - } - - /** - * only can used by cq->message event function - * @param $msg - * @param bool $yield - * @return mixed - */ - public function reply($msg, $yield = false) { - switch ($this->data["message_type"]) { - case "group": - case "private": - case "discuss": - return CQAPI::quick_reply($this->connection, $this->data, $msg, $yield); - } - return false; - } - - public function finalReply($msg, $yield = false) { - $this->setBlock(); - if ($msg == "") return true; - return $this->reply($msg, $yield); - } - - /** - * @param string $prompt - * @param int $timeout - * @param string $timeout_prompt - * @return string - * @throws InvalidArgumentException - * @throws WaitTimeoutException - */ - public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") { - if ($prompt != "") $this->reply($prompt); - if (!isset($this->data["user_id"], $this->data["message"], $this->data["self_id"])) - throw new InvalidArgumentException("协程等待参数缺失"); - $cid = Co::getuid(); - $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); - ZMBuf::$atomics["wait_msg_id"]->add(1); - $hang = [ - "coroutine" => $cid, - "user_id" => $this->data["user_id"], - "message" => $this->data["message"], - "self_id" => $this->data["self_id"], - "message_type" => $this->data["message_type"], - "result" => null - ]; - if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { - $hang[$hang["message_type"] . "_id"] = $this->data[$this->data["message_type"] . "_id"]; - } - ZMBuf::appendKey("wait_api", $api_id, $hang); - $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { - $r = ZMBuf::get("wait_api")[$api_id] ?? null; - if ($r !== null) { - Co::resume($r["coroutine"]); - } - }); - - Co::suspend(); - $sess = ZMBuf::get("wait_api")[$api_id]; - ZMBuf::unsetByValue("wait_api", $api_id); - $result = $sess["result"]; - if (isset($id)) swoole_timer_clear($id); - if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); - return $result; - } - - /** - * @param $arg - * @param $mode - * @param $prompt_msg - * @return mixed|string - * @throws InvalidArgumentException - * @throws WaitTimeoutException - */ - public function getArgs(&$arg, $mode, $prompt_msg) { - switch ($mode) { - case ZM_MATCH_ALL: - $p = $arg; - array_shift($p); - return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p)); - case ZM_MATCH_NUMBER: - foreach ($arg as $k => $v) { - if (is_numeric($v)) { - array_splice($arg, $k, 1); - return $v; - } - } - return $this->waitMessage($prompt_msg); - case ZM_MATCH_FIRST: - if (isset($arg[1])) { - $a = $arg[1]; - array_splice($arg, 1, 1); - return $a; - } else { - return $this->waitMessage($prompt_msg); - } - } - throw new InvalidArgumentException(); - } - - public function getMessage() { return $this->data["message"] ?? null; } - - public function getUserId() { return $this->data["user_id"] ?? null; } - - public function getGroupId() { return $this->data["group_id"] ?? null; } - - public function getMessageType() { return $this->data["message_type"] ?? null; } - - public function getRobotId() { return $this->data["self_id"]; } - - public function getConnection() { return $this->connection; } - - public function setBlock($result = true) { context()->setCache("block_continue", $result); } -} diff --git a/src/ZM/ModHandleType.php b/src/ZM/ModHandleType.php deleted file mode 100644 index 1082dfc7..00000000 --- a/src/ZM/ModHandleType.php +++ /dev/null @@ -1,20 +0,0 @@ -get($key); - } - static function config($config_name) { return self::$config[$config_name] ?? null; } public static function resetCache() { self::$cache = []; - self::$connect = []; self::$time_nlp = null; self::$instance = []; } @@ -114,8 +104,9 @@ class ZMBuf * 初始化atomic计数器 */ public static function initAtomic() { - foreach (ZMBuf::globals("init_atomics") as $k => $v) { + foreach (ZMConfig::get("global", "init_atomics") as $k => $v) { self::$atomics[$k] = new Atomic($v); } + self::$atomics["show_log_worker"] = new Atomic(999999); } } diff --git a/src/Framework/DataProvider.php b/src/ZM/Utils/DataProvider.php similarity index 91% rename from src/Framework/DataProvider.php rename to src/ZM/Utils/DataProvider.php index 13de875e..e3e8e57c 100644 --- a/src/Framework/DataProvider.php +++ b/src/ZM/Utils/DataProvider.php @@ -1,10 +1,13 @@ get() >= 3) + if (Console::getLevel() >= 3) echo $head; foreach (self::$buffer_list as $k => $v) { Console::debug("Saving " . $k . " to " . $v); @@ -47,12 +50,12 @@ class DataProvider Console::debug("Calling @OnSave: $c -> $method"); $class->$method(); } - if (ZMBuf::$atomics["info_level"]->get() >= 3) + if (Console::getLevel() >= 3) echo Console::setColor("saved", "blue") . PHP_EOL; } public static function getFrameworkLink() { - return ZMBuf::globals("http_reverse_link"); + return ZMConfig::get("global", "http_reverse_link"); } public static function getJsonData(string $string) { diff --git a/src/Framework/RemoteShell.php b/src/ZM/Utils/RemoteShell.php similarity index 98% rename from src/Framework/RemoteShell.php rename to src/ZM/Utils/RemoteShell.php index 0e718824..701b3e74 100644 --- a/src/Framework/RemoteShell.php +++ b/src/ZM/Utils/RemoteShell.php @@ -40,7 +40,6 @@ class RemoteShell * @throws Exception */ static function listen($serv, $host = "127.0.0.1", $port = 9599) { - Console::warning("正在监听".$host.":".strval($port)."的调试接口,请注意安全"); $port = $serv->listen($host, $port, SWOOLE_SOCK_TCP); if (!$port) { throw new Exception("listen fail."); @@ -261,4 +260,4 @@ function get_arg(&$arg) { } $arg = get_class($arg) . ' Object (' . implode(',', $args) . ')'; } -} \ No newline at end of file +} diff --git a/src/ZM/Utils/SQLPool.php b/src/ZM/Utils/SQLPool.php deleted file mode 100644 index 9fb66280..00000000 --- a/src/ZM/Utils/SQLPool.php +++ /dev/null @@ -1,122 +0,0 @@ -pool = new SplQueue; - $this->info = [ - "host" => ZMBuf::globals("sql_config")["sql_host"], - "port" => ZMBuf::globals("sql_config")["sql_port"], - "user" => ZMBuf::globals("sql_config")["sql_username"], - "password" => ZMBuf::globals("sql_config")["sql_password"], - "database" => ZMBuf::globals("sql_config")["sql_database"] - ]; - Console::debug("新建检测 MySQL 连接的计时器"); - zm_timer_tick(10000, function () { - //Console::debug("正在检测是否有坏死的MySQL连接,当前连接池有 ".count($this->pool) . " 个连接"); - if (count($this->pool) > 0) { - /** @var PDO $cnn */ - $cnn = $this->pool->pop(); - $this->connect_cnt -= 1; - try { - $cnn->getAttribute(PDO::ATTR_SERVER_INFO); - } catch (PDOException $e) { - if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { - Console::info("MySQL 长连接丢失,取消连接"); - unset($cnn); - return; - } - } - $this->pool->push($cnn); - $this->connect_cnt += 1; - } - }); - } - - /** - * 将利用过的连接入队 - * @param $mysql - */ - public function put($mysql) { - $this->pool->push($mysql); - if (($a = array_shift($this->co_list)) !== null) { - Coroutine::resume($a); - } - } - - /** - * 获取队中的连接,如果不存在则创建新的 - * @param bool $no_new_conn - * @return bool|mixed|PDO - */ - public function get($no_new_conn = false) { - if (count($this->pool) == 0 && $this->connect_cnt <= 70) { - if ($no_new_conn) return false; - $this->connect_cnt += 1; - $r = $this->newConnect(); - if ($r !== false) { - return $r; - } else { - $this->connect_cnt -= 1; - return false; - } - } elseif (count($this->pool) > 0) { - /** @var PDO $con */ - $con = $this->pool->pop(); - return $con; - } elseif ($this->connect_cnt > 70) { - $this->co_list[] = Coroutine::getuid(); - Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 " . Coroutine::stats()["coroutine_num"]); - Coroutine::suspend(); - return $this->get($no_new_conn); - } - return false; - } - - public function getCount() { - return $this->pool->count(); - } - - public function destruct() { - // 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出 - $this->available = false; - while (!$this->pool->isEmpty()) { - $this->pool->pop(); - } - } - - private function newConnect() { - //无空闲连接,创建新连接 - $dsn = "mysql:host=" . $this->info["host"] . ";dbname=" . $this->info["database"] . ";charset=utf8"; - try { - $mysql = new PDO($dsn, $this->info["user"], $this->info["password"], array(PDO::ATTR_PERSISTENT => true)); - } catch (PDOException $e) { - Console::error("PDO Error: " . $e->getMessage()); - return false; - } - Console::info("创建SQL连接中,当前有" . $this->connect_cnt . "个连接"); - return $mysql; - } -} diff --git a/src/ZM/Utils/ScheduleManager.php b/src/ZM/Utils/ScheduleManager.php deleted file mode 100644 index e87e15ce..00000000 --- a/src/ZM/Utils/ScheduleManager.php +++ /dev/null @@ -1,10 +0,0 @@ -worker_id === 0) Console::info("ConsoleCommand disabled."); + return; + } + global $terminal_id; + global $port; + $port = ZMConfig::get("global", "port"); + $vss = new SwooleEventAt(); + $vss->type = "open"; + $vss->level = 256; + $vss->rule = "connectType:terminal"; + + $vss->callback = function (?ConnectionObject $conn) use ($terminal_id) { + $req = ctx()->getRequest(); + if ($conn->getName() != "terminal") return false; + Console::debug("Terminal fd: " . $conn->getFd()); + ZMBuf::set("terminal_fd", $conn->getFd()); + if (($req->header["x-terminal-id"] ?? "") != $terminal_id) { + ZMBuf::$server->close($conn->getFd()); + return false; + } + return false; + }; + ZMBuf::$events[SwooleEventAt::class][] = $vss; + $vss2 = new SwooleEventAt(); + $vss2->type = "message"; + $vss2->rule = "connectType:terminal"; + $vss2->callback = function (?ConnectionObject $conn) { + if ($conn === null) return false; + if ($conn->getName() != "terminal") return false; + $cmd = ctx()->getFrame()->data; + self::executeCommand($cmd); + return false; + }; + ZMBuf::$events[SwooleEventAt::class][] = $vss2; + if (ZMBuf::$server->worker_id === 0) { + go(function () { + global $terminal_id, $port; + $descriptorspec = array( + 0 => STDIN, + 1 => STDOUT, + 2 => STDERR + ); + self::$console_proc = proc_open('php -r \'$terminal_id = "' . $terminal_id . '";$port = ' . $port . ';require "' . __DIR__ . '/terminal_listener.php";\'', $descriptorspec, self::$pipes); + }); + } + } + + /** + * @param string $cmd + * @return bool + */ + private static function executeCommand(string $cmd) { + $it = explodeMsg($cmd); + switch ($it[0] ?? '') { + case 'logtest': + Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)"); + Console::error("This is error msg. (0)"); + Console::warning("This is warning msg. (1)"); + Console::info("This is info msg. (2)"); + Console::success("This is success msg. (2)"); + Console::verbose("This is verbose msg. (3)"); + Console::debug("This is debug msg. (4)"); + return true; + case 'call': + $class_name = $it[1]; + $function_name = $it[2]; + $class = new $class_name([]); + call_user_func_array([$class, $function_name], []); + return true; + case 'bc': + $code = base64_decode($it[1] ?? '', true); + try { + eval($code); + } catch (Exception $e) { + } + return true; + case 'echo': + Console::info($it[1]); + return true; + case 'color': + Console::log($it[2], $it[1]); + return true; + case 'stop': + ZMUtil::stop(); + return false; + case 'reload': + case 'r': + ZMUtil::reload(); + return false; + case 'save': + //$origin = ZMBuf::$atomics["info_level"]->get(); + //ZMBuf::$atomics["info_level"]->set(3); + DataProvider::saveBuffer(); + //ZMBuf::$atomics["info_level"]->set($origin); + return true; + case '': + return true; + default: + Console::info("Command not found: " . $cmd); + return true; + } + } +} diff --git a/src/ZM/Utils/ZMRequest.php b/src/ZM/Utils/ZMRequest.php index fe1c5da5..2a6b6895 100644 --- a/src/ZM/Utils/ZMRequest.php +++ b/src/ZM/Utils/ZMRequest.php @@ -4,8 +4,7 @@ namespace ZM\Utils; -use Framework\Console; -use Swlib\Saber; +use ZM\Console\Console; use Swoole\Coroutine\Http\Client; class ZMRequest @@ -79,14 +78,6 @@ class ZMRequest return new ZMWebSocket($url, $set, $header); } - /** - * @param $option - * @return Saber - */ - public static function session($option) { - return Saber::session($option); - } - /** * @param $url * @param array $attribute diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index f09982a5..c4e5515e 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -5,10 +5,10 @@ namespace ZM\Utils; use Co; -use framework\Console; -use Framework\DataProvider; -use Framework\ZMBuf; +use ZM\Config\ZMConfig; +use ZM\Console\Console; use ZM\API\CQ; +use ZM\Store\ZMBuf; class ZMUtil { @@ -34,8 +34,8 @@ class ZMUtil } public static function getHttpCodePage(int $http_code) { - if (isset(ZMBuf::globals("http_default_code_page")[$http_code])) { - return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMBuf::globals("http_default_code_page")[$http_code]); + if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) { + return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); } else return null; } @@ -51,20 +51,10 @@ class ZMUtil ZMBuf::$server->reload(); } - /** - * 解析CQ码 - * @param $msg - * @return array|null - */ - static function getCQ($msg) { - return CQ::getCQ($msg); - } - public static function getModInstance($class) { if (!isset(ZMBuf::$instance[$class])) { Console::debug("Class instance $class not exist, so I created it."); - ZMBuf::$instance[$class] = new $class(); - return ZMBuf::$instance[$class]; + return ZMBuf::$instance[$class] = new $class(); } else { return ZMBuf::$instance[$class]; } diff --git a/src/ZM/Utils/ZMWebSocket.php b/src/ZM/Utils/ZMWebSocket.php index 6340ba7c..cd17b8aa 100644 --- a/src/ZM/Utils/ZMWebSocket.php +++ b/src/ZM/Utils/ZMWebSocket.php @@ -4,7 +4,7 @@ namespace ZM\Utils; -use Framework\Console; +use ZM\Console\Console; use Swoole\Coroutine\Http\Client; use Swoole\WebSocket\Frame; diff --git a/src/Framework/terminal_listener.php b/src/ZM/Utils/terminal_listener.php similarity index 100% rename from src/Framework/terminal_listener.php rename to src/ZM/Utils/terminal_listener.php diff --git a/src/Framework/global_functions.php b/src/ZM/global_functions.php similarity index 94% rename from src/Framework/global_functions.php rename to src/ZM/global_functions.php index daf8315a..167f6b53 100644 --- a/src/Framework/global_functions.php +++ b/src/ZM/global_functions.php @@ -1,16 +1,18 @@ Date: Mon, 31 Aug 2020 10:14:48 +0800 Subject: [PATCH 02/32] initial 2.0 commit --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bbec791b..12bd5184 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,11 @@ ## 开发者注意 -**由于 CQHTTP 不再提供维护,转为 [OneBot 标准](https://github.com/howmanybots/onebot)(原 CQHTTP 插件衍生而来的机器人 HTTP 接口标准),本框架也将在未来改为兼容此标准。** - -**同时将标准实现层独立出来,但默认依赖,对于一些基础的定义层会发生变更。** - -**以上涉及的变更将在下一个大版本 (v2.0.0) 更新,请关注 commit 和 project!** +**此版本目前无法正常运行,还在持续开发中,请勿使用!!!** +**2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。** +[![dev-version2](https://img.shields.io/badge/process-%2530-green)]() From 1510e2f0d0e9f9b417bc9db0631f5bf4ff359c21 Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 31 Aug 2020 10:15:25 +0800 Subject: [PATCH 03/32] initial 2.0 commit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12bd5184..799ebb42 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ **2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。** -[![dev-version2](https://img.shields.io/badge/process-%2530-green)]() +[![dev-version2](https://img.shields.io/badge/process-30%25-green)]() From f91d24aaaadda3b77641b43050f4d1debbcdda1c Mon Sep 17 00:00:00 2001 From: jerry Date: Tue, 29 Sep 2020 15:07:43 +0800 Subject: [PATCH 04/32] initial 2.0.0-a2 commit --- bin/phpunit-swoole | 65 +++ bin/start | 5 +- composer.json | 24 +- config/global.php | 32 +- src/Custom/Annotation/Example.php | 2 +- src/Custom/Command/CustomCommand.php | 31 ++ src/Custom/global_function.php | 3 + src/Module/Example/Hello.php | 31 +- src/Module/Middleware/TimerMiddleware.php | 6 +- src/ZM/API/CQAPI.php | 89 +--- src/ZM/API/ZMRobot.php | 371 +++++++++++-- src/ZM/API/ZMRobotExperiment.php | 102 ---- src/ZM/Annotation/AnnotationParser.php | 409 +++++---------- src/ZM/Annotation/Http/MiddlewareClass.php | 9 +- src/ZM/Annotation/Module/InitBuffer.php | 19 - src/ZM/Annotation/Module/LoadBuffer.php | 24 - src/ZM/Annotation/Module/SaveBuffer.php | 26 - .../Swoole/{OnEvent.php => HandleEvent.php} | 4 +- src/ZM/Annotation/Swoole/OnStart.php | 7 +- src/ZM/Annotation/Swoole/OnTick.php | 5 + .../{SwooleEventAt.php => SwooleEvent.php} | 6 +- src/ZM/Annotation/Swoole/SwooleSetup.php | 18 + src/ZM/Command/PureHttpCommand.php | 75 +++ src/ZM/Command/RunServerCommand.php | 4 +- src/ZM/ConsoleApplication.php | 42 +- src/ZM/Context/Context.php | 45 +- src/ZM/Event/CQ/MessageEvent.php | 126 ++--- src/ZM/Event/CQ/MetaEvent.php | 2 +- src/ZM/Event/EventDispatcher.php | 109 ++++ src/ZM/Event/EventHandler.php | 59 +-- src/ZM/Event/EventManager.php | 62 +++ src/ZM/Event/ServerEventHandler.php | 491 ++++++++++++++++-- src/ZM/Event/Swoole/MessageEvent.php | 8 +- src/ZM/Event/Swoole/RequestEvent.php | 30 +- ...ooleEvent.php => SwooleEventInterface.php} | 8 +- src/ZM/Event/Swoole/WSCloseEvent.php | 9 +- src/ZM/Event/Swoole/WSOpenEvent.php | 21 +- src/ZM/Event/Swoole/WorkerStartEvent.php | 189 ------- src/ZM/Exception/InterruptException.php | 12 + src/ZM/Framework.php | 128 ++--- src/ZM/Http/MiddlewareInterface.php | 2 +- src/ZM/Http/StaticFileHandler.php | 3 +- src/ZM/Module/QQBot.php | 15 + src/ZM/Store/LightCache.php | 129 +++++ src/ZM/Store/ZMBuf.php | 38 +- src/ZM/Utils/CoroutinePool.php | 51 ++ src/ZM/Utils/HttpUtil.php | 117 +++++ src/ZM/Utils/Terminal.php | 79 +-- src/ZM/Utils/ZMRequest.php | 144 ----- src/ZM/Utils/ZMUtil.php | 52 +- src/ZM/Utils/ZMWebSocket.php | 106 ---- src/ZM/global_defines.php | 22 + src/ZM/global_functions.php | 49 +- test/CoroutinePoolTest.php | 28 + test/LightCacheTest.php | 21 + test/ZMTest/Mock/mock.php | 2 + .../AnnotationParserRegisterTest.php | 73 +++ test/ZMTest/Testing/EventDispatcherTest.php | 51 ++ test/test.php | 56 ++ 59 files changed, 2271 insertions(+), 1475 deletions(-) create mode 100755 bin/phpunit-swoole create mode 100644 src/Custom/Command/CustomCommand.php delete mode 100644 src/ZM/API/ZMRobotExperiment.php delete mode 100644 src/ZM/Annotation/Module/InitBuffer.php delete mode 100644 src/ZM/Annotation/Module/LoadBuffer.php delete mode 100644 src/ZM/Annotation/Module/SaveBuffer.php rename src/ZM/Annotation/Swoole/{OnEvent.php => HandleEvent.php} (84%) rename src/ZM/Annotation/Swoole/{SwooleEventAt.php => SwooleEvent.php} (93%) create mode 100644 src/ZM/Annotation/Swoole/SwooleSetup.php create mode 100644 src/ZM/Command/PureHttpCommand.php create mode 100644 src/ZM/Event/EventDispatcher.php create mode 100644 src/ZM/Event/EventManager.php rename src/ZM/Event/Swoole/{SwooleEvent.php => SwooleEventInterface.php} (57%) delete mode 100644 src/ZM/Event/Swoole/WorkerStartEvent.php create mode 100644 src/ZM/Exception/InterruptException.php create mode 100644 src/ZM/Module/QQBot.php create mode 100644 src/ZM/Store/LightCache.php create mode 100644 src/ZM/Utils/CoroutinePool.php create mode 100644 src/ZM/Utils/HttpUtil.php delete mode 100644 src/ZM/Utils/ZMRequest.php delete mode 100644 src/ZM/Utils/ZMWebSocket.php create mode 100644 src/ZM/global_defines.php create mode 100644 test/CoroutinePoolTest.php create mode 100644 test/LightCacheTest.php create mode 100644 test/ZMTest/Mock/mock.php create mode 100644 test/ZMTest/PassedTest/AnnotationParserRegisterTest.php create mode 100644 test/ZMTest/Testing/EventDispatcherTest.php create mode 100644 test/test.php diff --git a/bin/phpunit-swoole b/bin/phpunit-swoole new file mode 100755 index 00000000..eec62ac2 --- /dev/null +++ b/bin/phpunit-swoole @@ -0,0 +1,65 @@ +#!/usr/bin/env php + + * Date: 2018/4/14 下午10:58 + */ + +Co::set([ + 'log_level' => SWOOLE_LOG_INFO, + 'trace_flags' => 0 +]); + +if (!ini_get('date.timezone')) { + ini_set('date.timezone', 'UTC'); +} + +foreach ([ + __DIR__ . '/../../../autoload.php', + __DIR__ . '/../../autoload.php', + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/vendor/autoload.php' + ] as $file +) { + if (file_exists($file)) { + define('PHPUNIT_COMPOSER_INSTALL', $file); + break; + } +} + +if (!defined('PHPUNIT_COMPOSER_INSTALL')) { + fwrite( + STDERR, + 'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL . + ' composer install' . PHP_EOL . PHP_EOL . + 'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL + ); + + die(1); +} else { + if (array_reverse(explode('/', __DIR__))[0] ?? '' === 'test') { + $vendor_dir = dirname(PHPUNIT_COMPOSER_INSTALL); + $bin_unit = "{$vendor_dir}/bin/phpunit"; + $unit_uint = "{$vendor_dir}/phpunit/phpunit/phpunit"; + if (file_exists($bin_unit)) { + @unlink($bin_unit); + @symlink(__FILE__, $bin_unit); + } + if (file_exists($unit_uint)) { + @unlink($unit_uint); + @symlink(__FILE__, $unit_uint); + } + } +} +require PHPUNIT_COMPOSER_INSTALL; +$starttime = microtime(true); +go(function (){ + try{ + PHPUnit\TextUI\Command::main(false); + } catch(Exception $e) { + echo $e->getMessage().PHP_EOL; + } +}); +Swoole\Event::wait(); +echo "Took ".round(microtime(true) - $starttime, 4). "s\n"; diff --git a/bin/start b/bin/start index 78c7a2b8..5a518c8f 100755 --- a/bin/start +++ b/bin/start @@ -5,7 +5,7 @@ use ZM\ConsoleApplication; // 这行是用于开发者自己电脑的调试功能 -$symbol = sha1(is_file("/flag") ? file_get_contents("/flag") : '1') == '6252c0ec7fcbd544c3d6f5f0a162f60407d7a896' || mb_strpos(getcwd(), "/private/tmp"); +$symbol = sha1(is_file("/flag2") ? file_get_contents("/flag2") : '1') == '6252c0ec7fcbd544c3d6f5f0a162f60407d7a896' || mb_strpos(getcwd(), "/private/tmp"); // 首先得判断是直接从library模式启动的框架还是从composer引入library启动的框架 // 判断方法:判断当前目录上面有没有 /vendor 目录,如果没有 /vendor 目录说明是从 composer 引入的 @@ -15,14 +15,13 @@ if (!is_dir(__DIR__ . '/../vendor') || $symbol) { define("LOAD_MODE_COMPOSER_PATH", getcwd()); /** @noinspection PhpIncludeInspection */ require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php"; -} elseif (substr(__DIR__, 0, 7) != 'phar://') { +} elseif (substr(__DIR__, 0, 7) == 'phar://') { define("LOAD_MODE", 2); //phar模式 // 会废弃phar启动的方式,在2.0 } else { define("LOAD_MODE", 0); require_once __DIR__ . "/../vendor/autoload.php"; } - // 终端的命令行功能启动!! $application = new ConsoleApplication("zhamao-framework"); $application->initEnv(); diff --git a/composer.json b/composer.json index c63eeedc..91558a97 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-a1", + "version": "2.0.0-a2", "authors": [ { "name": "whale", @@ -16,24 +16,30 @@ ], "prefer-stable": true, "bin": [ - "bin/start" + "bin/start", + "bin/phpunit-swoole" ], "require": { "php": ">=7.2", "swoole/ide-helper": "@dev", "ext-mbstring": "*", - "swlib/saber": "^1.0", "doctrine/annotations": "~1.10", "ext-json": "*", "ext-posix": "*", "ext-ctype": "*", - "ext-pdo": "*", "psy/psysh": "@stable", "symfony/console": "^5.1", "symfony/polyfill-ctype": "^1.18", "zhamao/connection-manager": "^1.0", "zhamao/console": "^1.0", - "zhamao/config": "^1.0" + "zhamao/config": "^1.0", + "zhamao/request": "^1.0", + "symfony/routing": "^5.1" + }, + "suggest": { + "ext-pdo": "Allows framework connecting with mysql server", + "ext-redis": "Allows framework connecting with redis server", + "ext-inotify": "Enable file watcher feature in framework" }, "autoload": { "psr-4": { @@ -45,6 +51,14 @@ "src/ZM/global_functions.php" ] }, + "autoload-dev": { + "psr-4": { + "ZMTest\\": "test/ZMTest" + }, + "files": [ + "test/ZMTest/Mock/mock.php" + ] + }, "require-dev": { "phpunit/phpunit": "^9.3" } diff --git a/config/global.php b/config/global.php index 14ebf7cd..8a42c83d 100644 --- a/config/global.php +++ b/config/global.php @@ -25,13 +25,21 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/'; /** 对应swoole的server->set参数 */ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', - 'worker_num' => 1, + 'worker_num' => 2, 'dispatch_mode' => 2, 'max_coroutine' => 30000, - //'task_worker_num' => 1, + //'task_worker_num' => 4, //'task_enable_coroutine' => true ]; +/** 轻量字符串缓存,默认开启 */ +$config['light_cache'] = [ + "status" => true, + "size" => 2048, //最多允许储存的条数(需要2的倍数) + "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) + "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多) +]; + /** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ $config['sql_config'] = [ 'sql_host' => '', @@ -46,7 +54,7 @@ $config['sql_config'] = [ PDO::ATTR_EMULATE_PREPARES => false ], 'sql_no_exception' => false, - 'sql_default_fetch_mode' => PDO::FETCH_BOTH // added in 1.5.6 + 'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6 ]; /** CQHTTP连接约定的token */ @@ -65,15 +73,13 @@ $config['http_default_code_page'] = [ /** zhamao-framework在框架启动时初始化的atomic们 */ $config['init_atomics'] = [ - 'in_count' => 0, //消息接收message的统计数量 - 'out_count' => 0, //消息发送(调用send_*_msg的统计数量) - 'reload_time' => 0, //调用reload功能统计数量 - 'wait_msg_id' => 0, //协程挂起id自增 + //'custom_atomic_name' => 0, //自定义添加的Atomic ]; +/** 终端日志显示等级(0-4) */ $config["info_level"] = 2; -/** 自动保存的缓存保存时间(秒) */ +/** 自动保存计时器的缓存保存时间(秒) */ $config['auto_save_interval'] = 900; /** 上下文接口类 implemented from ContextInterface */ @@ -93,4 +99,14 @@ $config['server_event_handler_class'] = [ \ZM\Event\ServerEventHandler::class, ]; +/** 注册自定义指令的类 */ +$config['command_register_class'] = [ + //\Custom\Command\CustomCommand::class +]; + +/** 服务器启用的外部第三方和内部插件 */ +$config['plugins'] = [ + 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 +]; + return $config; diff --git a/src/Custom/Annotation/Example.php b/src/Custom/Annotation/Example.php index e5abd1cb..4c255730 100644 --- a/src/Custom/Annotation/Example.php +++ b/src/Custom/Annotation/Example.php @@ -16,5 +16,5 @@ use ZM\Annotation\Interfaces\CustomAnnotation; class Example extends AnnotationBase implements CustomAnnotation { /** @var string */ - public $str; + public $str = ''; } diff --git a/src/Custom/Command/CustomCommand.php b/src/Custom/Command/CustomCommand.php new file mode 100644 index 00000000..9fe702f4 --- /dev/null +++ b/src/Custom/Command/CustomCommand.php @@ -0,0 +1,31 @@ +setDescription("custom description | 自定义命令的描述字段"); + $this->addOption("failure", null, null, "以错误码为1返回结果"); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output) { + if ($input->getOption("failure")) { + $output->writeln("Hello error! I am wrong message."); + return Command::FAILURE; + } else { + $output->writeln("Hello world! I am successful message."); + return Command::SUCCESS; + } + } +} diff --git a/src/Custom/global_function.php b/src/Custom/global_function.php index 8ebaa871..3fa7ef70 100644 --- a/src/Custom/global_function.php +++ b/src/Custom/global_function.php @@ -1,3 +1,6 @@ getConnection(); + public function onDisconnect(ConnectionObject $conn) { Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); } @@ -66,7 +66,7 @@ class Hello $a = min(intval($num1), intval($num2)); $b = max(intval($num1), intval($num2)); // 回复用户结果 - ctx()->reply("随机数是:".mt_rand($a, $b)); + ctx()->reply("随机数是:" . mt_rand($a, $b)); } /** @@ -87,10 +87,19 @@ class Hello return "Hello Zhamao!"; } + /** + * 使用自定义参数的路由参数 + * @RequestMapping("/whoami/{name}") + * @param $param + * @return string + */ + public function paramGet($param) { + return "Your name: {$param["name"]}"; + } /** * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 - * @SwooleEventAt(type="open",rule="connectType:unknown") + * @SwooleEvent(type="open",rule="connectIsDefault()") */ public function closeUnknownConn() { Console::info("Unknown connection , I will close it."); diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php index 8968c4ea..34e21c7b 100644 --- a/src/Module/Middleware/TimerMiddleware.php +++ b/src/Module/Middleware/TimerMiddleware.php @@ -9,10 +9,10 @@ use ZM\Console\Console; use ZM\Http\MiddlewareInterface; /** - * Class AuthMiddleware + * Class TimerMiddleware * 示例中间件:用于统计路由函数运行时间用的 * @package Module\Middleware - * @MiddlewareClass() + * @MiddlewareClass("timer") */ class TimerMiddleware implements MiddlewareInterface { @@ -33,6 +33,4 @@ class TimerMiddleware implements MiddlewareInterface public function onAfter() { Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms."); } - - public function getName() { return "timer"; } } diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 605c00b3..6e243c62 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -9,61 +9,6 @@ use ZM\Console\Console; use ZM\Event\EventHandler; use ZM\Store\ZMBuf; -/** - * @method static send_private_msg($self_id, $params, $function = null) - * @method static send_group_msg($self_id, $params, $function = null) - * @method static send_discuss_msg($self_id, $params, $function = null) - * @method static send_msg($self_id, $params, $function = null) - * @method static delete_msg($self_id, $params, $function = null) - * @method static send_like($self_id, $params, $function = null) - * @method static set_group_kick($self_id, $params, $function = null) - * @method static set_group_ban($self_id, $params, $function = null) - * @method static set_group_anonymous_ban($self_id, $params, $function = null) - * @method static set_group_whole_ban($self_id, $params, $function = null) - * @method static set_group_admin($self_id, $params, $function = null) - * @method static set_group_anonymous($self_id, $params, $function = null) - * @method static set_group_card($self_id, $params, $function = null) - * @method static set_group_leave($self_id, $params, $function = null) - * @method static set_group_special_title($self_id, $params, $function = null) - * @method static set_discuss_leave($self_id, $params, $function = null) - * @method static set_friend_add_request($self_id, $params, $function = null) - * @method static set_group_add_request($self_id, $params, $function = null) - * @method static get_login_info($self_id, $params, $function = null) - * @method static get_stranger_info($self_id, $params, $function = null) - * @method static get_group_list($self_id, $params, $function = null) - * @method static get_group_member_info($self_id, $params, $function = null) - * @method static get_group_member_list($self_id, $params, $function = null) - * @method static get_cookies($self_id, $params, $function = null) - * @method static get_csrf_token($self_id, $params, $function = null) - * @method static get_credentials($self_id, $params, $function = null) - * @method static get_record($self_id, $params, $function = null) - * @method static get_status($self_id, $params, $function = null) - * @method static get_version_info($self_id, $params, $function = null) - * @method static set_restart($self_id, $params, $function = null) - * @method static set_restart_plugin($self_id, $params, $function = null) - * @method static clean_data_dir($self_id, $params, $function = null) - * @method static clean_plugin_log($self_id, $params, $function = null) - * @method static _get_friend_list($self_id, $params, $function = null) - * @method static _get_group_info($self_id, $params, $function = null) - * @method static _get_vip_info($self_id, $params, $function = null) - * @method static send_private_msg_async($self_id, $params, $function = null) - * @method static send_group_msg_async($self_id, $params, $function = null) - * @method static send_discuss_msg_async($self_id, $params, $function = null) - * @method static send_msg_async($self_id, $params, $function = null) - * @method static delete_msg_async($self_id, $params, $function = null) - * @method static set_group_kick_async($self_id, $params, $function = null) - * @method static set_group_ban_async($self_id, $params, $function = null) - * @method static set_group_anonymous_ban_async($self_id, $params, $function = null) - * @method static set_group_whole_ban_async($self_id, $params, $function = null) - * @method static set_group_admin_async($self_id, $params, $function = null) - * @method static set_group_anonymous_async($self_id, $params, $function = null) - * @method static set_group_card_async($self_id, $params, $function = null) - * @method static set_group_leave_async($self_id, $params, $function = null) - * @method static set_group_special_title_async($self_id, $params, $function = null) - * @method static set_discuss_leave_async($self_id, $params, $function = null) - * @method static set_friend_add_request_async($self_id, $params, $function = null) - * @method static set_group_add_request_async($self_id, $params, $function = null) - */ trait CQAPI { /** @@ -73,34 +18,42 @@ trait CQAPI * @return bool|array */ private function processAPI($connection, $reply, $function = null) { - $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); + if ($connection->getOption("type") === CONN_WEBSOCKET) + return $this->processWebsocketAPI($connection, $reply, $function); + else + return $this->processHttpAPI($connection, $reply, $function); + + + } + + public function processWebsocketAPI($connection, $reply, $function = null) { + $api_id = ZMBuf::atomic("wait_msg_id")->get(); $reply["echo"] = $api_id; - ZMBuf::$atomics["wait_msg_id"]->add(1); + ZMBuf::atomic("wait_msg_id")->add(1); EventHandler::callCQAPISend($reply, $connection); if (is_callable($function)) { ZMBuf::appendKey("sent_api", $api_id, [ "data" => $reply, "time" => microtime(true), "func" => $function, - "self_id" => $connection->getQQ() + "self_id" => $connection->getOption("connect_id") ]); } elseif ($function === true) { ZMBuf::appendKey("sent_api", $api_id, [ "data" => $reply, "time" => microtime(true), "coroutine" => Co::getuid(), - "self_id" => $connection->getQQ() + "self_id" => $connection->getOption("connect_id") ]); } else { ZMBuf::appendKey("sent_api", $api_id, [ "data" => $reply, "time" => microtime(true), - "self_id" => $connection->getQQ() + "self_id" => $connection->getOption("connect_id") ]); } - if ($connection->push(json_encode($reply))) { - //Console::msg($reply, $connection->getQQ()); - ZMBuf::$atomics["out_count"]->add(1); + + if (server()->push($connection->getFd(), json_encode($reply))) { if ($function === true) { Co::suspend(); $data = ZMBuf::get("sent_api")[$api_id]; @@ -114,7 +67,7 @@ trait CQAPI "status" => "failed", "retcode" => -1000, "data" => null, - "self_id" => $connection->getQQ() + "self_id" => $connection->getOption("connect_id") ]; $s = ZMBuf::get("sent_api")[$reply["echo"]]; if (($s["func"] ?? null) !== null) @@ -124,4 +77,12 @@ trait CQAPI return false; } } + + public function processHttpAPI($connection, $reply, $function = null) { + return false; + } + + public function __call($name, $arguments) { + return false; + } } diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php index ad823906..126382bb 100644 --- a/src/ZM/API/ZMRobot.php +++ b/src/ZM/API/ZMRobot.php @@ -11,10 +11,12 @@ use ZM\Exception\RobotNotFoundException; * Class ZMRobot * @package ZM\Utils * @since 1.2 + * @version V11 */ class ZMRobot { use CQAPI; + const API_ASYNC = 1; const API_NORMAL = 0; const API_RATE_LIMITED = 2; @@ -32,22 +34,34 @@ class ZMRobot */ public static function get($robot_id) { $r = ManagerGM::getAllByName('qq'); - foreach($r as $v) { - if($v->getOption('connect_id') == $robot_id) return new ZMRobot($v); + foreach ($r as $v) { + if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v); } throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); } /** - * @throws RobotNotFoundException * @return ZMRobot + * @throws RobotNotFoundException */ public static function getRandom() { $r = ManagerGM::getAllByName('qq'); - if($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); + if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); return new ZMRobot($r[array_rand($r)]); } + /** + * @return ZMRobot[] + */ + public static function getAllRobot() { + $r = ManagerGM::getAllByName('qq'); + $obj = []; + foreach($r as $v) { + $obj[] = new ZMRobot($v); + } + return $obj; + } + public function __construct(ConnectionObject $connection) { $this->connection = $connection; } @@ -62,6 +76,20 @@ class ZMRobot return $this; } + public function getSelfId() { + return $this->connection->getOption('connect_id'); + } + + /* 下面是 OneBot 标准的 V11 公开 API */ + + /** + * 发送私聊消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF + * @param $user_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ public function sendPrivateMsg($user_id, $message, $auto_escape = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -73,6 +101,14 @@ class ZMRobot ], $this->callback); } + /** + * 发送群消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF + * @param $group_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ public function sendGroupMsg($group_id, $message, $auto_escape = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -84,17 +120,15 @@ class ZMRobot ], $this->callback); } - public function sendDiscussMsg($discuss_id, $message, $auto_escape = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'discuss_id' => $discuss_id, - 'message' => $message, - 'auto_escape' => $auto_escape - ] - ], $this->callback); - } - + /** + * 发送消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF + * @param $message_type + * @param $target_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ public function sendMsg($message_type, $target_id, $message, $auto_escape = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -107,6 +141,12 @@ class ZMRobot ], $this->callback); } + /** + * 撤回消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF + * @param $message_id + * @return array|bool|null + */ public function deleteMsg($message_id) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -116,6 +156,43 @@ class ZMRobot ], $this->callback); } + /** + * 获取消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF + * @param $message_id + * @return array|bool|null + */ + public function getMsg($message_id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_id' => $message_id + ] + ], $this->callback); + } + + /** + * 获取合并转发消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF + * @param $id + * @return array|bool|null + */ + public function getForwardMsg($id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'id' => $id + ] + ], $this->callback); + } + + /** + * 发送好友赞 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E + * @param $user_id + * @param int $times + * @return array|bool|null + */ public function sendLike($user_id, $times = 1) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -126,6 +203,14 @@ class ZMRobot ], $this->callback); } + /** + * 群组踢人 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA + * @param $group_id + * @param $user_id + * @param bool $reject_add_request + * @return array|bool|null + */ public function setGroupKick($group_id, $user_id, $reject_add_request = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -137,6 +222,14 @@ class ZMRobot ], $this->callback); } + /** + * 群组单人禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80 + * @param int $group_id + * @param int $user_id + * @param int $duration + * @return array|bool|null + */ public function setGroupBan($group_id, $user_id, $duration = 1800) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -148,6 +241,14 @@ class ZMRobot ], $this->callback); } + /** + * 群组匿名用户禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80 + * @param $group_id + * @param $anonymous_or_flag + * @param int $duration + * @return array|bool|null + */ public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -159,6 +260,13 @@ class ZMRobot ], $this->callback); } + /** + * 群组全员禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80 + * @param $group_id + * @param bool $enable + * @return array|bool|null + */ public function setGroupWholeBan($group_id, $enable = true) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -169,6 +277,14 @@ class ZMRobot ], $this->callback); } + /** + * 群组设置管理员 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98 + * @param $group_id + * @param $user_id + * @param bool $enable + * @return array|bool|null + */ public function setGroupAdmin($group_id, $user_id, $enable = true) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -180,6 +296,13 @@ class ZMRobot ], $this->callback); } + /** + * 群组匿名 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D + * @param $group_id + * @param bool $enable + * @return array|bool|null + */ public function setGroupAnonymous($group_id, $enable = true) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -190,6 +313,14 @@ class ZMRobot ], $this->callback); } + /** + * 设置群名片(群备注) + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8 + * @param $group_id + * @param $user_id + * @param string $card + * @return array|bool|null + */ public function setGroupCard($group_id, $user_id, $card = "") { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -201,6 +332,30 @@ class ZMRobot ], $this->callback); } + /** + * 设置群名 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D + * @param $group_id + * @param $group_name + * @return array|bool|null + */ + public function setGroupName($group_id, $group_name) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'group_name' => $group_name + ] + ], $this->callback); + } + + /** + * 退出群组 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84 + * @param $group_id + * @param bool $is_dismiss + * @return array|bool|null + */ public function setGroupLeave($group_id, $is_dismiss = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -211,6 +366,15 @@ class ZMRobot ], $this->callback); } + /** + * 设置群组专属头衔 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94 + * @param $group_id + * @param $user_id + * @param string $special_title + * @param int $duration + * @return array|bool|null + */ public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -223,15 +387,14 @@ class ZMRobot ], $this->callback); } - public function setDiscussLeave($discuss_id) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'discuss_id' => $discuss_id - ] - ], $this->callback); - } - + /** + * 处理加好友请求 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82 + * @param $flag + * @param bool $approve + * @param string $remark + * @return array|bool|null + */ public function setFriendAddRequest($flag, $approve = true, $remark = "") { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -243,6 +406,15 @@ class ZMRobot ], $this->callback); } + /** + * 处理加群请求/邀请 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7 + * @param $flag + * @param $sub_type + * @param bool $approve + * @param string $reason + * @return array|bool|null + */ public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -255,10 +427,22 @@ class ZMRobot ], $this->callback); } + /** + * 获取登录号信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF + * @return array|bool|null + */ public function getLoginInfo() { return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); } + /** + * 获取陌生人信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF + * @param $user_id + * @param bool $no_cache + * @return array|bool|null + */ public function getStrangerInfo($user_id, $no_cache = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -269,18 +453,24 @@ class ZMRobot ], $this->callback); } + /** + * 获取好友列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8 + * @return array|bool|null + */ public function getFriendList() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } - public function getGroupList() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - + /** + * 获取群信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF + * @param $group_id + * @param bool $no_cache + * @return array|bool|null + */ public function getGroupInfo($group_id, $no_cache = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -291,6 +481,25 @@ class ZMRobot ], $this->callback); } + /** + * 获取群列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8 + * @return array|bool|null + */ + public function getGroupList() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取群成员信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF + * @param $group_id + * @param $user_id + * @param bool $no_cache + * @return array|bool|null + */ public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -302,30 +511,55 @@ class ZMRobot ], $this->callback); } + /** + * 获取群成员列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8 + * @param $group_id + * @return array|bool|null + */ public function getGroupMemberList($group_id) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'group_id' => $group_id ] - ]); + ], $this->callback); } - public function getCookies($domain = "") { + /** + * 获取群荣誉信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF + * @param $group_id + * @param $type + * @return array|bool|null + */ + public function getGroupHonorInfo($group_id, $type) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ - 'domain' => $domain + 'group_id' => $group_id, + 'type' => $type ] - ]); + ], $this->callback); } + /** + * 获取 CSRF Token + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token + * @return array|bool|null + */ public function getCsrfToken() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } + /** + * 获取 QQ 相关接口凭证 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81 + * @param string $domain + * @return array|bool|null + */ public function getCredentials($domain = "") { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -335,17 +569,29 @@ class ZMRobot ], $this->callback); } - public function getRecord($file, $out_format, $full_path = false) { + /** + * 获取语音 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3 + * @param $file + * @param $out_format + * @return array|bool|null + */ + public function getRecord($file, $out_format) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ 'file' => $file, - 'out_format' => $out_format, - 'full_path' => $full_path + 'out_format' => $out_format ] ], $this->callback); } + /** + * 获取图片 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87 + * @param $file + * @return array|bool|null + */ public function getImage($file) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), @@ -355,31 +601,57 @@ class ZMRobot ], $this->callback); } + /** + * 检查是否可以发送图片 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87 + * @return array|bool|null + */ public function canSendImage() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } + /** + * 检查是否可以发送语音 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3 + * @return array|bool|null + */ public function canSendRecord() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } + /** + * 获取运行状态 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81 + * @return array|bool|null + */ public function getStatus() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } + /** + * 获取版本信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF + * @return array|bool|null + */ public function getVersionInfo() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } - public function setRestartPlugin($delay = 0) { + /** + * 重启 OneBot 实现 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0 + * @param int $delay + * @return array|bool|null + */ + public function setRestart($delay = 0) { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__), 'params' => [ @@ -388,23 +660,22 @@ class ZMRobot ], $this->callback); } - public function cleanDataDir($data_dir) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'data_dir' => $data_dir - ] - ], $this->callback); - } - - public function cleanPluginLog() { + /** + * 清理缓存 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98 + * @return array|bool|null + */ + public function cleanCache() { return $this->processAPI($this->connection, [ 'action' => $this->getActionName(__FUNCTION__) ], $this->callback); } - public function getExperimentAPI() { - return new ZMRobotExperiment($this->connection, $this->callback, $this->prefix); + public function callExtendedAPI($action, $params = []) { + return $this->processAPI($this->connection, [ + 'action' => $action, + 'params' => $params + ], $this->callback); } private function getActionName(string $method) { diff --git a/src/ZM/API/ZMRobotExperiment.php b/src/ZM/API/ZMRobotExperiment.php deleted file mode 100644 index f7b6a565..00000000 --- a/src/ZM/API/ZMRobotExperiment.php +++ /dev/null @@ -1,102 +0,0 @@ -connection = $connection; - $this->callback = $callback; - $this->prefix = $prefix; - } - - public function setCallback($callback = true) { - $this->callback = $callback; - return $this; - } - - public function setPrefix($prefix = ZMRobot::API_NORMAL) { - $this->prefix = $prefix; - return $this; - } - - public function getFriendList($flat = false) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'flat' => $flat - ] - ], $this->callback); - } - - public function getGroupInfo($group_id) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id - ] - ], $this->callback); - } - - public function getVipInfo($user_id) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'user_id' => $user_id - ] - ], $this->callback); - } - - public function getGroupNotice($group_id) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id - ] - ], $this->callback); - } - - public function sendGroupNotice($group_id, $title, $content) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'title' => $title, - 'content' => $content - ] - ], $this->callback); - } - - public function setRestart($clean_log = false, $clean_cache = false, $clean_event = false) { - return $this->processAPI($this->connection, [ - 'action' => '_' . $this->getActionName(__FUNCTION__), - 'params' => [ - 'clean_log' => $clean_log, - 'clean_cache' => $clean_cache, - 'clean_event' => $clean_event - ] - ], $this->callback); - } - - private function getActionName(string $method) { - $prefix = ($this->prefix == ZMRobot::API_ASYNC ? '_async' : ($this->prefix == ZMRobot::API_RATE_LIMITED ? '_rate_limited' : '')); - $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method)); - return $prefix . $func_name; - } -} diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index af175e6f..e5f40070 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -4,48 +4,15 @@ namespace ZM\Annotation; use Doctrine\Common\Annotations\AnnotationReader; -use Co; -use ZM\ConnectionManager\ConnectionObject; +use ZM\Annotation\Interfaces\ErgodicAnnotation; use ZM\Console\Console; -use ZM\Store\ZMBuf; -use Error; -use Exception; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use ZM\Annotation\CQ\CQAfter; -use ZM\Annotation\CQ\CQAPIResponse; -use ZM\Annotation\CQ\CQAPISend; -use ZM\Annotation\CQ\CQBefore; -use ZM\Annotation\CQ\CQCommand; -use ZM\Annotation\CQ\CQMessage; -use ZM\Annotation\CQ\CQMetaEvent; -use ZM\Annotation\CQ\CQNotice; -use ZM\Annotation\CQ\CQRequest; -use ZM\Annotation\Http\After; -use ZM\Annotation\Http\Before; -use ZM\Annotation\Http\Controller; -use ZM\Annotation\Http\HandleException; -use ZM\Annotation\Http\Middleware; -use ZM\Annotation\Http\MiddlewareClass; -use ZM\Annotation\Http\RequestMapping; -use Swoole\Timer; -use ZM\Annotation\Interfaces\CustomAnnotation; +use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Module\Closed; -use ZM\Annotation\Module\InitBuffer; -use ZM\Annotation\Module\LoadBuffer; -use ZM\Annotation\Module\SaveBuffer; -use ZM\Annotation\Swoole\OnSave; -use ZM\Annotation\Swoole\OnStart; -use ZM\Annotation\Swoole\OnTick; -use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Annotation\Swoole\SwooleEventAt; -use ZM\Annotation\Interfaces\Rule; -use ZM\Event\EventHandler; -use ZM\Http\MiddlewareInterface; use ZM\Utils\DataProvider; -use ZM\Utils\ZMUtil; class AnnotationParser { @@ -54,12 +21,20 @@ class AnnotationParser private $start_time; private $annotation_map = []; + private $middleware_map = []; + private $middlewares = []; + /** @var null|AnnotationReader */ + private $reader = null; + private $req_mapping = []; + + /** + * AnnotationParser constructor. + */ public function __construct() { $this->start_time = microtime(true); $this->loadAnnotationClasses(); - ZMBuf::$req_mapping = []; - ZMBuf::$req_mapping[0] = [ + $this->req_mapping[0] = [ 'id' => 0, 'pid' => -1, 'name' => '/' @@ -71,15 +46,17 @@ class AnnotationParser * @throws ReflectionException */ public function registerMods() { - foreach($this->path_list as $path) { + foreach ($this->path_list as $path) { + Console::debug("parsing annotation in ".$path[0]); $all_class = getAllClasses($path[0], $path[1]); - $reader = new AnnotationReader(); + $this->reader = new AnnotationReader(); foreach ($all_class as $v) { Console::debug("正在检索 " . $v); $reflection_class = new ReflectionClass($v); $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); - $class_annotations = $reader->getClassAnnotations($reflection_class); + $class_annotations = $this->reader->getClassAnnotations($reflection_class); + // 这段为新加的:start //这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析 /* $annotation_map: { @@ -97,222 +74,116 @@ class AnnotationParser } } */ + + // 生成主树 $this->annotation_map[$v]["class_annotations"] = $class_annotations; $this->annotation_map[$v]["methods"] = $methods; foreach ($methods as $method) { - $this->annotation_map[$v]["methods_annotations"][$method->getName()] = $reader->getMethodAnnotations($method); + $this->annotation_map[$v]["methods_annotations"][$method->getName()] = $this->reader->getMethodAnnotations($method); } - /* - $middleware_addon = []; - foreach ($class_annotations as $vs) { + + foreach ($this->annotation_map[$v]["class_annotations"] as $ks => $vs) { + $vs->class = $v; + + //预处理1:将适用于每一个函数的注解到类注解重新注解到每个函数下面 + if ($vs instanceof ErgodicAnnotation) { + foreach ($this->annotation_map[$v]["methods"] as $method) { + $copy = clone $vs; + $copy->method = $method->getName(); + $this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy; + } + } + + //预处理2:处理 class 下面的注解 if ($vs instanceof Closed) { + unset($this->annotation_map[$v]); continue 2; - } elseif ($vs instanceof Controller) { - Console::debug("找到 Controller 中间件: " . $vs->class); - $class_prefix = $vs->prefix; - } elseif ($vs instanceof SaveBuffer) { - Console::debug("注册自动保存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); - DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder); - } elseif ($vs instanceof LoadBuffer) { - Console::debug("注册到内存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")"); - ZMBuf::set($vs->buf_name, DataProvider::getJsonData(($vs->sub_folder ?? "") . "/" . $vs->buf_name . ".json")); - } elseif ($vs instanceof InitBuffer) { - ZMBuf::set($vs->buf_name, []); } elseif ($vs instanceof MiddlewareClass) { Console::verbose("正在注册中间件 " . $reflection_class->getName()); - $result = [ - "class" => "\\" . $reflection_class->getName() - ]; - foreach ($methods as $vss) { - if ($vss->getName() == "getName") { - /** @var MiddlewareInterface $tmp * - $tmp = new $v(); - $result["name"] = $tmp->getName(); - continue; - } - $method_annotations = $reader->getMethodAnnotations($vss); - foreach ($method_annotations as $vsss) { - if ($vss instanceof Rule) $vss = self::registerRuleEvent($vsss, $vss, $reflection_class); - else $vss = self::registerMethod($vsss, $vss, $reflection_class); - //echo get_class($vsss) . PHP_EOL; - if ($vsss instanceof Before) $result["before"] = $vsss->method; - if ($vsss instanceof After) $result["after"] = $vsss->method; - if ($vsss instanceof HandleException) { - $result["exceptions"][$vsss->class_name] = $vsss->method; - } - } - } - ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result; - continue 2; - } elseif ($vs instanceof Middleware) { - $middleware_addon[] = $vs; - } elseif ($vs instanceof CustomAnnotation) { - $vs->class = $reflection_class->getName(); - ZMBuf::$events[get_class($vs)][] = $vs; + $rs = $this->registerMiddleware($vs, $reflection_class); + $this->middlewares[$rs["name"]] = $rs; } } - foreach ($methods as $vs) { - if ($middleware_addon !== []) { - foreach ($middleware_addon as $value) { - Console::debug("Added middleware " . $value->middleware . " to $v -> " . $vs->getName()); - ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $value->middleware; + + //预处理3:处理每个函数上面的特殊注解,就是需要操作一些东西的 + foreach ($this->annotation_map[$v]["methods_annotations"] as $method_name => $methods_annotations) { + foreach ($methods_annotations as $method_anno) { + /** @var AnnotationBase $method_anno */ + $method_anno->class = $v; + $method_anno->method = $method_name; + if ($method_anno instanceof RequestMapping) { + $this->registerRequestMapping($method_anno, $method_name, $v, $methods_annotations); //TODO: 用symfony的routing重写 + } elseif ($method_anno instanceof Middleware) { + $this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno->middleware; } } - $method_annotations = $reader->getMethodAnnotations($vs); - foreach ($method_annotations as $vss) { - if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class); - else $vss = self::registerMethod($vss, $vs, $reflection_class); - Console::debug("寻找 " . $vs->getName() . " -> " . get_class($vss)); - - if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss; - elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss; - elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss; - elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss; - elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss; - elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss; - elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss; - elseif ($vss instanceof RequestMapping) { - self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix); - } elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss; - elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss; - elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss; - elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss; - elseif ($vss instanceof OnSave) ZMBuf::$events[OnSave::class][] = $vss; - elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method][] = $vss->middleware; - elseif ($vss instanceof OnTick) self::addTimerTick($vss); - elseif ($vss instanceof CQAPISend) ZMBuf::$events[CQAPISend::class][] = $vss; - elseif ($vss instanceof CQAPIResponse) ZMBuf::$events[CQAPIResponse::class][$vss->retcode] = [$vss->class, $vss->method]; - } - }*/ + } } } - - - $tree = self::genTree(ZMBuf::$req_mapping); - ZMBuf::$req_mapping = $tree[0]; - //给支持level的排个序 + //预处理4:生成路由树(换成symfony后就不需要了) + $tree = $this->genTree($this->req_mapping); + $this->req_mapping = $tree[0]; Console::debug("解析注解完毕!"); - if (ZMBuf::isset("timer_count")) { - Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!"); - ZMBuf::unsetCache("timer_count"); - } } - public function sortLevels() { - foreach (ZMBuf::$events as $class_name => $v) { - if (is_a($class_name, Level::class, true)) { - for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) { - for ($j = 0; $j < count(ZMBuf::$events[$class_name]) - $i - 1; ++$j) { - $l1 = ZMBuf::$events[$class_name][$j]->level; - $l2 = ZMBuf::$events[$class_name][$j + 1]->level; - if ($l1 < $l2) { - $t = ZMBuf::$events[$class_name][$j + 1]; - ZMBuf::$events[$class_name][$j + 1] = ZMBuf::$events[$class_name][$j]; - ZMBuf::$events[$class_name][$j] = $t; - } - } + /** + * @return array + */ + public function generateAnnotationEvents() { + $o = []; + foreach ($this->annotation_map as $module => $obj) { + foreach ($obj["class_annotations"] as $class_annotation) { + if ($class_annotation instanceof ErgodicAnnotation) continue; + else $o[get_class($class_annotation)][] = $class_annotation; + } + foreach ($obj["methods_annotations"] as $method_name => $methods_annotations) { + foreach ($methods_annotations as $annotation) { + $o[get_class($annotation)][] = $annotation; } } + } + foreach ($o as $k => $v) { + $this->sortByLevel($o, $k); + } + return $o; } - public static function getRuleCallback($rule_str) { - $func = null; - $rule = $rule_str; - if ($rule != "") { - $asp = explode(":", $rule); - $asp_name = array_shift($asp); - $rest = implode(":", $asp); - //Swoole 事件时走此switch - switch ($asp_name) { - case "connectType": //websocket连接类型 - $func = function (?ConnectionObject $connection) use ($rest) { - if ($connection === null) return false; - return $connection->getName() == $rest ? true : false; - }; - break; - case "containsGet": //handle http request事件时才能用 - case "containsPost": - $get_list = explode(",", $rest); - if ($asp_name == "containsGet") - $func = function ($request) use ($get_list) { - foreach ($get_list as $v) if (!isset($request->get[$v])) return false; - return true; - }; - else - $func = function ($request) use ($get_list) { - foreach ($get_list as $v) if (!isset($request->post[$v])) return false; - return true; - }; - /* - if ($controller_prefix != '') { - $p = ZMBuf::$req_mapping_node; - $prefix_exp = explode("/", $controller_prefix); - foreach ($prefix_exp as $k => $v) { - if ($v == "" || $v == ".." || $v == ".") { - unset($prefix_exp[$k]); - } - } - while (($shift = array_shift($prefix_exp)) !== null) { - $p->addRoute($shift, new MappingNode($shift)); - $p = $p->getRoute($shift); - } - if ($p->getNodeName() != "/") { - $p->setMethod($method->getName()); - $p->setClass($class->getName()); - $p->setRule($func); - return "mapped"; - } - }*/ - break; - case "containsJson": //handle http request事件时才能用 - $json_list = explode(",", $rest); - $func = function ($json) use ($json_list) { - foreach ($json_list as $v) if (!isset($json[$v])) return false; - return true; - }; - break; - case "dataEqual": //handle websocket message事件时才能用 - $func = function ($data) use ($rest) { - return $data == $rest; - }; - break; - } - switch ($asp_name) { - case "msgMatch": //handle cq message事件时才能用 - $func = function ($msg) use ($rest) { - return matchPattern($rest, $msg); - }; - break; - case "msgEqual": //handle cq message事件时才能用 - $func = function ($msg) use ($rest) { - return trim($msg) == $rest; - }; - break; + /** + * @return array + */ + public function getMiddlewares() { return $this->middlewares; } + /** + * @return array + */ + public function getMiddlewareMap() { return $this->middleware_map; } + + /** + * @return array + */ + public function getReqMapping() { return $this->req_mapping; } + + /** + * @param $path + * @param $indoor_name + */ + public function addRegisterPath($path, $indoor_name) { $this->path_list[] = [$path, $indoor_name]; } + + //private function below + + private function registerRequestMapping(RequestMapping $vss, $method, $class, $methods_annotations) { + $prefix = ''; + foreach ($methods_annotations as $annotation) { + if ($annotation instanceof Controller) { + $prefix = $annotation->prefix; + break; } } - return $func; - } - - public static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) { - $vss->callback = self::getRuleCallback($vss->getRule()); - $vss->method = $method->getName(); - $vss->class = $class->getName(); - return $vss; - } - - public static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) { - $vss->method = $method->getName(); - $vss->class = $class->getName(); - return $vss; - } - - private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) { - $array = ZMBuf::$req_mapping; + $array = $this->req_mapping; $uid = count($array); $prefix_exp = explode("/", $prefix); $route_exp = explode("/", $vss->route); @@ -327,10 +198,10 @@ class AnnotationParser } } if ($prefix_exp == [] && $route_exp == []) { - $array[0]['method'] = $method->getName(); - $array[0]['class'] = $class->getName(); + $array[0]['method'] = $method; + $array[0]['class'] = $class; $array[0]['request_method'] = $vss->request_method; - ZMBuf::$req_mapping = $array; + $this->req_mapping = $array; return; } $pid = 0; @@ -373,16 +244,19 @@ class AnnotationParser ]; $pid = $uid - 1; } - $array[$uid - 1]['method'] = $method->getName(); - $array[$uid - 1]['class'] = $class->getName(); + $array[$uid - 1]['method'] = $method; + $array[$uid - 1]['class'] = $class; $array[$uid - 1]['request_method'] = $vss->request_method; - ZMBuf::$req_mapping = $array; + $array[$uid - 1]['route'] = $vss->route; + $this->req_mapping = $array; } + /** @noinspection PhpIncludeInspection */ private function loadAnnotationClasses() { $class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation"); foreach ($class as $v) { $s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php"; + //Console::debug("Requiring annotation " . $s); require_once $s; } $class = getAllClasses(DataProvider::getWorkingDir() . "/src/Custom/Annotation/", "Custom\\Annotation"); @@ -393,7 +267,7 @@ class AnnotationParser } } - public static function genTree($items) { + private function genTree($items) { $tree = array(); foreach ($items as $item) if (isset($items[$item['pid']])) @@ -403,48 +277,33 @@ class AnnotationParser return $tree; } - private static function addTimerTick(?OnTick $vss) { - ZMBuf::set("timer_count", ZMBuf::get("timer_count", 0) + 1); - $class = ZMUtil::getModInstance($vss->class); - $method = $vss->method; - $ms = $vss->tick_ms; - $cid = go(function () use ($class, $method, $ms) { - Co::suspend(); - $plain_class = get_class($class); - if (!isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) { - Console::debug("Added timer: " . $plain_class . " -> " . $method); - Timer::tick($ms, function () use ($class, $method) { - set_coroutine_params([]); - try { - $class->$method(); - } catch (Exception $e) { - Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); - } catch (Error $e) { - Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage()); - echo Console::setColor($e->getTraceAsString(), "gray"); - Console::error("Please check your code!"); - } - }); - } else { - Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $method); - Timer::tick($ms, function () use ($class, $method) { - set_coroutine_params([]); - try { - EventHandler::callWithMiddleware($class, $method, [], []); - } catch (Exception $e) { - Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); - } catch (Error $e) { - Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage()); - echo Console::setColor($e->getTraceAsString(), "gray"); - Console::error("Please check your code!"); - } - }); + private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class) { + $result = [ + "class" => "\\" . $reflection_class->getName(), + "name" => $vs->name + ]; + + foreach ($reflection_class->getMethods() as $vss) { + $method_annotations = $this->reader->getMethodAnnotations($vss); + foreach ($method_annotations as $vsss) { + if ($vsss instanceof Before) $result["before"] = $vss->getName(); + if ($vsss instanceof After) $result["after"] = $vss->getName(); + if ($vsss instanceof HandleException) { + $result["exceptions"][$vsss->class_name] = $vss->getName(); + } } - }); - ZMBuf::append("paused_tick", $cid); + } + return $result; } - public function addRegisterPath($path, $indoor_name) { - $this->path_list[] = [$path, $indoor_name]; + private function sortByLevel(&$events, string $class_name, $prefix = "") { + if (is_a($class_name, Level::class, true)) { + $class_name .= $prefix; + usort($events[$class_name], function ($a, $b) { + $left = $a->level; + $right = $b->level; + return $left > $right ? -1 : ($left == $right ? 0 : 1); + }); + } } } diff --git a/src/ZM/Annotation/Http/MiddlewareClass.php b/src/ZM/Annotation/Http/MiddlewareClass.php index 4d182aae..58f98d1d 100644 --- a/src/ZM/Annotation/Http/MiddlewareClass.php +++ b/src/ZM/Annotation/Http/MiddlewareClass.php @@ -4,6 +4,7 @@ namespace ZM\Annotation\Http; +use Doctrine\Common\Annotations\Annotation\Required; use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; @@ -15,5 +16,9 @@ use ZM\Annotation\AnnotationBase; */ class MiddlewareClass extends AnnotationBase { - -} \ No newline at end of file + /** + * @var string + * @Required() + */ + public $name = ''; +} diff --git a/src/ZM/Annotation/Module/InitBuffer.php b/src/ZM/Annotation/Module/InitBuffer.php deleted file mode 100644 index a541004e..00000000 --- a/src/ZM/Annotation/Module/InitBuffer.php +++ /dev/null @@ -1,19 +0,0 @@ -level = $level; } -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Swoole/SwooleSetup.php b/src/ZM/Annotation/Swoole/SwooleSetup.php new file mode 100644 index 00000000..d0037379 --- /dev/null +++ b/src/ZM/Annotation/Swoole/SwooleSetup.php @@ -0,0 +1,18 @@ +setDescription("Run a simple http server | 启动一个简单的文件 HTTP 服务器"); + $this->setHelp("直接运行可以启动"); + $this->addArgument('dir', InputArgument::OPTIONAL, 'Your directory'); + $this->addOption("host", 'H', InputOption::VALUE_REQUIRED, "启动监听地址"); + $this->addOption("port", 'P', InputOption::VALUE_REQUIRED, "启动监听地址的端口"); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output) { + $tty_width = explode(" ", trim(exec("stty size")))[1]; + $global = ZMConfig::get("global"); + $host = $input->getOption("host") ?? $global["host"]; + $port = $input->getOption("port") ?? $global["port"]; + $server = new Server($host, $port); + Console::init(2, $server); + $index = ["index.html", "index.htm"]; + $server->on("request", function (Request $request, Response $response) use ($input, $index){ + HttpUtil::handleStaticPage( + $request->server["request_uri"], + $response, + [ + "document_root" => realpath($input->getArgument('dir') ?? '.'), + "document_index" => $index + ]); + }); + $server->on("start", function($server){ + Process::signal(SIGINT, function () use ($server){ + Console::warning("Server interrupted by keyboard."); + $server->shutdown(); + $server->stop(); + }); + Console::success("Server started. Use Ctrl+C to stop."); + }); + $out = [ + "host" => $host, + "port" => $port, + "document_root" => realpath($input->getArgument('dir') ?? '.'), + "document_index" => implode(", ", $index) + ]; + Console::printProps($out, $tty_width); + $server->start(); + // return this if there was no problem running the command + // (it's equivalent to returning int(0)) + return Command::SUCCESS; + + // or return this if some error happened during the execution + // (it's equivalent to returning int(1)) + // return Command::FAILURE; + } +} diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 2dac62fe..9ffd6614 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -3,7 +3,6 @@ namespace ZM\Command; -use Framework\FrameworkLoader; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; @@ -26,6 +25,8 @@ class RunServerCommand extends Command $this->addOption("log-error", null, null, "调整消息等级到error (log-level=0)"); $this->addOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"); $this->addOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"); + $this->addOption("disable-coroutine", null, null, "关闭协程Hook"); + $this->addOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"); $this->addOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"); // ... } @@ -34,6 +35,7 @@ class RunServerCommand extends Command if(($opt = $input->getOption("env")) !== null) { if(!in_array($opt, ["production", "staging", "development"])) { $output->writeln(" \"--env\" option only accept production, development and staging ! "); + return Command::FAILURE; } } // ... put here the code to run in your command diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 9c81b63d..9614b11f 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -5,12 +5,17 @@ namespace ZM; use Exception; +use Symfony\Component\Console\Command\Command; +use TypeError; use ZM\Command\BuildCommand; use ZM\Command\InitCommand; +use ZM\Command\PureHttpCommand; use ZM\Command\RunServerCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use ZM\Config\ZMConfig; +use ZM\Utils\DataProvider; class ConsoleApplication extends Application { @@ -22,9 +27,42 @@ class ConsoleApplication extends Application public function initEnv() { $this->addCommands([ new RunServerCommand(), //运行主服务的指令控制器 - new InitCommand() //初始化用的,用于项目初始化和phar初始化 + new InitCommand(), //初始化用的,用于项目初始化和phar初始化 + new PureHttpCommand() ]); - if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 + //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 + + if (LOAD_MODE === 0) define("WORKING_DIR", getcwd()); + elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); + elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL; + if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) { + /** @noinspection PhpIncludeInspection */ + require_once DataProvider::getWorkingDir() . "/vendor/autoload.php"; + } + if (LOAD_MODE == 2) { + // Phar 模式,2.0 不提供哦 + //require_once FRAMEWORK_DIR . "/vendor/autoload.php"; + spl_autoload_register('phar_classloader'); + } elseif (LOAD_MODE == 0) { + /** @noinspection PhpIncludeInspection + * @noinspection RedundantSuppression + */ + require_once WORKING_DIR . "/vendor/autoload.php"; + } + + if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { + die("Unable to find source directory.\nMaybe you need to run \"init\"?"); + } + ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config'); + ZMConfig::env($args["env"] ?? ""); + if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error); + + $command_register = ZMConfig::get("global", "command_register_class") ?? []; + foreach($command_register as $v) { + $obj = new $v(); + if(!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); + $this->add($obj); + } } /** diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 503b7526..850a4e43 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -18,6 +18,7 @@ use ZM\Store\ZMBuf; class Context implements ContextInterface { + public static $context = []; private $cid; public function __construct($cid) { $this->cid = $cid; } @@ -25,31 +26,31 @@ class Context implements ContextInterface /** * @return swoole_server|null */ - public function getServer() { return ZMBuf::$context[$this->cid]["server"] ?? null; } + public function getServer() { return self::$context[$this->cid]["server"] ?? null; } /** * @return Frame|null */ - public function getFrame() { return ZMBuf::$context[$this->cid]["frame"] ?? null; } + public function getFrame() { return self::$context[$this->cid]["frame"] ?? null; } - public function getFd() { return ZMBuf::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; } + public function getFd() { return self::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; } /** * @return array|null */ - public function getData() { return ZMBuf::$context[$this->cid]["data"] ?? null; } + public function getData() { return self::$context[$this->cid]["data"] ?? null; } - public function setData($data) { ZMBuf::$context[$this->cid]["data"] = $data; } + public function setData($data) { self::$context[$this->cid]["data"] = $data; } /** * @return Request|null */ - public function getRequest() { return ZMBuf::$context[$this->cid]["request"] ?? null; } + public function getRequest() { return self::$context[$this->cid]["request"] ?? null; } /** * @return Response|null */ - public function getResponse() { return ZMBuf::$context[$this->cid]["response"] ?? null; } + public function getResponse() { return self::$context[$this->cid]["response"] ?? null; } /** @return ConnectionObject|null */ public function getConnection() { return ManagerGM::get($this->getFd()); } @@ -67,33 +68,33 @@ class Context implements ContextInterface return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null; } - public function getMessage() { return ZMBuf::$context[$this->cid]["data"]["message"] ?? null; } + public function getMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; } - public function setMessage($msg) { ZMBuf::$context[$this->cid]["data"]["message"] = $msg; } + public function setMessage($msg) { self::$context[$this->cid]["data"]["message"] = $msg; } public function getUserId() { return $this->getData()["user_id"] ?? null; } - public function setUserId($id) { ZMBuf::$context[$this->cid]["data"]["user_id"] = $id; } + public function setUserId($id) { self::$context[$this->cid]["data"]["user_id"] = $id; } public function getGroupId() { return $this->getData()["group_id"] ?? null; } - public function setGroupId($id) { ZMBuf::$context[$this->cid]["data"]["group_id"] = $id; } + public function setGroupId($id) { self::$context[$this->cid]["data"]["group_id"] = $id; } public function getDiscussId() { return $this->getData()["discuss_id"] ?? null; } - public function setDiscussId($id) { ZMBuf::$context[$this->cid]["data"]["discuss_id"] = $id; } + public function setDiscussId($id) { self::$context[$this->cid]["data"]["discuss_id"] = $id; } public function getMessageType() { return $this->getData()["message_type"] ?? null; } - public function setMessageType($type) { ZMBuf::$context[$this->cid]["data"]["message_type"] = $type; } + public function setMessageType($type) { self::$context[$this->cid]["data"]["message_type"] = $type; } public function getRobotId() { return $this->getData()["self_id"] ?? null; } - public function getCache($key) { return ZMBuf::$context[$this->cid]["cache"][$key] ?? null; } + public function getCache($key) { return self::$context[$this->cid]["cache"][$key] ?? null; } - public function setCache($key, $value) { ZMBuf::$context[$this->cid]["cache"][$key] = $value; } + public function setCache($key, $value) { self::$context[$this->cid]["cache"][$key] = $value; } - public function getCQResponse() { return ZMBuf::$context[$this->cid]["cq_response"] ?? null; } + public function getCQResponse() { return self::$context[$this->cid]["cq_response"] ?? null; } /** * only can used by cq->message event function @@ -114,8 +115,6 @@ class Context implements ContextInterface return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg); case "private": return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg); - case "discuss": - return (new ZMRobot($conn))->setCallback($yield)->sendDiscussMsg($data["discuss_id"], $msg); } return null; } @@ -123,7 +122,7 @@ class Context implements ContextInterface } public function finalReply($msg, $yield = false) { - ZMBuf::$context[$this->cid]["cache"]["block_continue"] = true; + self::$context[$this->cid]["cache"]["block_continue"] = true; if ($msg == "") return true; return $this->reply($msg, $yield); } @@ -141,8 +140,8 @@ class Context implements ContextInterface if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"])) throw new InvalidArgumentException("协程等待参数缺失"); $cid = Co::getuid(); - $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); - ZMBuf::$atomics["wait_msg_id"]->add(1); + $api_id = ZMBuf::atomic("wait_msg_id")->get(); + ZMBuf::atomic("wait_msg_id")->add(1); $hang = [ "coroutine" => $cid, "user_id" => $this->getData()["user_id"], @@ -206,9 +205,9 @@ class Context implements ContextInterface } public function cloneFromParent() { - set_coroutine_params(ZMBuf::$context[Co::getPcid()] ?? ZMBuf::$context[$this->cid]); + set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); return context(); } - public function copy() { return ZMBuf::$context[$this->cid]; } + public function copy() { return self::$context[$this->cid]; } } diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php index ca5d2eb1..734671a5 100644 --- a/src/ZM/Event/CQ/MessageEvent.php +++ b/src/ZM/Event/CQ/MessageEvent.php @@ -8,6 +8,8 @@ use Co; use Doctrine\Common\Annotations\AnnotationException; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; +use ZM\Event\EventDispatcher; +use ZM\Event\EventManager; use ZM\Store\ZMBuf; use ZM\Annotation\CQ\CQAfter; use ZM\Annotation\CQ\CQBefore; @@ -36,21 +38,23 @@ class MessageEvent * @throws AnnotationException */ public function onBefore() { - $obj_list = ZMBuf::$events[CQBefore::class]["message"] ?? []; - foreach ($obj_list as $v) { - if ($v->level < 200) break; - EventHandler::callWithMiddleware( - $v->class, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if (!$r) context()->setCache("block_continue", true); - } - ); - if (context()->getCache("block_continue") === true) return false; - } + $dispatcher = new EventDispatcher(CQBefore::class . "::message"); + $dispatcher->setRuleFunction(function ($v) { + if($v->level < 200) EventDispatcher::interrupt(); + return true; + }); + $dispatcher->setReturnFunction(function($result){ + if(!$result) EventDispatcher::interrupt(); + }); + $dispatcher->dispatchEvents(); + foreach (ZMBuf::get("wait_api", []) as $k => $v) { + if(zm_data_hash(ctx()->getData()) == $v["hash"]) { + $v["result"] = context()->getData()["message"]; + ZMBuf::appendKey("wait_api", $k, $v); + Co::resume($v["coroutine"]); + return false; + } if (context()->getData()["user_id"] == $v["user_id"] && context()->getData()["self_id"] == $v["self_id"] && context()->getData()["message_type"] == $v["message_type"] && @@ -62,7 +66,7 @@ class MessageEvent return false; } } - foreach (ZMBuf::$events[CQBefore::class]["message"] ?? [] as $v) { + foreach (EventManager::$events[CQBefore::class]["message"] ?? [] as $v) { if ($v->level >= 200) continue; $c = $v->class; if (ctx()->getCache("level") != 0) continue; @@ -95,70 +99,44 @@ class MessageEvent $word[$k] = trim($word[$k]); } } - $obj = []; - foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) { - /** @var CQCommand $v */ - if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") continue; - elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) && - ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) && - ($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) && - ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) - ) { - $c = $v->class; - if (!isset($obj[$c])) { - $obj[$c] = new $c(); - } - if ($word[0] != "" && $v->match == $word[0]) { - Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$word], function ($r) { - if (is_string($r)) context()->reply($r); - return true; - }); - return; - } elseif (in_array($word[0], $v->alias)) { - Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$word], function ($r) { - if (is_string($r)) context()->reply($r); - return true; - }); - return; - } elseif ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, context()->getMessage())) !== false) { - Console::debug("Calling $c -> {$v->method}"); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$args], function ($r) { - if (is_string($r)) context()->reply($r); - return true; - }); - return; - } elseif ($v->fullMatch != "" && (preg_match("/".$v->fullMatch."/u", ctx()->getMessage(), $args)) != 0) { - Console::debug("Calling $c -> {$v->method}"); - array_shift($args); - $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, [], [$args], function ($r) { - if (is_string($r)) context()->reply($r); - return true; - }); - return; + + //分发CQCommand事件 + $dispatcher = new EventDispatcher(CQCommand::class); + $dispatcher->setRuleFunction(function ($v) use ($word) { + if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") return false; + elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) && + ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) && + ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType())) + ) { + if (($word[0] != "" && $v->match == $word[0]) || + in_array($word[0], $v->alias) || + ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, ctx()->getMessage())) !== false) || + ($v->fullMatch != "" && (preg_match("/" . $v->fullMatch . "/u", ctx()->getMessage(), $args)) != 0)) { + return true; } } - } - foreach (ZMBuf::$events[CQMessage::class] ?? [] as $v) { - /** @var CQMessage $v */ - if ( - ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) && + return false; + }); + $dispatcher->setReturnFunction(function ($result) { + if (is_string($result)) ctx()->reply($result); + EventDispatcher::interrupt(); + }); + $r = $dispatcher->dispatchEvents($word); + if ($r === false) return; + + //分发CQMessage事件 + $msg_dispatcher = new EventDispatcher(CQMessage::class); + $msg_dispatcher->setRuleFunction(function ($v) { + return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) && ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) && ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) && - ($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) && ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) && - ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]))) { - $c = $v->class; - Console::debug("Calling CQMessage: $c -> {$v->method}"); - if (!isset($obj[$c])) - $obj[$c] = new $c(); - EventHandler::callWithMiddleware($obj[$c], $v->method, [], [context()->getData()["message"]], function ($r) { - if (is_string($r)) context()->reply($r); - }); - if (context()->getCache("block_continue") === true) return; - } - } + ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"])); + }); + $msg_dispatcher->setReturnFunction(function ($result) { + if (is_string($result)) ctx()->reply($result); + }); + $msg_dispatcher->dispatchEvents(ctx()->getMessage()); } catch (WaitTimeoutException $e) { $e->module->finalReply($e->getMessage()); } diff --git a/src/ZM/Event/CQ/MetaEvent.php b/src/ZM/Event/CQ/MetaEvent.php index 52ef7507..78a7671e 100644 --- a/src/ZM/Event/CQ/MetaEvent.php +++ b/src/ZM/Event/CQ/MetaEvent.php @@ -64,7 +64,7 @@ class MetaEvent if (context()->getCache("block_continue") === true) return; } } - } catch (WaitTimeoutException $e) { + } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { $e->module->finalReply($e->getMessage()); } } diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php new file mode 100644 index 00000000..3ad6121f --- /dev/null +++ b/src/ZM/Event/EventDispatcher.php @@ -0,0 +1,109 @@ +class = $class; + } + + public function setRuleFunction(callable $rule = null) { + $this->rule = $rule; + return $this; + } + + public function setReturnFunction(callable $return_func) { + $this->return_func = $return_func; + return $this; + } + + public function dispatchEvents(...$params) { + try { + foreach (EventManager::$events[$this->class] ?? [] as $v) { + $result = $this->dispatchEvent($v, $this->rule, ...$params); + if (is_callable($this->return_func)) ($this->return_func)($result); + } + return true; + } catch (InterruptException $e) { + return false; + } catch (AnnotationException $e) { + return false; + } + } + + public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { + $q_c = $v->class; + $q_f = $v->method; + if ($rule_func !== null && !$rule_func($v)) return false; + if (isset(EventManager::$middleware_map[$q_c][$q_f])) { + $middlewares = EventManager::$middleware_map[$q_c][$q_f]; + $before_result = true; + $r = []; + foreach ($middlewares as $k => $middleware) { + if (!isset(EventManager::$middlewares[$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!"); + $middleware_obj = EventManager::$middlewares[$middleware]; + $before = $middleware_obj["class"]; + //var_dump($middleware_obj); + $r[$k] = new $before(); + $r[$k]->class = $q_c; + $r[$k]->method = $q_f; + if (isset($middleware_obj["before"])) { + $rs = $middleware_obj["before"]; + $before_result = $r[$k]->$rs(...$params); + if ($before_result === false) break; + } + } + if ($before_result) { + try { + $q_o = ZMUtil::getModInstance($q_c); + $result = $q_o->$q_f(...$params); + } catch (Exception $e) { + if ($e instanceof InterruptException) throw $e; + for ($i = count($middlewares) - 1; $i >= 0; --$i) { + $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; + if (!isset($middleware_obj["exceptions"])) continue; + foreach ($middleware_obj["exceptions"] as $name => $method) { + if ($e instanceof $name) { + $r[$i]->$method($e); + self::interrupt(); + } + } + } + throw $e; + } + for ($i = count($middlewares) - 1; $i >= 0; --$i) { + $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; + if (isset($middleware_obj["after"], $r[$i])) { + $r[$i]->{$middleware_obj["after"]}(...$params); + } + } + return $result; + } + return false; + } else { + $q_o = ZMUtil::getModInstance($q_c); + return $q_o->$q_f(...$params); + } + } +} diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php index 84631539..c3cbfcac 100644 --- a/src/ZM/Event/EventHandler.php +++ b/src/ZM/Event/EventHandler.php @@ -8,20 +8,21 @@ use Co; use Doctrine\Common\Annotations\AnnotationException; use Error; use Exception; +use ZM\Config\ZMConfig; use ZM\ConnectionManager\ConnectionObject; use ZM\ConnectionManager\ManagerGM; use ZM\Console\Console; -use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent}; +use ZM\Event\Swoole\{MessageEvent, RequestEvent, WSCloseEvent, WSOpenEvent}; use Swoole\Http\Request; use Swoole\Server; use Swoole\WebSocket\Frame; use ZM\Annotation\CQ\CQAPIResponse; use ZM\Annotation\CQ\CQAPISend; use ZM\Annotation\Http\MiddlewareClass; +use ZM\Context\Context; use ZM\Http\MiddlewareInterface; use ZM\Http\Response; use ZM\Store\ZMBuf; -use ZM\Utils\DataProvider; use ZM\Utils\ZMUtil; class EventHandler @@ -34,38 +35,9 @@ class EventHandler */ public static function callSwooleEvent($event_name, $param0, $param1 = null) { //$starttime = microtime(true); - unset(ZMBuf::$context[Co::getCid()]); + unset(Context::$context[Co::getCid()]); $event_name = strtolower($event_name); switch ($event_name) { - case "workerstart": - try { - register_shutdown_function(function () use ($param0) { - $error = error_get_last(); - if ($error["type"] != 0) { - Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); - } - DataProvider::saveBuffer(); - /** @var Server $param0 */ - if (ZMBuf::$server === null) $param0->shutdown(); - else ZMBuf::$server->shutdown(); - }); - ZMBuf::$server = $param0; - $r = (new WorkerStartEvent($param0, $param1))->onActivate(); - Console::success("Worker #" . $param0->worker_id . " 已启动"); - $r->onAfter(); - self::startTick(); - } catch (Exception $e) { - Console::error("Worker加载出错!停止服务!"); - Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); - ZMUtil::stop(); - return; - } catch (Error $e) { - Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); - Console::error("Maybe it caused by your own code if in your own Module directory."); - Console::log($e->getTraceAsString(), 'gray'); - ZMUtil::stop(); - } - break; case "message": /** @var Frame $param1 */ /** @var Server $param0 */ @@ -90,7 +62,7 @@ class EventHandler " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"] ); if (!$param1->isEnd()) { - if (\ZM\Config\ZMConfig::get("global", "debug_mode")) + if (ZMConfig::get("global", "debug_mode")) $param1->end("Internal server error: " . $e->getMessage()); else $param1->end("Internal server error."); @@ -268,11 +240,13 @@ class EventHandler if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!"); $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middleware]; $before = $middleware_obj["class"]; + //var_dump($middleware_obj); $r[$k] = new $before(); $r[$k]->class = is_object($c) ? get_class($c) : $c; $r[$k]->method = $method; if (isset($middleware_obj["before"])) { - $before_result = call_user_func_array([$r[$k], $middleware_obj["before"]], $func_args); + $rs = $middleware_obj["before"]; + $before_result = $r[$k]->$rs(...$func_args); if ($before_result === false) break; } } @@ -281,9 +255,9 @@ class EventHandler if (is_object($c)) $class = $c; elseif ($class_construct == []) $class = ZMUtil::getModInstance($c); else $class = new $c($class_construct); - $result = call_user_func_array([$class, $method], $func_args); + $result = $class->$method(...$func_args); if (is_callable($after_call)) - $return_value = call_user_func_array($after_call, [$result]); + $return_value = $after_call($result); } catch (Exception $e) { for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]]; @@ -301,8 +275,9 @@ class EventHandler } for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]]; - if (isset($middleware_obj["after"], $r[$i])) - call_user_func_array([$r[$i], $middleware_obj["after"]], $func_args); + if (isset($middleware_obj["after"], $r[$i])) { + $r[$i]->{$middleware_obj["after"]}(...$func_args); + } } } else { if (is_object($c)) $class = $c; @@ -314,12 +289,4 @@ class EventHandler } return $return_value; } - - private static function startTick() { - Console::debug("Starting " . count(ZMBuf::get("paused_tick", [])) . " custom tick function"); - foreach (ZMBuf::get("paused_tick", []) as $cid) { - Co::resume($cid); - } - - } } diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php new file mode 100644 index 00000000..e6546abc --- /dev/null +++ b/src/ZM/Event/EventManager.php @@ -0,0 +1,62 @@ +generateAnnotationEvents(); + self::$middlewares = $parser->getMiddlewares(); + self::$middleware_map = $parser->getMiddlewareMap(); + self::$req_mapping = $parser->getReqMapping(); + } + + /** + * 注册所有计时器给每个进程 + */ + public static function registerTimerTick() { + $dispatcher = new EventDispatcher(OnTick::class); + foreach (self::$events[OnTick::class] ?? [] as $vss) { + if (server()->worker_id !== $vss->worker_id) return; + //echo server()->worker_id.PHP_EOL; + $plain_class = $vss->class; + Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method); + Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) { + set_coroutine_params([]); + if (ZMBuf::atomic("stop_signal")->get() != 0) { + Timer::clearAll(); + return; + } + try { + $dispatcher->dispatchEvent($vss, null); + } catch (Exception $e) { + Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); + } catch (Error $e) { + Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage()); + echo Console::setColor($e->getTraceAsString(), "gray"); + Console::error("Please check your code!"); + } + }); + } + } +} diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index e7f757de..931641be 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -1,80 +1,507 @@ -taskworker === false) { - EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id); - } else { - ob_start(); - //AnnotationParser::registerMods(); - ob_get_clean(); + public function onStart() { + global $terminal_id; + $r = null; + if ($terminal_id !== null) { + ZMBuf::$terminal = $r = STDIN; + Event::add($r, function () use ($r) { + $var = trim(fgets($r)); + try { + Terminal::executeCommand($var, $r); + } catch (Exception $e) { + Console::error("Uncaught exception ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + } catch (Error $e) { + Console::error("Uncaught error ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + } + }); + } + Process::signal(SIGINT, function () use ($r) { + Console::warning("Server interrupted by keyboard on Master."); + if ((Framework::$server->inotify ?? null) !== null) + /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); + ZMUtil::stop(); + }); + if(Framework::$argv["watch"]) { + if (extension_loaded('inotify')) { + Console::warning("Enabled File watcher, do not use in production."); + Framework::$server->inotify = $fd = inotify_init(); + $this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd); + Event::add($fd, function () use ($fd) { + $r = inotify_read($fd); + var_dump($r); + ZMUtil::reload(); + }); + } else { + Console::warning("You have not loaded inotify extension."); + } } } /** - * @OnEvent("message") - * @param $server - * @param Frame $frame - * @throws AnnotationException + * @HandleEvent("shutdown") */ - public function onMessage($server, Frame $frame) { - if ($frame->fd !== ZMBuf::get("terminal_fd")) - Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd); - EventHandler::callSwooleEvent("message", $server, $frame); + public function onShutdown() { + Console::debug("正在关闭 Master 进程,pid=" . posix_getpid()); } /** - * @OnEvent("request") + * @HandleEvent("WorkerStop") + * @param $server + * @param $worker_id + */ + public function onWorkerStop(Server $server, $worker_id) { + Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止"); + } + + /** + * @HandleEvent("WorkerStart") + * @param Server $server + * @param $worker_id + */ + public function onWorkerStart(Server $server, $worker_id) { + //if (ZMBuf::atomic("stop_signal")->get() != 0) return; + Process::signal(SIGINT, function () use ($worker_id, $server) { + Console::debug("正在关闭 " . ($server->taskworker ? "Task" : "") . "Worker 进程 " . Console::setColor("#" . \server()->worker_id, "gold") . TermColor::frontColor256(59) . ", pid=" . posix_getpid()); + server()->stop($worker_id); + }); + unset(Context::$context[Co::getCid()]); + if ($server->taskworker === false) { + try { + register_shutdown_function(function () use ($server) { + $error = error_get_last(); + if ($error["type"] != 0) { + Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); + } + DataProvider::saveBuffer(); + /** @var Server $server */ + if (server() === null) $server->shutdown(); + else server()->shutdown(); + }); + + Console::info("Worker #{$server->worker_id} 启动中"); + Framework::$server = $server; + ZMBuf::resetCache(); //清空变量缓存 + //ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 + foreach ($server->connections as $v) { + $server->close($v); + } + if (ZMBuf::$sql_pool !== null) { + ZMBuf::$sql_pool->close(); + ZMBuf::$sql_pool = null; + } + + // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器 + /*if ($server->worker_id === 0) { + global $terminal_id; + if ($terminal_id !== null) + go(function () { + while (true) { + $r = server()->process->exportSocket(); + $result = $r->recv(); + try { + if (!Terminal::executeCommand($result)) { + //if ($result == "stop" || $result == "reload" || $result == "r") { + //echo "Stopped.\n"; + break; + } + } catch (Exception $e) { + Console::error($e->getMessage()); + } catch (Error $e) { + Console::error($e->getMessage()); + } + } + }); + }*/ + //TODO: 单独抽出来MySQL和Redis连接池 + if (ZMConfig::get("global", "sql_config")["sql_host"] != "") { + Console::info("新建SQL连接池中"); + ob_start(); + phpinfo(); + $str = ob_get_clean(); + $str = explode("\n", $str); + foreach ($str as $k => $v) { + $v = trim($v); + if ($v == "") continue; + if (mb_strpos($v, "API Extensions") === false) continue; + if (mb_strpos($v, "pdo_mysql") === false) { + throw new DbException("未安装 mysqlnd php-mysql扩展。"); + } + } + $sql = ZMConfig::get("global", "sql_config"); + ZMBuf::$sql_pool = new PDOPool((new PDOConfig()) + ->withHost($sql["sql_host"]) + ->withPort($sql["sql_port"]) + // ->withUnixSocket('/tmp/mysql.sock') + ->withDbName($sql["sql_database"]) + ->withCharset('utf8mb4') + ->withUsername($sql["sql_username"]) + ->withPassword($sql["sql_password"]) + ->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false]) + ); + DB::initTableList(); + } + + $this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等 + + //echo json_encode(debug_backtrace(), 128|256); + Console::success("Worker #" . $worker_id . " 已启动"); + EventManager::registerTimerTick(); //启动计时器 + //ZMBuf::unsetCache("wait_start"); + set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); + $dispatcher = new EventDispatcher(OnStart::class); + $dispatcher->setRuleFunction(function ($v) { + return server()->worker_id === $v->worker_id || $v->worker_id === -1; + }); + $dispatcher->dispatchEvents($server, $worker_id); + Console::debug("@OnStart 执行完毕"); + } catch (Exception $e) { + Console::error("Worker加载出错!停止服务!"); + Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); + ZMUtil::stop(); + return; + } catch (Error $e) { + Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); + Console::error("Maybe it caused by your own code if in your own Module directory."); + Console::log($e->getTraceAsString(), 'gray'); + ZMUtil::stop(); + } + } else { + // 这里是TaskWorker初始化的内容部分 + try { + Framework::$server = $server; + $this->loadAnnotations(); + Console::debug("TaskWorker #" . $server->worker_id . " 已启动"); + } catch (Exception $e) { + Console::error("Worker加载出错!停止服务!"); + Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); + ZMUtil::stop(); + return; + } catch (Error $e) { + Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); + Console::error("Maybe it caused by your own code if in your own Module directory."); + Console::log($e->getTraceAsString(), 'gray'); + ZMUtil::stop(); + } + } + } + + /** + * @HandleEvent("message") + * @param $server + * @param Frame $frame + */ + public function onMessage($server, Frame $frame) { + Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd); + unset(Context::$context[Co::getCid()]); + $conn = ManagerGM::get($frame->fd); + set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]); + $dispatcher = new EventDispatcher(SwooleEvent::class); + $dispatcher->setRuleFunction(function ($v) { + if ($v->getRule() == '') { + return strtolower($v->type) == 'message'; + } else { + /** @noinspection PhpUnreachableStatementInspection + * @noinspection RedundantSuppression + */ + if (strtolower($v->type) == 'message' && eval("return " . $v->getRule() . ";")) return true; + else return false; + } + }); + try { + $dispatcher->dispatchEvents($conn); + } catch (Exception $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::trace(); + } catch (Error $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::trace(); + } + } + + /** + * @HandleEvent("request") * @param $request * @param $response - * @throws AnnotationException */ public function onRequest($request, $response) { $response = new Response($response); - Console::debug("Receiving Http request event, cid=" . Co::getCid()); - EventHandler::callSwooleEvent("request", $request, $response); + unset(Context::$context[Co::getCid()]); + Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd); + set_coroutine_params(["request" => $request, "response" => $response]); + + $dis = new EventDispatcher(); + $dis->setRuleFunction(function ($v) { + if ($v->getRule() == '') { + return strtolower($v->type) == 'request'; + } else { + /** @noinspection PhpUnreachableStatementInspection */ + if (strtolower($v->type) == 'request' && eval("return " . $v->getRule() . ";")) return true; + else return false; + } + }); + + try { + $no_interrupt = $dis->dispatchEvents($request, $response); + if (!$no_interrupt) { + $result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params); + if (!$result) { + ctx()->setCache("params", $params); + $dispatcher = new EventDispatcher(RequestMapping::class); + $div = new RequestMapping(); + $div->route = $node["route"]; + $div->params = $params; + $div->method = $node["method"]; + $div->request_method = $node["request_method"]; + $div->class = $node["class"]; + $r = $dispatcher->dispatchEvent($div, null, $params, $request, $response); + if (is_string($r) && !$response->isEnd()) $response->end($r); + } + } + if (!$response->isEnd()) { + HttpUtil::responseCodePage($response, 404); + } + } catch (Exception $e) { + $response->status(500); + Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] . + " [" . $response->getStatusCode() . "] " . $request->server["request_uri"] + ); + if (!$response->isEnd()) { + if (ZMConfig::get("global", "debug_mode")) + $response->end("Internal server exception: " . $e->getMessage()); + else + $response->end("Internal server error."); + } + Console::error("Internal server exception (500), caused by " . get_class($e)); + Console::log($e->getTraceAsString(), "gray"); + } catch (Error $e) { + $response->status(500); + Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] . + " [" . $response->getStatusCode() . "] " . $request->server["request_uri"] + ); + if (!$response->isEnd()) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + if (ZMConfig::get("global", "debug_mode")) + $response->end("Internal server error: " . $error_msg); + else + $response->end("Internal server error."); + } + Console::error("Internal server error (500), caused by " . get_class($e)); + Console::log($e->getTraceAsString(), "gray"); + } } /** - * @OnEvent("open") + * @HandleEvent("open") * @param $server * @param Request $request - * @throws AnnotationException */ public function onOpen($server, Request $request) { Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); - EventHandler::callSwooleEvent("open", $server, $request); + unset(Context::$context[Co::getCid()]); + $type = strtolower($request->get["type"] ?? $request->header["x-client-role"] ?? ""); + $type_conn = ManagerGM::getTypeClassName($type); + ManagerGM::pushConnect($request->fd, $type_conn); + $conn = ManagerGM::get($request->fd); + set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]); + $dispatcher = new EventDispatcher(SwooleEvent::class); + $dispatcher->setRuleFunction(function ($v) { + if ($v->getRule() == '') { + return strtolower($v->type) == 'open'; + } else { + /** @noinspection PhpUnreachableStatementInspection */ + if (strtolower($v->type) == 'open' && eval("return " . $v->getRule() . ";")) return true; + else return false; + } + }); + try { + $dispatcher->dispatchEvents($conn); + } catch (Exception $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::trace(); + } catch (Error $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught Error " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::trace(); + } + //EventHandler::callSwooleEvent("open", $server, $request); } /** - * @OnEvent("close") + * @HandleEvent("close") * @param $server * @param $fd - * @throws AnnotationException */ public function onClose($server, $fd) { Console::debug("Calling Swoole \"close\" event from fd=" . $fd); - EventHandler::callSwooleEvent("close", $server, $fd); + unset(Context::$context[Co::getCid()]); + $conn = ManagerGM::get($fd); + set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]); + $dispatcher = new EventDispatcher(SwooleEvent::class); + $dispatcher->setRuleFunction(function ($v) { + if ($v->getRule() == '') { + return strtolower($v->type) == 'close'; + } else { + /** @noinspection PhpUnreachableStatementInspection */ + if (strtolower($v->type) == 'close' && eval("return " . $v->getRule() . ";")) return true; + else return false; + } + }); + try { + $dispatcher->dispatchEvents($conn); + } catch (Exception $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg); + Console::trace(); + } catch (Error $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error("Uncaught Error " . get_class($e) . " when calling \"close\": " . $error_msg); + Console::trace(); + } + ManagerGM::popConnect($fd); + } + + /** + * @HandleEvent("pipeMessage") + * @param $server + * @param $src_worker_id + * @param $data + */ + public function onPipeMessage(Server $server, $src_worker_id, $data) { + //var_dump($data, $server->worker_id); + unset(Context::$context[Co::getCid()]); + $data = json_decode($data, true); + switch ($data["action"]) { + case "stop": + Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器'); + Timer::clearAll(); + break; + case "terminate": + $server->stop(); + break; + case 'echo': + Console::success('接收到来自 #' . $src_worker_id . ' 的消息'); + break; + default: + echo $data . PHP_EOL; + } + } + + /** + * @HandleEvent("task") + */ + public function onTask() { + } + + /** + * @throws ReflectionException + * @throws Exception + */ + private function loadAnnotations() { + //加载phar包 + /*Console::debug("加载外部phar包中"); + $dir = DataProvider::getWorkingDir() . "/resources/package/"; + if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll(); + if (is_dir($dir)) { + $list = scandir($dir); + unset($list[0], $list[1]); + foreach ($list as $v) { + if (is_dir($dir . $v)) continue; + if (pathinfo($dir . $v, 4) == "phar") { + Console::debug("加载Phar: " . $dir . $v . " 中"); + require_once($dir . $v); + } + } + }*/ + + //加载各个模块的注解类,以及反射 + Console::debug("检索Module中"); + $parser = new AnnotationParser(); + $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module"); + $parser->registerMods(); + EventManager::loadEventByParser($parser); //加载事件 + + //加载自定义的全局函数 + Console::debug("加载自定义上下文中..."); + $context_class = ZMConfig::get("global", "context_class"); + if (!is_a($context_class, ContextInterface::class, true)) { + throw new Exception("Context class must implemented from ContextInterface!"); + } + + //加载插件 + $plugins = ZMConfig::get("global", "plugins") ?? []; + if (!isset($plugins["qqbot"])) $plugins["qqbot"] = true; + + if ($plugins["qqbot"]) { + $obj = new SwooleEvent(); + $obj->class = QQBot::class; + $obj->method = 'handle'; + $obj->type = 'message'; + $obj->level = 99999; + $obj->rule = 'connectIsQQ()'; + EventManager::addEvent(SwooleEvent::class, $obj); + } + + //TODO: 编写加载外部插件的方式 + //$this->loadExternalModules(); + } + + private function addWatcher($maindir, $fd) { + $dir = scandir($maindir); + unset($dir[0], $dir[1]); + foreach ($dir as $subdir) { + if (is_dir($maindir . "/" . $subdir)) { + Console::debug("添加监听目录:" . $maindir . "/" . $subdir); + inotify_add_watch($fd, $maindir . "/" . $subdir, IN_ATTRIB | IN_ISDIR); + $this->addWatcher($maindir . "/" . $subdir, $fd); + } + } } } diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php index fc2b8db7..55fde62f 100644 --- a/src/ZM/Event/Swoole/MessageEvent.php +++ b/src/ZM/Event/Swoole/MessageEvent.php @@ -10,13 +10,12 @@ use ZM\Console\Console; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Annotation\Swoole\SwooleEventAt; +use ZM\Annotation\Swoole\SwooleEvent; use Exception; use ZM\Event\EventHandler; use ZM\Store\ZMBuf; -use ZM\Utils\ZMUtil; -class MessageEvent implements SwooleEvent +class MessageEvent implements SwooleEventInterface { /** * @var Server @@ -36,7 +35,6 @@ class MessageEvent implements SwooleEvent * @inheritDoc */ public function onActivate() { - ZMUtil::checkWait(); $conn = ManagerGM::get(context()->getFrame()->fd); try { if ($conn->getName() == "qq") { @@ -51,7 +49,7 @@ class MessageEvent implements SwooleEvent EventHandler::callCQResponse($data); } } - foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) { $c = $v->class; EventHandler::callWithMiddleware( diff --git a/src/ZM/Event/Swoole/RequestEvent.php b/src/ZM/Event/Swoole/RequestEvent.php index 54c1fbeb..f05f7a54 100644 --- a/src/ZM/Event/Swoole/RequestEvent.php +++ b/src/ZM/Event/Swoole/RequestEvent.php @@ -6,17 +6,20 @@ namespace ZM\Event\Swoole; use Closure; use Exception; +use ZM\Config\ZMConfig; use ZM\Console\Console; -use Framework\ZMBuf; +use ZM\Event\EventManager; +use ZM\Store\ZMBuf; use Swoole\Http\Request; use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Annotation\Swoole\SwooleEventAt; +use ZM\Annotation\Swoole\SwooleEvent; use ZM\Event\EventHandler; use ZM\Http\Response; use ZM\Utils\DataProvider; +use ZM\Utils\HttpUtil; use ZM\Utils\ZMUtil; -class RequestEvent implements SwooleEvent +class RequestEvent implements SwooleEventInterface { /** * @var Request @@ -37,15 +40,14 @@ class RequestEvent implements SwooleEvent * @throws Exception */ public function onActivate() { - ZMUtil::checkWait(); - foreach (\ZM\Config\ZMConfig::get("global", "http_header") as $k => $v) { + foreach (ZMConfig::get("global", "http_header") as $k => $v) { $this->response->setHeader($k, $v); } $uri = $this->request->server["request_uri"]; Console::verbose($this->request->server["remote_addr"] . " request " . $uri); $uri = explode("/", $uri); $uri = array_diff($uri, ["..", "", "."]); - $node = ZMBuf::$req_mapping; + $node = EventManager::$req_mapping; $params = []; while (true) { $r = array_shift($uri); @@ -81,9 +83,9 @@ class RequestEvent implements SwooleEvent } } - if (\ZM\Config\ZMConfig::get("global", "static_file_server")["status"]) { - $base_dir = \ZM\Config\ZMConfig::get("global", "static_file_server")["document_root"]; - $base_index = \ZM\Config\ZMConfig::get("global", "static_file_server")["document_index"]; + if (ZMConfig::get("global", "static_file_server")["status"]) { + $base_dir = ZMConfig::get("global", "static_file_server")["document_root"]; + $base_index = ZMConfig::get("global", "static_file_server")["document_index"]; $uri = $this->request->server["request_uri"]; $path = realpath($base_dir . urldecode($uri)); if ($path !== false) { @@ -98,7 +100,7 @@ class RequestEvent implements SwooleEvent if (is_file($path . $vp)) { Console::info("[200] " . $uri . " (static)"); $exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown"); - $this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream"); + $this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); $this->response->end(file_get_contents($path . $vp)); return $this; } @@ -106,14 +108,14 @@ class RequestEvent implements SwooleEvent } elseif (is_file($path)) { Console::info("[200] " . $uri . " (static)"); $exp = strtolower(pathinfo($path)['extension'] ?? "unknown"); - $this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream"); + $this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); $this->response->end(file_get_contents($path)); return $this; } } } $this->response->status(404); - $this->response->end(ZMUtil::getHttpCodePage(404)); + $this->response->end(HttpUtil::getHttpCodePage(404)); return $this; } context()->setCache("params", $params); @@ -131,7 +133,7 @@ class RequestEvent implements SwooleEvent } ); } - foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) { $c = $v->class; EventHandler::callWithMiddleware($c, $v->method, ["request" => $this->request, "response" => $this->response], []); @@ -141,7 +143,7 @@ class RequestEvent implements SwooleEvent if (!$this->response->isEnd()) { $this->response->status(404); - $this->response->end(ZMUtil::getHttpCodePage(404)); + $this->response->end(HttpUtil::getHttpCodePage(404)); } return $this; } diff --git a/src/ZM/Event/Swoole/SwooleEvent.php b/src/ZM/Event/Swoole/SwooleEventInterface.php similarity index 57% rename from src/ZM/Event/Swoole/SwooleEvent.php rename to src/ZM/Event/Swoole/SwooleEventInterface.php index 73b1d54e..2918f3ff 100644 --- a/src/ZM/Event/Swoole/SwooleEvent.php +++ b/src/ZM/Event/Swoole/SwooleEventInterface.php @@ -6,15 +6,15 @@ namespace ZM\Event\Swoole; use ZM\Event\Event; -interface SwooleEvent extends Event +interface SwooleEventInterface extends Event { /** - * @return SwooleEvent + * @return SwooleEventInterface */ public function onActivate(); /** - * @return SwooleEvent + * @return SwooleEventInterface */ public function onAfter(); -} \ No newline at end of file +} diff --git a/src/ZM/Event/Swoole/WSCloseEvent.php b/src/ZM/Event/Swoole/WSCloseEvent.php index 0bd6d214..e06e2086 100644 --- a/src/ZM/Event/Swoole/WSCloseEvent.php +++ b/src/ZM/Event/Swoole/WSCloseEvent.php @@ -10,12 +10,12 @@ use ZM\ConnectionManager\ManagerGM; use ZM\Console\Console; use Swoole\Server; use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Annotation\Swoole\SwooleEventAt; +use ZM\Annotation\Swoole\SwooleEvent; use ZM\Event\EventHandler; use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; -class WSCloseEvent implements SwooleEvent +class WSCloseEvent implements SwooleEventInterface { public $server; @@ -31,10 +31,9 @@ class WSCloseEvent implements SwooleEvent * @throws AnnotationException */ public function onActivate() { - Console::info("Closed #{$this->fd}"); - ZMUtil::checkWait(); + Console::debug("Websocket closed #{$this->fd}"); set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ManagerGM::get($this->fd)]); - foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + foreach(ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) { $c = $v->class; EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []); diff --git a/src/ZM/Event/Swoole/WSOpenEvent.php b/src/ZM/Event/Swoole/WSOpenEvent.php index 1524fa3e..b0117435 100644 --- a/src/ZM/Event/Swoole/WSOpenEvent.php +++ b/src/ZM/Event/Swoole/WSOpenEvent.php @@ -13,12 +13,11 @@ use ZM\Console\Console; use Swoole\Http\Request; use Swoole\WebSocket\Server; use ZM\Annotation\Swoole\SwooleEventAfter; -use ZM\Annotation\Swoole\SwooleEventAt; +use ZM\Annotation\Swoole\SwooleEvent; use ZM\Event\EventHandler; use ZM\Store\ZMBuf; -use ZM\Utils\ZMUtil; -class WSOpenEvent implements SwooleEvent +class WSOpenEvent implements SwooleEventInterface { /** * @var Server @@ -41,10 +40,9 @@ class WSOpenEvent implements SwooleEvent * @throws AnnotationException */ public function onActivate() { - ZMUtil::checkWait(); - ManagerGM::pushConnect($this->request->fd); $type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? ""); $type_conn = $this->getTypeClassName($type); + ManagerGM::pushConnect($this->request->fd, $type_conn); if ($type_conn == "qq") { ManagerGM::setName($this->request->fd, "qq"); $qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? ""; @@ -65,7 +63,7 @@ class WSOpenEvent implements SwooleEvent $this->conn = ManagerGM::get($this->request->fd); } set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]); - foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) { + foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { $c = $v->class; EventHandler::callWithMiddleware( @@ -104,14 +102,5 @@ class WSOpenEvent implements SwooleEvent return true; } - private function getTypeClassName(string $type) { - $map = [ - "qq" => "qq", - "universal" => "qq", - "webconsole" => "webconsole", - "proxy" => "proxy", - "terminal" => "terminal" - ]; - return $map[$type] ?? "default"; - } + } diff --git a/src/ZM/Event/Swoole/WorkerStartEvent.php b/src/ZM/Event/Swoole/WorkerStartEvent.php deleted file mode 100644 index d5b5911b..00000000 --- a/src/ZM/Event/Swoole/WorkerStartEvent.php +++ /dev/null @@ -1,189 +0,0 @@ -server = $server; - $this->worker_id = $worker_id; - } - - /** - * @return WorkerStartEvent - * @throws AnnotationException - * @throws ReflectionException - * @throws DbException - */ - public function onActivate(): WorkerStartEvent { - - Console::info("Worker #{$this->server->worker_id} 启动中"); - ZMBuf::$server = $this->server; - ZMBuf::resetCache(); //清空变量缓存 - ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 - $this->resetConnections();//释放所有与framework的连接 - - global $terminal_id; - - Terminal::listenConsole($terminal_id); //这个方法只能在这里调用,且如果worker_num不为1的话,此功能不可用 - // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器 - if ($this->server->worker_id === 0) { - if($terminal_id !== null) Console::info("监听console输入"); - Process::signal(SIGINT, function () { - echo PHP_EOL; - Console::warning("Server interrupted by keyboard."); - ZMUtil::stop(); - }); - ZMBuf::$atomics['reload_time']->add(1); - $this->setAutosaveTimer(ZMConfig::get("global", "auto_save_interval")); - } else { - Process::signal(SIGINT, function () { - // Do Nothing - }); - } - if (ZMConfig::get("global", "sql_config")["sql_host"] != "") { - Console::info("新建SQL连接池中"); - ob_start(); - phpinfo(); - $str = ob_get_clean(); - $str = explode("\n", $str); - foreach ($str as $k => $v) { - $v = trim($v); - if ($v == "") continue; - if (mb_strpos($v, "API Extensions") === false) continue; - if (mb_strpos($v, "pdo_mysql") === false) { - throw new DbException("未安装 mysqlnd php-mysql扩展。"); - } - } - $sql = ZMConfig::get("global", "sql_config"); - ZMBuf::$sql_pool = new PDOPool((new PDOConfig()) - ->withHost($sql["sql_host"]) - ->withPort($sql["sql_port"]) - // ->withUnixSocket('/tmp/mysql.sock') - ->withDbName($sql["sql_database"]) - ->withCharset('utf8mb4') - ->withUsername($sql["sql_username"]) - ->withPassword($sql["sql_password"]) - ->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false]) - ); - DB::initTableList(); - } - - $this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等 - return $this; - } - - /** - * @return WorkerStartEvent - * @throws AnnotationException - */ - public function onAfter(): WorkerStartEvent { - foreach (ZMBuf::get("wait_start") as $v) { - Coroutine::resume($v); - } - ZMBuf::unsetCache("wait_start"); - set_coroutine_params(["server" => $this->server, "worker_id" => $this->worker_id]); - if($this->server->worker_id === 0) { - foreach (ZMBuf::$events[OnStart::class] ?? [] as $v) { - $class_name = $v->class; - Console::debug("正在调用启动时函数: " . $class_name . " -> " . $v->method); - EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []); - } - Console::debug("@OnStart 执行完毕"); - } - return $this; - } - - private function resetConnections() { - foreach ($this->server->connections as $v) { - $this->server->close($v); - } - if (ZMBuf::$sql_pool !== null) { - ZMBuf::$sql_pool->close(); - ZMBuf::$sql_pool = null; - } - } - - /** - * @throws ReflectionException - * @throws Exception - */ - private function loadAllClass() { - //加载phar包 - Console::info("加载外部phar包中"); - $dir = DataProvider::getWorkingDir() . "/resources/package/"; - if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll(); - if (is_dir($dir)) { - $list = scandir($dir); - unset($list[0], $list[1]); - foreach ($list as $v) { - if (is_dir($dir . $v)) continue; - if (pathinfo($dir . $v, 4) == "phar") { - Console::verbose("加载Phar: " . $dir . $v . " 中"); - require_once($dir . $v); - } - } - } - //加载composer类 - //remove stupid duplicate code - - //加载各个模块的注解类,以及反射 - Console::info("检索Module中"); - $parser = new AnnotationParser(); - $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module"); - $parser->registerMods(); - $parser->sortLevels(); - - //加载自定义的全局函数 - Console::debug("加载自定义的全局函数中"); - $this->afterCheck(); - } - - private function setAutosaveTimer($globals) { - DataProvider::$buffer_list = []; - zm_timer_tick($globals * 1000, function () { - DataProvider::saveBuffer(); - }); - } - - /** - * @throws Exception - */ - private function afterCheck() { - $context_class = ZMConfig::get("global", "context_class"); - if (!is_a($context_class, ContextInterface::class, true)) { - throw new Exception("Context class must implemented from ContextInterface!"); - } - } -} diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php new file mode 100644 index 00000000..b11016cd --- /dev/null +++ b/src/ZM/Exception/InterruptException.php @@ -0,0 +1,12 @@ +getTtyWidth(); - if (LOAD_MODE == 0) define("WORKING_DIR", getcwd()); - elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); - elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL; - require_once "Utils/DataProvider.php"; - if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) { - /** @noinspection PhpIncludeInspection */ - require_once DataProvider::getWorkingDir() . "/vendor/autoload.php"; - } - if (LOAD_MODE == 2) { - // Phar 模式,2.0 不提供哦 - //require_once FRAMEWORK_DIR . "/vendor/autoload.php"; - spl_autoload_register('phar_classloader'); - } elseif (LOAD_MODE == 0) { - /** @noinspection PhpIncludeInspection - * @noinspection RedundantSuppression - */ - require_once WORKING_DIR . "/vendor/autoload.php"; - } - - if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { - die("Unable to find source directory.\nMaybe you need to run \"init\"?"); - } - ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config'); - ZMConfig::env($args["env"] ?? ""); - if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error); self::$argv = $args; - $this->defineProperties(); + //定义常量 + include_once "global_defines.php"; + ZMBuf::initAtomic(); - ManagerGM::init(1024, 0.2, [ - [ - "key" => "connect_id", - "type" => "string", - "size" => 30 - ] - ]); + try { + ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [ + [ + "key" => "connect_id", + "type" => "string", + "size" => 30 + ], + [ + "key" => "type", + "type" => "int" + ] + ]); + } catch (ConnectionManager\TableException $e) { + die($e->getMessage()); + } //start swoole Framework $this->selfCheck(); try { @@ -85,8 +76,6 @@ class Framework $args["log-theme"] ?? "default", ($o = ZMConfig::get("console_color")) === false ? [] : $o ); - // 注册 Swoole Server 的事件 - $this->registerServerEvents(); $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; date_default_timezone_set($timezone); @@ -107,27 +96,13 @@ class Framework if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) { $out["worker_num"] = $num; } - $store = ""; - foreach ($out as $k => $v) { - $line = $k . ": " . $v; - if (strlen($line) > 19 && $store == "" || $tty_width < 53) { - Console::log($line); - } else { - if ($store === "") $store = str_pad($line, 19, " ", STR_PAD_RIGHT); - else { - $store .= (" | " . $line); - Console::log($store); - $store = ""; - } - } - } - if ($store != "") Console::log($store); + Console::printProps($out, $tty_width); self::$server->set($this->server_set); if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { $motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); } else { - $motd = file_get_contents(__DIR__."/../../config/motd.txt"); + $motd = file_get_contents(__DIR__ . "/../../config/motd.txt"); } $motd = explode("\n", $motd); foreach ($motd as $k => $v) { @@ -137,6 +112,13 @@ class Framework echo $motd; global $asd; $asd = get_included_files(); + // 注册 Swoole Server 的事件 + $this->registerServerEvents(); + LightCache::init(ZMConfig::get("global", "light_cache") ?? [ + "size" => 2048, + "max_strlen" => 4096, + "hash_conflict_proportion" => 0.6, + ]); self::$server->start(); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); @@ -145,24 +127,6 @@ class Framework } } - private function defineProperties() { - define("ZM_START_TIME", microtime(true)); - define("ZM_DATA", ZMConfig::get("global", "zm_data")); - define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown"); - define("CONFIG_DIR", ZMConfig::get("global", "config_dir")); - define("CRASH_DIR", ZMConfig::get("global", "crash_dir")); - @mkdir(ZM_DATA); - @mkdir(CONFIG_DIR); - @mkdir(CRASH_DIR); - define("ZM_MATCH_ALL", 0); - define("ZM_MATCH_FIRST", 1); - define("ZM_MATCH_NUMBER", 2); - define("ZM_MATCH_SECOND", 3); - define("ZM_BREAKPOINT", 'if(Framework::$argv["debug-mode"]) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));'); - define("BP", ZM_BREAKPOINT); - define("ZM_DEFAULT_FETCH_MODE", 4); - } - private function selfCheck() { if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); @@ -194,20 +158,25 @@ class Framework $method_annotations = $reader->getMethodAnnotations($vs); if ($method_annotations != []) { $annotation = $method_annotations[0]; - if ($annotation instanceof OnEvent) { + if ($annotation instanceof HandleEvent) { $annotation->class = $v; $annotation->method = $vs->getName(); $event_list[strtolower($annotation->event)] = $annotation; + } elseif ($annotation instanceof SwooleSetup) { + $annotation->class = $v; + $annotation->method = $vs->getName(); + $c = new $v(); + $m = $annotation->method; + $c->$m(); } } } } foreach ($event_list as $k => $v) { self::$server->on($k, function (...$param) use ($v) { - $c = $v->class; - //echo $c.PHP_EOL; - $c = new $c(); - call_user_func_array([$c, $v->method], $param); + $c = ZMUtil::getModInstance($v->class); + $m = $v->method; + $c->$m(...$param); }); } } @@ -232,6 +201,11 @@ class Framework }); foreach ($args as $x => $y) { switch ($x) { + case 'disable-coroutine': + if($y) { + $coroutine_mode = false; + } + break; case 'debug-mode': if ($y) { $coroutine_mode = false; @@ -273,7 +247,7 @@ class Framework if ($y) Console::setLevel(4); break; case 'log-theme': - if($y !== null) { + if ($y !== null) { Console::$theme = $y; } break; diff --git a/src/ZM/Http/MiddlewareInterface.php b/src/ZM/Http/MiddlewareInterface.php index 79c00865..a0ed6969 100644 --- a/src/ZM/Http/MiddlewareInterface.php +++ b/src/ZM/Http/MiddlewareInterface.php @@ -6,5 +6,5 @@ namespace ZM\Http; interface MiddlewareInterface { - public function getName(); + } diff --git a/src/ZM/Http/StaticFileHandler.php b/src/ZM/Http/StaticFileHandler.php index f1163405..efc3172e 100644 --- a/src/ZM/Http/StaticFileHandler.php +++ b/src/ZM/Http/StaticFileHandler.php @@ -6,6 +6,7 @@ namespace ZM\Http; use ZM\Console\Console; use Framework\ZMBuf; +use ZM\Utils\HttpUtil; use ZM\Utils\ZMUtil; class StaticFileHandler @@ -29,7 +30,7 @@ class StaticFileHandler } } $response->status(404); - $response->end(ZMUtil::getHttpCodePage(404)); + $response->end(HttpUtil::getHttpCodePage(404)); return true; } } diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php new file mode 100644 index 00000000..b50e7706 --- /dev/null +++ b/src/ZM/Module/QQBot.php @@ -0,0 +1,15 @@ +column("value", Table::TYPE_STRING, $config["max_strlen"]); + self::$kv_table->column("expire", Table::TYPE_INT); + self::$kv_table->column("data_type", Table::TYPE_STRING, 12); + return self::$kv_table->create(); + } + + /** + * @param string $key + * @return null|string + * @throws Exception + */ + public static function get(string $key) { + if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + self::checkExpire($key); + $r = self::$kv_table->get($key); + return $r === false ? null : self::parseGet($r); + } + + /** + * @param string $key + * @return mixed|null + * @throws Exception + */ + public static function getExpire(string $key) { + if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + self::checkExpire($key); + $r = self::$kv_table->get($key, "expire"); + return $r === false ? null : $r - time(); + } + + /** + * @param string $key + * @param string|array|int $value + * @param int $expire + * @return mixed + * @throws Exception + */ + public static function set(string $key, $value, int $expire = -1) { + if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + if (is_array($value) || is_int($value)) { + $value = json_encode($value, JSON_UNESCAPED_UNICODE); + if (strlen($value) >= self::$config["max_strlen"]) return false; + $data_type = "json"; + } elseif (is_string($value)) { + $data_type = ""; + } else { + throw new Exception("Only can set string, array and int"); + } + try { + return self::$kv_table->set($key, [ + "value" => $value, + "expire" => $expire != -1 ? $expire + time() : -1, + "data_type" => $data_type + ]); + } catch (Exception $e) { + return false; + } + } + + public static function getMemoryUsage() { + return self::$kv_table->getMemorySize(); + } + + /** + * @param string $key + * @return bool + * @throws Exception + */ + public static function isset(string $key) { + return self::get($key) !== null; + } + + public static function unset(string $key) { + return self::$kv_table->del($key); + } + + public static function getAll() { + $r = []; + $del = []; + foreach (self::$kv_table as $k => $v) { + if ($v["expire"] <= time()) { + $del[]=$k; + continue; + } + $r[$k] = self::parseGet($v); + } + foreach($del as $v) { + self::unset($v); + } + return $r; + } + + private static function checkExpire($key) { + if (($expire = self::$kv_table->get($key, "expire")) !== -1) { + if ($expire <= time()) { + self::$kv_table->del($key); + } + } + } + + private static function parseGet($r) { + switch ($r["data_type"]) { + case "json": + return json_decode($r["value"], true); + case "": + default: + return $r["value"]; + } + } +} diff --git a/src/ZM/Store/ZMBuf.php b/src/ZM/Store/ZMBuf.php index 147003a8..60b6e89e 100755 --- a/src/ZM/Store/ZMBuf.php +++ b/src/ZM/Store/ZMBuf.php @@ -10,7 +10,6 @@ namespace ZM\Store; use Swoole\Atomic; use Swoole\Database\PDOPool; -use swoole_atomic; use ZM\Config\ZMConfig; class ZMBuf @@ -18,36 +17,19 @@ class ZMBuf //读写的缓存数据,需要在worker_num = 1下才能正常使用 /** @var mixed[] ZMBuf的data */ private static $cache = []; - //Scheduler计划任务连接实例,只可以在单worker_num时使用 - static $scheduler = null; //This is stupid warning... - //Swoole SQL连接池,多进程下每个进程一个连接池 /** @var PDOPool */ static $sql_pool = null;//保存sql连接池的类 - - // swoole server操作对象,每个进程均分配 - /** @var \swoole_websocket_server $server */ - static $server; /** @var array Http请求uri路径根节点 */ public static $req_mapping_node; - /** @var mixed TimeNLP初始化后的对象,每个进程均可初始化 */ - public static $time_nlp; - /** @var string[] $custom_connection_class */ - public static $custom_connection_class = [];//保存自定义的ws connection连接类型的 - - // Atomic:可跨进程读写的原子计数,任何地方均可使用 - /** @var null|swoole_atomic */ - static $info_level = null;//保存log等级的原子计数 + /** @var array 事件注解的绑定对 */ public static $events = []; /** @var Atomic[] */ public static $atomics; - public static $req_mapping = []; - public static $config = []; - public static $context = []; public static $instance = []; public static $context_class = []; - public static $server_events = []; + public static $terminal = null; static function get($name, $default = null) { return self::$cache[$name] ?? $default; @@ -90,13 +72,8 @@ class ZMBuf return in_array($val, self::$cache[$name]); } - static function config($config_name) { - return self::$config[$config_name] ?? null; - } - public static function resetCache() { self::$cache = []; - self::$time_nlp = null; self::$instance = []; } @@ -107,6 +84,15 @@ class ZMBuf foreach (ZMConfig::get("global", "init_atomics") as $k => $v) { self::$atomics[$k] = new Atomic($v); } - self::$atomics["show_log_worker"] = new Atomic(999999); + self::$atomics["stop_signal"] = new Atomic(0); + self::$atomics["wait_msg_id"] = new Atomic(0); + } + + /** + * @param $name + * @return Atomic|null + */ + public static function atomic($name) { + return self::$atomics[$name] ?? null; } } diff --git a/src/ZM/Utils/CoroutinePool.php b/src/ZM/Utils/CoroutinePool.php new file mode 100644 index 00000000..042500b8 --- /dev/null +++ b/src/ZM/Utils/CoroutinePool.php @@ -0,0 +1,51 @@ += (self::$sizes[$name] ?? self::$default_size)) { + self::$yields[] = Coroutine::getCid(); + Coroutine::suspend(); + } + go(function () use ($func, $name) { + self::$cids[$name][] = Coroutine::getCid(); + //Console::debug("正在执行协程,当前协程池中有 " . count(self::$cids[$name]) . " 个正在运行的协程: ".implode(", ", self::$cids[$name])); + $func(); + self::checkCids($name); + }); + } + + public static function defaultSize(int $size) { + self::$default_size = $size; + } + + public static function setSize($name, int $size) { + self::$sizes[$name] = $size; + } + + private static function checkCids($name) { + if (in_array(Coroutine::getCid(), self::$cids[$name])) { + $a = array_search(Coroutine::getCid(), self::$cids[$name]); + array_splice(self::$cids[$name], $a, 1); + $r = array_shift(self::$yields); + if ($r !== null) { + Coroutine::resume($r); + } + } + } +} diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php new file mode 100644 index 00000000..cc2ab53e --- /dev/null +++ b/src/ZM/Utils/HttpUtil.php @@ -0,0 +1,117 @@ + $v) { + if ($v["id"] == $node["param_route"]) { + $node = $v; + $params[mb_substr($v["name"], 1, -1)] = $r; + continue 2; + } + } + } elseif ($node["son"][0]["name"] == $r) { + $node = $node["son"][0]; + continue; + } + } elseif ($cnt >= 1) { + if (isset($node["param_route"])) { + foreach ($node["son"] as $k => $v) { + if ($v["id"] == $node["param_route"]) { + $node = $v; + $params[mb_substr($v["name"], 1, -1)] = $r; + continue 2; + } + } + } + foreach ($node["son"] as $k => $v) { + if ($v["name"] == $r) { + $node = $v; + continue 2; + } + } + } + if (ZMConfig::get("global", "static_file_server")["status"]) { + HttpUtil::handleStaticPage($request->server["request_uri"], $response); + return true; + } + } + return false; + } + + public static function getHttpCodePage(int $http_code) { + if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) { + return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); + } else return null; + } + + /** + * @param $uri + * @param Response|\Swoole\Http\Response $response + * @param array $settings + * @return bool + */ + public static function handleStaticPage($uri, $response, $settings = []) { + $base_dir = $settings["document_root"] ?? ZMConfig::get("global", "static_file_server")["document_root"]; + $base_index = $settings["document_index"] ?? ZMConfig::get("global", "static_file_server")["document_index"]; + $path = realpath($base_dir . urldecode($uri)); + if ($path !== false) { + if (is_dir($path)) $path = $path . '/'; + $work = realpath($base_dir) . '/'; + if (strpos($path, $work) !== 0) { + Console::info("[403] " . $uri); + self::responseCodePage($response, 403); + return true; + } + if (is_dir($path)) { + if(mb_substr($uri, -1, 1) != "/") { + Console::info("[302] " . $uri); + $response->redirect($uri."/", 302); + return true; + } + foreach ($base_index as $vp) { + if (is_file($path . "/" . $vp)) { + Console::info("[200] " . $uri); + $exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown"); + $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); + $response->end(file_get_contents($path . $vp)); + return true; + } + } + } elseif (is_file($path)) { + Console::info("[200] " . $uri); + $exp = strtolower(pathinfo($path)['extension'] ?? "unknown"); + $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); + $response->end(file_get_contents($path)); + return true; + } + } + Console::info("[404] " . $uri); + self::responseCodePage($response, 404); + return true; + } + + public static function responseCodePage($response, $code) { + $response->status($code); + $response->end(self::getHttpCodePage($code)); + } +} diff --git a/src/ZM/Utils/Terminal.php b/src/ZM/Utils/Terminal.php index 3993e657..70bb7f94 100644 --- a/src/ZM/Utils/Terminal.php +++ b/src/ZM/Utils/Terminal.php @@ -5,74 +5,18 @@ namespace ZM\Utils; use Exception; -use ZM\Config\ZMConfig; -use ZM\ConnectionManager\ConnectionObject; +use Swoole\Event; use ZM\Console\Console; -use ZM\Store\ZMBuf; -use ZM\Annotation\Swoole\SwooleEventAt; +use ZM\Framework; class Terminal { - /** - * @var false|resource - */ - public static $console_proc = null; - public static $pipes = []; - - static function listenConsole($terminal_id) { - if ($terminal_id === null) { - if (ZMBuf::$server->worker_id === 0) Console::info("ConsoleCommand disabled."); - return; - } - global $terminal_id; - global $port; - $port = ZMConfig::get("global", "port"); - $vss = new SwooleEventAt(); - $vss->type = "open"; - $vss->level = 256; - $vss->rule = "connectType:terminal"; - - $vss->callback = function (?ConnectionObject $conn) use ($terminal_id) { - $req = ctx()->getRequest(); - if ($conn->getName() != "terminal") return false; - Console::debug("Terminal fd: " . $conn->getFd()); - ZMBuf::set("terminal_fd", $conn->getFd()); - if (($req->header["x-terminal-id"] ?? "") != $terminal_id) { - ZMBuf::$server->close($conn->getFd()); - return false; - } - return false; - }; - ZMBuf::$events[SwooleEventAt::class][] = $vss; - $vss2 = new SwooleEventAt(); - $vss2->type = "message"; - $vss2->rule = "connectType:terminal"; - $vss2->callback = function (?ConnectionObject $conn) { - if ($conn === null) return false; - if ($conn->getName() != "terminal") return false; - $cmd = ctx()->getFrame()->data; - self::executeCommand($cmd); - return false; - }; - ZMBuf::$events[SwooleEventAt::class][] = $vss2; - if (ZMBuf::$server->worker_id === 0) { - go(function () { - global $terminal_id, $port; - $descriptorspec = array( - 0 => STDIN, - 1 => STDOUT, - 2 => STDERR - ); - self::$console_proc = proc_open('php -r \'$terminal_id = "' . $terminal_id . '";$port = ' . $port . ';require "' . __DIR__ . '/terminal_listener.php";\'', $descriptorspec, self::$pipes); - }); - } - } - /** * @param string $cmd + * @param $resource * @return bool */ - private static function executeCommand(string $cmd) { + public static function executeCommand(string $cmd, $resource) { $it = explodeMsg($cmd); switch ($it[0] ?? '') { case 'logtest': @@ -88,7 +32,13 @@ class Terminal $class_name = $it[1]; $function_name = $it[2]; $class = new $class_name([]); - call_user_func_array([$class, $function_name], []); + $class->$function_name(); + return true; + case 'psysh': + if (Framework::$argv["disable-coroutine"]) + eval(\Psy\sh()); + else + Console::error("Only \"--disable-coroutine\" mode can use psysh!!!"); return true; case 'bc': $code = base64_decode($it[1] ?? '', true); @@ -104,18 +54,13 @@ class Terminal Console::log($it[2], $it[1]); return true; case 'stop': + Event::del($resource); ZMUtil::stop(); return false; case 'reload': case 'r': ZMUtil::reload(); return false; - case 'save': - //$origin = ZMBuf::$atomics["info_level"]->get(); - //ZMBuf::$atomics["info_level"]->set(3); - DataProvider::saveBuffer(); - //ZMBuf::$atomics["info_level"]->set($origin); - return true; case '': return true; default: diff --git a/src/ZM/Utils/ZMRequest.php b/src/ZM/Utils/ZMRequest.php deleted file mode 100644 index 2a6b6895..00000000 --- a/src/ZM/Utils/ZMRequest.php +++ /dev/null @@ -1,144 +0,0 @@ -set($set == [] ? ['timeout' => 15.0] : $set); - $cli->setHeaders($headers); - $cli->get($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : "")); - if ($return_body) { - if ($cli->errCode != 0 || $cli->statusCode != 200) return false; - $a = $cli->body; - $cli->close(); - return $a; - } else { - $cli->close(); - return $cli; - } - } - - /** - * 使用Swoole协程客户端发起HTTP POST请求 - * 返回请求后的body - * 如果请求失败或返回状态不是200,则返回 false - * @param $url - * @param array $header - * @param $data - * @param array $set - * @param bool $return_body - * @return bool|string|Client - */ - public static function post($url, array $header, $data, $set = [], $return_body = true) { - /** @var Client $cli */ - list($cli, $parse) = self::getNewClient($url); - if($cli === null) return false; - $cli->set($set == [] ? ['timeout' => 15.0] : $set); - $cli->setHeaders($header); - $cli->post($parse["path"] . (isset($parse["query"]) ? ("?" . $parse["query"]) : ""), $data); - if ($return_body) { - if ($cli->errCode != 0 || $cli->statusCode != 200) return false; - $a = $cli->body; - $cli->close(); - return $a; - } else { - $cli->close(); - return $cli; - } - } - - /** - * @param $url - * @param array $set - * @param array $header - * @return ZMWebSocket - * @since 1.5 - */ - public static function websocket($url, $set = ['websocket_mask' => true], $header = []) { - return new ZMWebSocket($url, $set, $header); - } - - /** - * @param $url - * @param array $attribute - * @param bool $return_body - * @return bool|string|Client - */ - public static function request($url, $attribute = [], $return_body = true) { - /** @var Client $cli */ - list($cli, $parse) = self::getNewClient($url); - if($cli === null) return false; - $cli->set($attribute["set"] ?? ["timeout" => 15.0]); - $cli->setMethod($attribute["method"] ?? "GET"); - $cli->setHeaders($attribute["headers"] ?? []); - if(isset($attribute["data"])) $cli->setData($attribute["data"]); - if(isset($attribute["file"])) { - foreach($attribute["file"] as $k => $v) { - $cli->addFile($v["path"], $v["name"], $v["mime_type"] ?? null, $v["filename"] ?? null, $v["offset"] ?? 0, $v["length"] ?? 0); - } - } - $cli->execute($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : "")); - if ($return_body) { - if ($cli->errCode != 0 || $cli->statusCode != 200) return false; - $a = $cli->body; - $cli->close(); - return $a; - } else { - $cli->close(); - return $cli; - } - } - - /** - * @param $url - * @param null|bool $dst - * @return bool - */ - public static function downloadFile($url, $dst = null) { - /** @var Client $cli */ - list($cli, $parse) = self::getNewClient($url); - if($cli === null) return false; - $cli->set(["timeout" => 60.0]); - $save_path = $dst === null ? "/tmp/_zm_".mt_rand(1000000, 9999999) : $dst; - $result = $cli->download($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""), $save_path); - if($result === false) return false; - elseif ($dst === null) return $save_path; - else return true; - } - - /** - * @param $url - * @return bool|array - */ - private static function getNewClient($url) { - $parse = parse_url($url); - if (!isset($parse["host"])) { - Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\""); - return false; - } - if(!isset($parse["path"])) $parse["path"] = "/"; - $port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80); - $cli = new Client($parse["host"], $port, ($parse["scheme"] ?? "http") == "https"); - return [$cli, $parse]; - } -} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index c4e5515e..31882c37 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -5,55 +5,45 @@ namespace ZM\Utils; use Co; -use ZM\Config\ZMConfig; +use Exception; +use Swoole\Event; +use Swoole\Timer; use ZM\Console\Console; -use ZM\API\CQ; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; class ZMUtil { - /** - * 检查workerStart是否运行结束 - */ - public static function checkWait() { - if (ZMBuf::isset("wait_start")) { - ZMBuf::append("wait_start", Co::getCid()); - Co::suspend(); + public static function stop() { + Console::warning(Console::setColor("Stopping server...", "red")); + if (ZMBuf::$terminal !== null) + Event::del(ZMBuf::$terminal); + ZMBuf::atomic("stop_signal")->set(1); + try { + LightCache::set('stop', 'OK'); + } catch (Exception $e) { } + server()->shutdown(); + server()->stop(); } - public static function stop($without_shutdown = false) { - Console::info(Console::setColor("Stopping server...", "red")); - foreach ((ZMBuf::$server->connections ?? []) as $v) { - ZMBuf::$server->close($v); - } - DataProvider::saveBuffer(); - if (!$without_shutdown) - ZMBuf::$server->shutdown(); - ZMBuf::$server->stop(); - } - - public static function getHttpCodePage(int $http_code) { - if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) { - return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); - } else return null; - } - - public static function reload() { + public static function reload($delay = 800) { Console::info(Console::setColor("Reloading server...", "gold")); + usleep($delay * 1000); foreach (ZMBuf::get("wait_api", []) as $k => $v) { if ($v["result"] === null) Co::resume($v["coroutine"]); } - foreach (ZMBuf::$server->connections as $v) { - ZMBuf::$server->close($v); + foreach (server()->connections as $v) { + server()->close($v); } DataProvider::saveBuffer(); - ZMBuf::$server->reload(); + Timer::clearAll(); + server()->reload(); } public static function getModInstance($class) { if (!isset(ZMBuf::$instance[$class])) { - Console::debug("Class instance $class not exist, so I created it."); + //Console::debug("Class instance $class not exist, so I created it."); return ZMBuf::$instance[$class] = new $class(); } else { return ZMBuf::$instance[$class]; diff --git a/src/ZM/Utils/ZMWebSocket.php b/src/ZM/Utils/ZMWebSocket.php deleted file mode 100644 index cd17b8aa..00000000 --- a/src/ZM/Utils/ZMWebSocket.php +++ /dev/null @@ -1,106 +0,0 @@ - true], $header = []) { - $this->parse = parse_url($url); - if (!isset($this->parse["host"])) { - Console::warning("ZMRequest: url must contains scheme such as \"ws(s)://\""); - return; - } - if (!isset($this->parse["path"])) $this->parse["path"] = "/"; - $port = $this->parse["port"] ?? (($this->parse["scheme"] ?? "ws") == "wss" ? 443 : 80); - $this->client = new Client($this->parse["host"], $port, (($this->parse["scheme"] ?? "ws") == "wss" ? true : false)); - $this->client->set($set); - if ($header != []) $this->client->setHeaders($header); - $this->is_available = true; - } - - /** - * @return bool - */ - public function upgrade() { - if (!$this->is_available) return false; - $r = $this->client->upgrade($this->parse["path"] . (isset($this->parse["query"]) ? ("?" . $this->parse["query"]) : "")); - if ($r) { - go(function () { - while (true) { - $result = $this->client->recv(60); - if ($result === false) { - if ($this->client->connected === false) { - go(function () { - call_user_func($this->close_func, $this->client); - }); - break; - } - } elseif ($result instanceof Frame) { - go(function () use ($result) { - $this->is_available = false; - call_user_func($this->message_func, $result, $this->client); - }); - } - } - }); - return true; - } - return false; - } - - /** - * @param callable $callable - * @return $this - */ - public function onMessage(callable $callable) { - $this->message_func = $callable; - return $this; - } - - /** - * @param callable $callable - * @return $this - */ - public function onClose(callable $callable) { - $this->close_func = $callable; - return $this; - } -} - -if (!debug_backtrace()) { - go(function () { - require_once __DIR__ . "/../../Framework/Console.php"; - $cli = new ZMWebSocket("ws://127.0.0.1:20001/"); - if (!$cli->is_available) die("Error!\n"); - $cli->onMessage(function (Frame $frame) { - var_dump($frame); - }); - $cli->onClose(function () { - echo "Connection closed.\n"; - }); - if ($cli->upgrade()) { - echo "成功连接!\n"; - } else { - echo "连接失败!\n"; - } - }); -} diff --git a/src/ZM/global_defines.php b/src/ZM/global_defines.php new file mode 100644 index 00000000..eaff3954 --- /dev/null +++ b/src/ZM/global_defines.php @@ -0,0 +1,22 @@ +getConnection()->getName() == 'qq'; +} + +function connectIsDefault() { + return ctx()->getConnection()->getName() == 'default'; +} + +function connectIs($type) { + return ctx()->getConnection()->getName() == $type; +} + +function getAnnotations() { + $s = debug_backtrace()[1]; + //echo json_encode($s, 128|256); + $list = []; + foreach(EventManager::$events as $event => $v) { + foreach($v as $ks => $vs) { + //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL; + if($vs->class == $s["class"] && $vs->method == $s["function"]) { + $list[get_class($vs)][]=$vs; + } + } + } + return $list; +} + function set_coroutine_params($array) { $cid = Co::getCid(); if ($cid == -1) die("Cannot set coroutine params at none coroutine mode."); - if (isset(ZMBuf::$context[$cid])) ZMBuf::$context[$cid] = array_merge(ZMBuf::$context[$cid], $array); - else ZMBuf::$context[$cid] = $array; - foreach (ZMBuf::$context as $c => $v) { - if (!Co::exists($c)) unset(ZMBuf::$context[$c], ZMBuf::$context_class[$c]); + if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array); + else Context::$context[$cid] = $array; + foreach (Context::$context as $c => $v) { + if (!Co::exists($c)) unset(Context::$context[$c], ZMBuf::$context_class[$c]); } } @@ -185,13 +214,13 @@ function context() { function ctx() { $cid = Co::getCid(); $c_class = ZMConfig::get("global", "context_class"); - if (isset(ZMBuf::$context[$cid])) { + if (isset(Context::$context[$cid])) { return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); } else { Console::debug("未找到当前协程的上下文($cid),正在找父进程的上下文"); while (($pcid = Co::getPcid($cid)) !== -1) { $cid = $pcid; - if (isset(ZMBuf::$context[$cid])) return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); + if (isset(Context::$context[$cid])) return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); } return null; } @@ -211,21 +240,23 @@ function zm_resume(int $cid) { Co::resume($cid); } function zm_timer_after($ms, callable $callable) { go(function () use ($ms, $callable) { - ZMUtil::checkWait(); Swoole\Timer::after($ms, $callable); }); } function zm_timer_tick($ms, callable $callable) { go(function () use ($ms, $callable) { - ZMUtil::checkWait(); Console::debug("Adding extra timer tick of " . $ms . " ms"); Swoole\Timer::tick($ms, $callable); }); } +function zm_data_hash($v) { + return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"])); +} + function server() { - return Framework::getServer(); + return Framework::$server; } diff --git a/test/CoroutinePoolTest.php b/test/CoroutinePoolTest.php new file mode 100644 index 00000000..cefced7b --- /dev/null +++ b/test/CoroutinePoolTest.php @@ -0,0 +1,28 @@ +assertTrue(true); + Console::init(4); + CoroutinePool::setSize("default", 2); + CoroutinePool::defaultSize(50); + for ($i = 0; $i < 59; ++$i) { + CoroutinePool::go(function () use ($i) { + //Console::debug("第 $i 个马上进入睡眠..."); + ZMRequest::get("http://localhost:9002/test/ping"); + Console::verbose(strval($i)); + }); + } + } + + public function testA() { + $this->assertTrue(true); + } +} diff --git a/test/LightCacheTest.php b/test/LightCacheTest.php new file mode 100644 index 00000000..95f6b3d4 --- /dev/null +++ b/test/LightCacheTest.php @@ -0,0 +1,21 @@ + 2048, + "max_strlen" => 4096, + "hash_conflict_proportion" => 0.6, + ]); + //LightCache::set("bool", true); + $this->assertEquals(true, LightCache::set("2048", 123, 3)); + $this->assertArrayHasKey("2048", LightCache::getAll()); + sleep(3); + $this->assertArrayNotHasKey("2048", LightCache::getAll()); + } +} diff --git a/test/ZMTest/Mock/mock.php b/test/ZMTest/Mock/mock.php new file mode 100644 index 00000000..a4abe2da --- /dev/null +++ b/test/ZMTest/Mock/mock.php @@ -0,0 +1,2 @@ +parser = new AnnotationParser(); + $this->parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module"); + try { + $this->parser->registerMods(); + } catch (ReflectionException $e) { + throw $e; + } + } + + public function testAnnotation() { + ob_start(); + $gen = $this->parser->generateAnnotationEvents(); + $m = $gen[OnStart::class][0]->method; + $class = $gen[OnStart::class][0]->class; + $c = new $class(); + try { + $c->$m(); + } catch (Exception $e) { + } + $result = ob_get_clean(); + echo $result; + $this->assertStringContainsString("我开始了!", $result); + } + + public function testAnnotation2() { + + foreach ($this->parser->generateAnnotationEvents() as $k => $v) { + foreach ($v as $vs) { + $this->assertTrue($vs->method === null || $vs->method != ''); + $this->assertTrue(strlen($vs->class) > 0); + } + } + } + + public function testAnnotationMap() { + $map = $this->parser->getMiddlewareMap(); + $this->assertContainsEquals("timer", $map[Hello::class]["timer"]); + } + + public function testMiddlewares() { + $wares = $this->parser->getMiddlewares(); + $this->assertArrayHasKey("timer", $wares); + } + + public function testReqMapping() { + $mapping = $this->parser->getReqMapping(); + $this->assertEquals("index", $mapping["method"]); + } +} diff --git a/test/ZMTest/Testing/EventDispatcherTest.php b/test/ZMTest/Testing/EventDispatcherTest.php new file mode 100644 index 00000000..26984427 --- /dev/null +++ b/test/ZMTest/Testing/EventDispatcherTest.php @@ -0,0 +1,51 @@ +addRegisterPath(WORKING_DIR . "/src/Module/", "Module"); + try { + $parser->registerMods(); + } catch (ReflectionException $e) { + throw $e; + } + EventManager::loadEventByParser($parser); + $dispatcher = new EventDispatcher(CQCommand::class); + $dispatcher->setReturnFunction(function ($result) { + echo $result . PHP_EOL; + }); + //$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; }); + $dispatcher->setRuleFunction(function ($v) { return $v->match == "你好"; }); + //$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe"); + ob_start(); + try { + $dispatcher->dispatchEvents(); + } catch (AnnotationException $e) { + } + $r = ob_get_clean(); + echo $r; + $this->assertStringContainsString("你好啊", $r); + } +} diff --git a/test/test.php b/test/test.php new file mode 100644 index 00000000..64918112 --- /dev/null +++ b/test/test.php @@ -0,0 +1,56 @@ + 'MyController')); +$routes = new RouteCollection(); +$routes->add('route_name', $route); + + + +$route = new Route( + '/archive/{month}', // path + array('controller' => 'showArchive', 'asd' => 'feswf'), // default values + array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements + array(), // options + '', // host + array(), // schemes + array() // methods +); + +// ... + +$routes->add('date', $route); + +$route = new Route('/archive/test'); + +$routes->add('qwerty', $route); + +$route = new Route('/'); + +$routes->add('root', $route); + +$context = new RequestContext(); + +$matcher = new UrlMatcher($routes, $context); + +//$parameters = $matcher->match('/test/foo');var_dump($parameters); + +$parameters = $matcher->match('/archive/2012-01'); +var_dump($parameters); +// array( +// 'controller' => 'showArchive', +// 'month' => '2012-01', +// 'subdomain' => 'www', +// '_route' => ... +// ) + +$parameters = $matcher->match('/'); +var_dump($parameters); + +$sub = new RouteCollection(); From da584e0542bd7928dc1fa6b30b2f4712f9fc68ad Mon Sep 17 00:00:00 2001 From: jerry Date: Sat, 3 Oct 2020 23:00:18 +0800 Subject: [PATCH 05/32] initial 2.0.0-a3 commit --- composer.json | 10 +- config/global.php | 7 +- src/Module/Example/Hello.php | 6 +- src/ZM/API/CQAPI.php | 24 +- .../Swoole/{OnStart.php => OnWorkerStart.php} | 4 +- src/ZM/Command/PureHttpCommand.php | 23 +- src/ZM/ConsoleApplication.php | 14 +- src/ZM/Context/Context.php | 11 +- src/ZM/DB/DB.php | 37 +-- src/ZM/Event/CQ/MessageEvent.php | 3 +- src/ZM/Event/EventDispatcher.php | 14 +- src/ZM/Event/EventHandler.php | 13 +- src/ZM/Event/ServerEventHandler.php | 27 +- src/ZM/Framework.php | 15 +- src/ZM/Http/StaticFileHandler.php | 5 +- src/ZM/Module/QQBot.php | 3 +- src/ZM/Store/LightCache.php | 45 ++- src/ZM/Store/Redis/Redis.php | 9 + src/ZM/Store/ZMBuf.php | 55 +--- src/ZM/Utils/DataProvider.php | 50 ---- src/ZM/Utils/HttpUtil.php | 4 +- src/ZM/Utils/Terminal.php | 6 +- src/ZM/Utils/ZMUtil.php | 7 +- test/ZMTest/Mock/Context.php | 256 ++++++++++++++++++ test/ZMTest/Mock/global.php | 114 ++++++++ .../AnnotationParserRegisterTest.php | 6 +- test/ZMTest/Testing/ModuleTest.php | 27 ++ test/test.php | 10 +- 28 files changed, 580 insertions(+), 225 deletions(-) rename src/ZM/Annotation/Swoole/{OnStart.php => OnWorkerStart.php} (80%) create mode 100644 src/ZM/Store/Redis/Redis.php create mode 100644 test/ZMTest/Mock/Context.php create mode 100644 test/ZMTest/Mock/global.php create mode 100644 test/ZMTest/Testing/ModuleTest.php diff --git a/composer.json b/composer.json index 91558a97..85aeec66 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "symfony/console": "^5.1", "symfony/polyfill-ctype": "^1.18", "zhamao/connection-manager": "^1.0", - "zhamao/console": "^1.0", + "zhamao/console": "*@dev", "zhamao/config": "^1.0", "zhamao/request": "^1.0", "symfony/routing": "^5.1" @@ -61,5 +61,11 @@ }, "require-dev": { "phpunit/phpunit": "^9.3" - } + }, + "repositories": [ + { + "type": "path", + "url": "/Users/jerry/project/git-project/zhamao-console" + } + ] } diff --git a/config/global.php b/config/global.php index 8a42c83d..c594efbc 100644 --- a/config/global.php +++ b/config/global.php @@ -1,4 +1,6 @@ set参数 */ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', - 'worker_num' => 2, + 'worker_num' => 8, 'dispatch_mode' => 2, 'max_coroutine' => 30000, //'task_worker_num' => 4, @@ -37,7 +39,8 @@ $config['light_cache'] = [ "status" => true, "size" => 2048, //最多允许储存的条数(需要2的倍数) "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) - "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多) + "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) + "persistence_path" => $config['zm_data']."_cache.json" ]; /** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index bcb467b3..db3b718c 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -2,7 +2,7 @@ namespace Module\Example; -use ZM\Annotation\Swoole\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; use ZM\Annotation\Swoole\OnTick; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; @@ -10,6 +10,8 @@ use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\Middleware; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Swoole\SwooleEvent; +use ZM\Store\LightCache; +use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; /** @@ -72,9 +74,9 @@ class Hello /** * 中间件测试的一个示例函数 * @RequestMapping("/httpTimer") - * @Middleware("timer") */ public function timer() { + ZMBuf::atomic("_tmp_2")->add(1); return "This page is used as testing TimerMiddleware! Do not use it in production."; } diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 6e243c62..751a364a 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -7,6 +7,7 @@ use Co; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Event\EventHandler; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; trait CQAPI @@ -26,27 +27,20 @@ trait CQAPI } - public function processWebsocketAPI($connection, $reply, $function = null) { + public function processWebsocketAPI($connection, $reply, $function = false) { $api_id = ZMBuf::atomic("wait_msg_id")->get(); $reply["echo"] = $api_id; ZMBuf::atomic("wait_msg_id")->add(1); EventHandler::callCQAPISend($reply, $connection); - if (is_callable($function)) { - ZMBuf::appendKey("sent_api", $api_id, [ - "data" => $reply, - "time" => microtime(true), - "func" => $function, - "self_id" => $connection->getOption("connect_id") - ]); - } elseif ($function === true) { - ZMBuf::appendKey("sent_api", $api_id, [ + if ($function === true) { + LightCache::set("sent_api_".$api_id, [ "data" => $reply, "time" => microtime(true), "coroutine" => Co::getuid(), "self_id" => $connection->getOption("connect_id") ]); } else { - ZMBuf::appendKey("sent_api", $api_id, [ + LightCache::set("sent_api_".$api_id, [ "data" => $reply, "time" => microtime(true), "self_id" => $connection->getOption("connect_id") @@ -56,8 +50,8 @@ trait CQAPI if (server()->push($connection->getFd(), json_encode($reply))) { if ($function === true) { Co::suspend(); - $data = ZMBuf::get("sent_api")[$api_id]; - ZMBuf::unsetByValue("sent_api", $reply["echo"]); + $data = LightCache::get("sent_api_".$api_id); + LightCache::unset("sent_api_".$api_id); return isset($data['result']) ? $data['result'] : null; } return true; @@ -69,10 +63,10 @@ trait CQAPI "data" => null, "self_id" => $connection->getOption("connect_id") ]; - $s = ZMBuf::get("sent_api")[$reply["echo"]]; + $s = LightCache::get("sent_api_".$reply["echo"]); if (($s["func"] ?? null) !== null) call_user_func($s["func"], $response, $reply); - ZMBuf::unsetByValue("sent_api", $reply["echo"]); + LightCache::unset("sent_api_".$reply["echo"]); if ($function === true) return $response; return false; } diff --git a/src/ZM/Annotation/Swoole/OnStart.php b/src/ZM/Annotation/Swoole/OnWorkerStart.php similarity index 80% rename from src/ZM/Annotation/Swoole/OnStart.php rename to src/ZM/Annotation/Swoole/OnWorkerStart.php index 3c56ee5f..1eb0f14d 100644 --- a/src/ZM/Annotation/Swoole/OnStart.php +++ b/src/ZM/Annotation/Swoole/OnWorkerStart.php @@ -7,12 +7,12 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class OnStart + * Class OnWorkerStart * @package ZM\Annotation\Swoole * @Annotation * @Target("ALL") */ -class OnStart extends AnnotationBase +class OnWorkerStart extends AnnotationBase { /** * @var int diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php index 94002c4d..d30ce4be 100644 --- a/src/ZM/Command/PureHttpCommand.php +++ b/src/ZM/Command/PureHttpCommand.php @@ -4,6 +4,8 @@ namespace ZM\Command; +use Swoole\Atomic; +use Swoole\Coroutine; use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; @@ -15,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use ZM\Config\ZMConfig; use ZM\Console\Console; +use ZM\Store\ZMBuf; use ZM\Utils\HttpUtil; class PureHttpCommand extends Command @@ -37,9 +40,15 @@ class PureHttpCommand extends Command $host = $input->getOption("host") ?? $global["host"]; $port = $input->getOption("port") ?? $global["port"]; $server = new Server($host, $port); - Console::init(2, $server); + $server->set(ZMConfig::get("global", "swoole")); + Console::init(0, $server); + ZMBuf::$atomics["request"] = []; + for ($i = 0; $i < 32; ++$i) { + ZMBuf::$atomics["request"][$i] = new Atomic(0); + } $index = ["index.html", "index.htm"]; - $server->on("request", function (Request $request, Response $response) use ($input, $index){ + $server->on("request", function (Request $request, Response $response) use ($input, $index, $server) { + ZMBuf::$atomics["request"][$server->worker_id]->add(1); HttpUtil::handleStaticPage( $request->server["request_uri"], $response, @@ -47,10 +56,16 @@ class PureHttpCommand extends Command "document_root" => realpath($input->getArgument('dir') ?? '.'), "document_index" => $index ]); + echo "\r".Coroutine::stats()["coroutine_peak_num"]; }); - $server->on("start", function($server){ - Process::signal(SIGINT, function () use ($server){ + $server->on("start", function ($server) { + Process::signal(SIGINT, function () use ($server) { Console::warning("Server interrupted by keyboard."); + for ($i = 0; $i < 32; ++$i) { + $num = ZMBuf::$atomics["request"][$i]->get(); + if($num != 0) + echo "[$i]: ".$num."\n"; + } $server->shutdown(); $server->stop(); }); diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 9614b11f..67ea073d 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -7,7 +7,6 @@ namespace ZM; use Exception; use Symfony\Component\Console\Command\Command; use TypeError; -use ZM\Command\BuildCommand; use ZM\Command\InitCommand; use ZM\Command\PureHttpCommand; use ZM\Command\RunServerCommand; @@ -25,6 +24,7 @@ class ConsoleApplication extends Application } public function initEnv() { + $this->selfCheck(); $this->addCommands([ new RunServerCommand(), //运行主服务的指令控制器 new InitCommand(), //初始化用的,用于项目初始化和phar初始化 @@ -77,4 +77,16 @@ class ConsoleApplication extends Application die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); } } + + private function selfCheck() { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); + //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); + if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); + //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); + //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); + return true; + } } diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 850a4e43..f7d0ed0e 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -14,6 +14,7 @@ use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; use ZM\API\ZMRobot; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; class Context implements ContextInterface @@ -153,17 +154,17 @@ class Context implements ContextInterface if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { $hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"]; } - ZMBuf::appendKey("wait_api", $api_id, $hang); + LightCache::set("wait_api_".$api_id, $hang); $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { - $r = ZMBuf::get("wait_api")[$api_id] ?? null; - if ($r !== null) { + $r = LightCache::get("wait_api_".$api_id); + if (is_array($r)) { Co::resume($r["coroutine"]); } }); Co::suspend(); - $sess = ZMBuf::get("wait_api")[$api_id]; - ZMBuf::unsetByValue("wait_api", $api_id); + $sess = LightCache::get("wait_api_".$api_id); + LightCache::unset("wait_api_".$api_id); $result = $sess["result"]; if (isset($id)) swoole_timer_clear($id); if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index 6c288471..9163c44d 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -1,4 +1,4 @@ -get(); if ($conn === false) { @@ -76,13 +72,6 @@ class DB ZMBuf::$sql_pool->put($conn); return $result; } catch (DBException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 5) . - "] " . $line . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } Console::warning($e->getMessage()); throw $e; } @@ -96,9 +85,6 @@ class DB * @throws DbException */ public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) { - if (ZMBuf::get("sql_log") === true) { - $starttime = microtime(true); - } Console::debug("MySQL: ".$line." | ". implode(", ", $params)); try { $conn = ZMBuf::$sql_pool->get(); @@ -126,23 +112,9 @@ class DB //echo json_encode(debug_backtrace(), 128 | 256); } ZMBuf::$sql_pool->put($conn); - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } return $ps->fetchAll($fetch_mode); } } catch (DbException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } if(mb_strpos($e->getMessage(), "has gone away") !== false) { zm_sleep(0.2); Console::warning("Gone away of MySQL! retrying!"); @@ -151,13 +123,6 @@ class DB Console::warning($e->getMessage()); throw $e; } catch (PDOException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } if(mb_strpos($e->getMessage(), "has gone away") !== false) { zm_sleep(0.2); Console::warning("Gone away of MySQL! retrying!"); diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php index 734671a5..d128af8e 100644 --- a/src/ZM/Event/CQ/MessageEvent.php +++ b/src/ZM/Event/CQ/MessageEvent.php @@ -85,7 +85,6 @@ class MessageEvent } /** - * @throws AnnotationException * @noinspection PhpRedundantCatchClauseInspection */ public function onActivate() { @@ -122,7 +121,7 @@ class MessageEvent EventDispatcher::interrupt(); }); $r = $dispatcher->dispatchEvents($word); - if ($r === false) return; + if ($r === null) return; //分发CQMessage事件 $msg_dispatcher = new EventDispatcher(CQMessage::class); diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 3ad6121f..2d32c271 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -7,7 +7,6 @@ namespace ZM\Event; use Doctrine\Common\Annotations\AnnotationException; use Exception; use ZM\Annotation\AnnotationBase; -use ZM\Annotation\Interfaces\Rule; use ZM\Exception\InterruptException; use ZM\Utils\ZMUtil; @@ -20,6 +19,9 @@ class EventDispatcher /** @var null|callable */ private $return_func = null; + /** + * @throws InterruptException + */ public static function interrupt() { throw new InterruptException('interrupt'); } @@ -46,12 +48,20 @@ class EventDispatcher } return true; } catch (InterruptException $e) { - return false; + return null; } catch (AnnotationException $e) { return false; } } + /** + * @param AnnotationBase|null $v + * @param null $rule_func + * @param mixed ...$params + * @return bool + * @throws AnnotationException + * @throws InterruptException + */ public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { $q_c = $v->class; $q_f = $v->method; diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php index c3cbfcac..6636d2e5 100644 --- a/src/ZM/Event/EventHandler.php +++ b/src/ZM/Event/EventHandler.php @@ -22,6 +22,7 @@ use ZM\Annotation\Http\MiddlewareClass; use ZM\Context\Context; use ZM\Http\MiddlewareInterface; use ZM\Http\Response; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; @@ -158,8 +159,8 @@ class EventHandler $status = $req["status"]; $retcode = $req["retcode"]; $data = $req["data"]; - if (isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) { - $origin = ZMBuf::get("sent_api")[$req["echo"]]; + if (isset($req["echo"]) && LightCache::isset("sent_api_" . $req["echo"])) { + $origin = LightCache::get("sent_api_" . $req["echo"]); $self_id = $origin["self_id"]; $response = [ "status" => $status, @@ -188,12 +189,12 @@ class EventHandler if (($origin["func"] ?? null) !== null) { call_user_func($origin["func"], $response, $origin["data"]); } elseif (($origin["coroutine"] ?? false) !== false) { - $p = ZMBuf::get("sent_api"); - $p[$req["echo"]]["result"] = $response; - ZMBuf::set("sent_api", $p); + $r = LightCache::get("sent_api_" . $req["echo"]); + $r["result"] = $response; + LightCache::set("sent_api_" . $req["echo"], $r); Co::resume($origin['coroutine']); } - ZMBuf::unsetByValue("sent_api", $req["echo"]); + LightCache::unset("sent_api_" . $req["echo"]); } } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 931641be..4551fedc 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -16,7 +16,7 @@ use Swoole\Process; use Swoole\Timer; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Http\RequestMapping; -use ZM\Annotation\Swoole\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; use ZM\Annotation\Swoole\SwooleEvent; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; @@ -54,21 +54,23 @@ class ServerEventHandler try { Terminal::executeCommand($var, $r); } catch (Exception $e) { - Console::error("Uncaught exception ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + Console::error("Uncaught exception " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"); } catch (Error $e) { - Console::error("Uncaught error ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"); } }); } Process::signal(SIGINT, function () use ($r) { + echo "\r"; Console::warning("Server interrupted by keyboard on Master."); if ((Framework::$server->inotify ?? null) !== null) /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); ZMUtil::stop(); }); - if(Framework::$argv["watch"]) { + if (Framework::$argv["watch"]) { if (extension_loaded('inotify')) { Console::warning("Enabled File watcher, do not use in production."); + /** @noinspection PhpUndefinedFieldInspection */ Framework::$server->inotify = $fd = inotify_init(); $this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd); Event::add($fd, function () use ($fd) { @@ -117,7 +119,7 @@ class ServerEventHandler if ($error["type"] != 0) { Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); } - DataProvider::saveBuffer(); + //DataProvider::saveBuffer(); /** @var Server $server */ if (server() === null) $server->shutdown(); else server()->shutdown(); @@ -125,7 +127,7 @@ class ServerEventHandler Console::info("Worker #{$server->worker_id} 启动中"); Framework::$server = $server; - ZMBuf::resetCache(); //清空变量缓存 + //ZMBuf::resetCache(); //清空变量缓存 //ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 foreach ($server->connections as $v) { $server->close($v); @@ -193,12 +195,12 @@ class ServerEventHandler EventManager::registerTimerTick(); //启动计时器 //ZMBuf::unsetCache("wait_start"); set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); - $dispatcher = new EventDispatcher(OnStart::class); + $dispatcher = new EventDispatcher(OnWorkerStart::class); $dispatcher->setRuleFunction(function ($v) { return server()->worker_id === $v->worker_id || $v->worker_id === -1; }); $dispatcher->dispatchEvents($server, $worker_id); - Console::debug("@OnStart 执行完毕"); + Console::debug("@OnWorkerStart 执行完毕"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); @@ -289,9 +291,9 @@ class ServerEventHandler try { $no_interrupt = $dis->dispatchEvents($request, $response); - if (!$no_interrupt) { + if ($no_interrupt !== null) { $result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params); - if (!$result) { + if ($result === true) { ctx()->setCache("params", $params); $dispatcher = new EventDispatcher(RequestMapping::class); $div = new RequestMapping(); @@ -300,11 +302,13 @@ class ServerEventHandler $div->method = $node["method"]; $div->request_method = $node["request_method"]; $div->class = $node["class"]; + //Console::success("正在执行路由:".$node["method"]); $r = $dispatcher->dispatchEvent($div, null, $params, $request, $response); if (is_string($r) && !$response->isEnd()) $response->end($r); } } if (!$response->isEnd()) { + //Console::warning('返回了404'); HttpUtil::responseCodePage($response, 404); } } catch (Exception $e) { @@ -380,9 +384,10 @@ class ServerEventHandler * @param $fd */ public function onClose($server, $fd) { - Console::debug("Calling Swoole \"close\" event from fd=" . $fd); unset(Context::$context[Co::getCid()]); $conn = ManagerGM::get($fd); + if ($conn === null) return; + Console::debug("Calling Swoole \"close\" event from fd=" . $fd); set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]); $dispatcher = new EventDispatcher(SwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 339676e8..9583aa4b 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -65,8 +65,6 @@ class Framework } catch (ConnectionManager\TableException $e) { die($e->getMessage()); } - //start swoole Framework - $this->selfCheck(); try { self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); $this->server_set = ZMConfig::get("global", "swoole"); @@ -118,6 +116,7 @@ class Framework "size" => 2048, "max_strlen" => 4096, "hash_conflict_proportion" => 0.6, + "persistence_path" => realpath(DataProvider::getDataFolder()."_cache.json") ]); self::$server->start(); } catch (Exception $e) { @@ -127,18 +126,6 @@ class Framework } } - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); - if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); - //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); - //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); - //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); - //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); - return true; - } - /** * 从全局配置文件里读取注入系统事件的类 * @throws ReflectionException diff --git a/src/ZM/Http/StaticFileHandler.php b/src/ZM/Http/StaticFileHandler.php index efc3172e..e31407d0 100644 --- a/src/ZM/Http/StaticFileHandler.php +++ b/src/ZM/Http/StaticFileHandler.php @@ -4,10 +4,9 @@ namespace ZM\Http; +use ZM\Config\ZMConfig; use ZM\Console\Console; -use Framework\ZMBuf; use ZM\Utils\HttpUtil; -use ZM\Utils\ZMUtil; class StaticFileHandler { @@ -23,7 +22,7 @@ class StaticFileHandler } else { if(is_file($full_path)) { $exp = strtolower(pathinfo($full_path)['extension'] ?? "unknown"); - $response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream"); + $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); $response->end(file_get_contents($full_path)); return true; } diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index b50e7706..0de1be68 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -5,7 +5,8 @@ namespace ZM\Module; /** * Class QQBot - * @package ZM\Plugins + * @package ZM\Module + * @ExternalModule("qqbot") */ class QQBot { diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 0de1a01c..964f8f71 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -6,6 +6,7 @@ namespace ZM\Store; use Exception; use Swoole\Table; +use ZM\Console\Console; class LightCache { @@ -14,13 +15,31 @@ class LightCache private static $config = []; + public static $last_error = ''; + public static function init($config) { self::$config = $config; self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]); self::$kv_table->column("value", Table::TYPE_STRING, $config["max_strlen"]); self::$kv_table->column("expire", Table::TYPE_INT); self::$kv_table->column("data_type", Table::TYPE_STRING, 12); - return self::$kv_table->create(); + $result = self::$kv_table->create(); + if ($result === true && isset($config["persistence_path"])) { + $r = json_decode(file_get_contents($config["persistence_path"]), true); + if ($r === null) $r = []; + foreach ($r as $k => $v) { + $write = self::set($k, $v); + Console::debug("Writing LightCache: " . $k); + if ($write === false) { + self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; + return false; + } + } + } + if ($result === false) { + self::$last_error = '系统内存不足,申请失败'; + } + return $result; } /** @@ -56,12 +75,14 @@ class LightCache */ public static function set(string $key, $value, int $expire = -1) { if (self::$kv_table === null) throw new Exception("not initialized LightCache"); - if (is_array($value) || is_int($value)) { + if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); if (strlen($value) >= self::$config["max_strlen"]) return false; $data_type = "json"; } elseif (is_string($value)) { $data_type = ""; + } elseif (is_int($value)) { + $data_type = "int"; } else { throw new Exception("Only can set string, array and int"); } @@ -97,20 +118,31 @@ class LightCache $r = []; $del = []; foreach (self::$kv_table as $k => $v) { - if ($v["expire"] <= time()) { - $del[]=$k; + if ($v["expire"] <= time() && $v["expire"] >= 0) { + $del[] = $k; continue; } $r[$k] = self::parseGet($v); } - foreach($del as $v) { + foreach ($del as $v) { self::unset($v); } return $r; } + public static function savePersistence() { + $r = []; + foreach (self::$kv_table as $k => $v) { + if ($v["expire"] === -2) { + $r[$k] = self::parseGet($v); + } + } + $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); + if($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + } + private static function checkExpire($key) { - if (($expire = self::$kv_table->get($key, "expire")) !== -1) { + if (($expire = self::$kv_table->get($key, "expire")) >= 0) { if ($expire <= time()) { self::$kv_table->del($key); } @@ -120,6 +152,7 @@ class LightCache private static function parseGet($r) { switch ($r["data_type"]) { case "json": + case "int": return json_decode($r["value"], true); case "": default: diff --git a/src/ZM/Store/Redis/Redis.php b/src/ZM/Store/Redis/Redis.php new file mode 100644 index 00000000..b2bce3b0 --- /dev/null +++ b/src/ZM/Store/Redis/Redis.php @@ -0,0 +1,9 @@ += 3) - echo $head; - foreach (self::$buffer_list as $k => $v) { - Console::debug("Saving " . $k . " to " . $v); - self::setJsonData($v, ZMBuf::get($k)); - } - foreach (ZMBuf::$events[OnSave::class] ?? [] as $v) { - $c = $v->class; - $method = $v->method; - $class = new $c(); - Console::debug("Calling @OnSave: $c -> $method"); - $class->$method(); - } - if (Console::getLevel() >= 3) - echo Console::setColor("saved", "blue") . PHP_EOL; - } - public static function getFrameworkLink() { return ZMConfig::get("global", "http_reverse_link"); } - public static function getJsonData(string $string) { - if (!file_exists(self::getDataConfig() . $string)) return []; - return json_decode(file_get_contents(self::getDataConfig() . $string), true); - } - - public static function setJsonData($filename, array $args) { - $pathinfo = pathinfo($filename); - if (!is_dir(self::getDataConfig() . $pathinfo["dirname"])) { - Console::debug("Making Directory: " . self::getDataConfig() . $pathinfo["dirname"]); - mkdir(self::getDataConfig() . $pathinfo["dirname"]); - } - $r = file_put_contents(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING)); - if ($r === false) { - Console::warning("无法保存文件: " . $filename); - } - } - public static function getDataFolder() { return ZM_DATA; } diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php index cc2ab53e..9e5ac510 100644 --- a/src/ZM/Utils/HttpUtil.php +++ b/src/ZM/Utils/HttpUtil.php @@ -52,10 +52,10 @@ class HttpUtil } if (ZMConfig::get("global", "static_file_server")["status"]) { HttpUtil::handleStaticPage($request->server["request_uri"], $response); - return true; + return null; } } - return false; + return !isset($node["route"]) ? false : true; } public static function getHttpCodePage(int $http_code) { diff --git a/src/ZM/Utils/Terminal.php b/src/ZM/Utils/Terminal.php index 70bb7f94..142a429b 100644 --- a/src/ZM/Utils/Terminal.php +++ b/src/ZM/Utils/Terminal.php @@ -5,6 +5,7 @@ namespace ZM\Utils; use Exception; +use Psy\Shell; use Swoole\Event; use ZM\Console\Console; use ZM\Framework; @@ -35,8 +36,9 @@ class Terminal $class->$function_name(); return true; case 'psysh': - if (Framework::$argv["disable-coroutine"]) - eval(\Psy\sh()); + if (Framework::$argv["disable-coroutine"]) { + (new Shell())->run(); + } else Console::error("Only \"--disable-coroutine\" mode can use psysh!!!"); return true; diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 31882c37..8b2e1960 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -30,13 +30,14 @@ class ZMUtil public static function reload($delay = 800) { Console::info(Console::setColor("Reloading server...", "gold")); usleep($delay * 1000); - foreach (ZMBuf::get("wait_api", []) as $k => $v) { - if ($v["result"] === null) Co::resume($v["coroutine"]); + foreach (LightCache::getAll() as $k => $v) { + if (mb_substr($k, 0, 8) == "wait_api") + if ($v["result"] === null) Co::resume($v["coroutine"]); } foreach (server()->connections as $v) { server()->close($v); } - DataProvider::saveBuffer(); + //DataProvider::saveBuffer(); Timer::clearAll(); server()->reload(); } diff --git a/test/ZMTest/Mock/Context.php b/test/ZMTest/Mock/Context.php new file mode 100644 index 00000000..5992f718 --- /dev/null +++ b/test/ZMTest/Mock/Context.php @@ -0,0 +1,256 @@ +set参数 */ +$config['swoole'] = [ + 'log_file' => $config['crash_dir'] . 'swoole_error.log', + 'worker_num' => 8, + 'dispatch_mode' => 2, + 'max_coroutine' => 30000, + //'task_worker_num' => 4, + //'task_enable_coroutine' => true +]; + +/** 轻量字符串缓存,默认开启 */ +$config['light_cache'] = [ + "status" => true, + "size" => 2048, //最多允许储存的条数(需要2的倍数) + "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) + "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多) +]; + +/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ +$config['sql_config'] = [ + 'sql_host' => '', + 'sql_port' => 3306, + 'sql_username' => 'name', + 'sql_database' => 'db_name', + 'sql_password' => '', + 'sql_enable_cache' => true, + 'sql_reset_cache' => '0300', + 'sql_options' => [ + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false + ], + 'sql_no_exception' => false, + 'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6 +]; + +/** CQHTTP连接约定的token */ +$config["access_token"] = ""; + +/** HTTP服务器固定请求头的返回 */ +$config['http_header'] = [ + 'X-Powered-By' => 'zhamao-framework', + 'Content-Type' => 'text/html; charset=utf-8' +]; + +/** HTTP服务器在指定状态码下回复的页面(默认) */ +$config['http_default_code_page'] = [ + '404' => '404.html' +]; + +/** zhamao-framework在框架启动时初始化的atomic们 */ +$config['init_atomics'] = [ + //'custom_atomic_name' => 0, //自定义添加的Atomic +]; + +/** 终端日志显示等级(0-4) */ +$config["info_level"] = 2; + +/** 自动保存计时器的缓存保存时间(秒) */ +$config['auto_save_interval'] = 900; + +/** 上下文接口类 implemented from ContextInterface */ +$config['context_class'] = \ZMTest\Mock\Context::class; + +/** 静态文件访问 */ +$config['static_file_server'] = [ + 'status' => false, + 'document_root' => realpath(__DIR__ . "/../") . '/resources/html', + 'document_index' => [ + 'index.html' + ] +]; + +/** 注册 Swoole Server 事件注解的类列表 */ +$config['server_event_handler_class'] = [ + \ZM\Event\ServerEventHandler::class, +]; + +/** 注册自定义指令的类 */ +$config['command_register_class'] = [ + //\Custom\Command\CustomCommand::class +]; + +/** 服务器启用的外部第三方和内部插件 */ +$config['plugins'] = [ + 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 +]; + +return $config; diff --git a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php index 3ade094b..d09b2d4b 100644 --- a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php +++ b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php @@ -9,7 +9,7 @@ use Module\Example\Hello; use PHPUnit\Framework\TestCase; use ReflectionException; use ZM\Annotation\AnnotationParser; -use ZM\Annotation\Swoole\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; use ZM\Console\Console; class AnnotationParserRegisterTest extends TestCase @@ -34,8 +34,8 @@ class AnnotationParserRegisterTest extends TestCase public function testAnnotation() { ob_start(); $gen = $this->parser->generateAnnotationEvents(); - $m = $gen[OnStart::class][0]->method; - $class = $gen[OnStart::class][0]->class; + $m = $gen[OnWorkerStart::class][0]->method; + $class = $gen[OnWorkerStart::class][0]->class; $c = new $class(); try { $c->$m(); diff --git a/test/ZMTest/Testing/ModuleTest.php b/test/ZMTest/Testing/ModuleTest.php new file mode 100644 index 00000000..a42644f6 --- /dev/null +++ b/test/ZMTest/Testing/ModuleTest.php @@ -0,0 +1,27 @@ +randNum(["随机数", "1", "5"]); + $out = ob_get_clean(); + $this->assertEquals("随机数是:1\n", $out); + } +} diff --git a/test/test.php b/test/test.php index 64918112..13ae52ed 100644 --- a/test/test.php +++ b/test/test.php @@ -25,7 +25,15 @@ $route = new Route( // ... -$routes->add('date', $route); +$routes->add('date', new Route( + '/archive/{month}', // path + array('controller' => 'showArchive', 'asd' => 'feswf'), // default values + array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements + array(), // options + '', // host + array(), // schemes + array() // methods +)); $route = new Route('/archive/test'); From 29fa9d86620c5518319a91efa9cc114f73069a67 Mon Sep 17 00:00:00 2001 From: jerry Date: Tue, 3 Nov 2020 21:02:24 +0800 Subject: [PATCH 06/32] initial 2.0.0-a4 commit --- composer.json | 8 +- config/console_color.json | 11 +- config/global.php | 30 +- src/Module/Example/Hello.php | 17 +- src/ZM/API/CQ.php | 1 - src/ZM/API/CQAPI.php | 60 ++-- src/ZM/Annotation/AnnotationParser.php | 1 + .../{SwooleEvent.php => OnSwooleEvent.php} | 4 +- src/ZM/Command/PureHttpCommand.php | 16 +- src/ZM/Command/RunServerCommand.php | 26 +- src/ZM/ConsoleApplication.php | 1 - src/ZM/Context/Context.php | 34 +- src/ZM/DB/DB.php | 26 +- src/ZM/DB/SelectBody.php | 11 - src/ZM/DB/Table.php | 8 +- src/ZM/Event/CQ/MessageEvent.php | 169 ---------- src/ZM/Event/CQ/MetaEvent.php | 71 ----- src/ZM/Event/CQ/NoticeEvent.php | 95 ------ src/ZM/Event/CQ/RequestEvent.php | 96 ------ src/ZM/Event/Event.php | 11 - src/ZM/Event/EventDispatcher.php | 9 +- src/ZM/Event/EventHandler.php | 293 ------------------ src/ZM/Event/EventManager.php | 6 +- src/ZM/Event/ServerEventHandler.php | 49 ++- src/ZM/Event/Swoole/MessageEvent.php | 97 ------ src/ZM/Event/Swoole/RequestEvent.php | 186 ----------- src/ZM/Event/Swoole/SwooleEventInterface.php | 20 -- src/ZM/Event/Swoole/WSCloseEvent.php | 70 ----- src/ZM/Event/Swoole/WSOpenEvent.php | 106 ------- src/ZM/Exception/NotInitializedException.php | 12 + src/ZM/Framework.php | 39 ++- src/ZM/Module/QQBot.php | 182 ++++++++++- src/ZM/Store/LightCache.php | 66 +++- src/ZM/Store/LightCacheInside.php | 55 ++++ src/ZM/Store/Lock/SpinLock.php | 44 +++ src/ZM/Store/MySQL/SqlPoolStorage.php | 13 + src/ZM/Store/Redis/Redis.php | 9 - src/ZM/Store/Redis/ZMRedis.php | 47 +++ src/ZM/Store/Redis/ZMRedisPool.php | 37 +++ src/ZM/Store/ZMAtomic.php | 38 +++ src/ZM/Store/ZMBuf.php | 36 --- src/ZM/Utils/CoMessage.php | 80 +++++ src/ZM/Utils/ZMUtil.php | 14 +- src/ZM/Utils/terminal_listener.php | 33 -- src/ZM/global_defines.php | 2 - src/ZM/global_functions.php | 11 +- test/LightCacheTest.php | 9 +- test/ZMTest/Mock/global.php | 5 +- 48 files changed, 794 insertions(+), 1470 deletions(-) rename src/ZM/Annotation/Swoole/{SwooleEvent.php => OnSwooleEvent.php} (93%) delete mode 100644 src/ZM/Event/CQ/MessageEvent.php delete mode 100644 src/ZM/Event/CQ/MetaEvent.php delete mode 100644 src/ZM/Event/CQ/NoticeEvent.php delete mode 100644 src/ZM/Event/CQ/RequestEvent.php delete mode 100644 src/ZM/Event/Event.php delete mode 100644 src/ZM/Event/EventHandler.php delete mode 100644 src/ZM/Event/Swoole/MessageEvent.php delete mode 100644 src/ZM/Event/Swoole/RequestEvent.php delete mode 100644 src/ZM/Event/Swoole/SwooleEventInterface.php delete mode 100644 src/ZM/Event/Swoole/WSCloseEvent.php delete mode 100644 src/ZM/Event/Swoole/WSOpenEvent.php create mode 100644 src/ZM/Exception/NotInitializedException.php create mode 100644 src/ZM/Store/LightCacheInside.php create mode 100644 src/ZM/Store/Lock/SpinLock.php create mode 100644 src/ZM/Store/MySQL/SqlPoolStorage.php delete mode 100644 src/ZM/Store/Redis/Redis.php create mode 100644 src/ZM/Store/Redis/ZMRedis.php create mode 100644 src/ZM/Store/Redis/ZMRedisPool.php create mode 100644 src/ZM/Store/ZMAtomic.php create mode 100644 src/ZM/Utils/CoMessage.php delete mode 100644 src/ZM/Utils/terminal_listener.php diff --git a/composer.json b/composer.json index 85aeec66..7b0b8bce 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ ], "require": { "php": ">=7.2", - "swoole/ide-helper": "@dev", "ext-mbstring": "*", "doctrine/annotations": "~1.10", "ext-json": "*", @@ -60,12 +59,17 @@ ] }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^9.3", + "swoole/ide-helper": "@dev" }, "repositories": [ { "type": "path", "url": "/Users/jerry/project/git-project/zhamao-console" + }, + { + "type": "path", + "url": "/Users/jerry/project/git-project/zhamao-lock" } ] } diff --git a/config/console_color.json b/config/console_color.json index e398759f..ffb81891 100644 --- a/config/console_color.json +++ b/config/console_color.json @@ -10,11 +10,20 @@ }, "white-term": { "success": "green", - "info": "black", + "info": "", "warning": "yellow", "error": "red", "verbose": "blue", "debug": "gray", "trace": "gray" + }, + "no-color": { + "success": "", + "info": "", + "warning": "", + "error": "", + "verbose": "", + "debug": "", + "trace": "" } } diff --git a/config/global.php b/config/global.php index c594efbc..83379206 100644 --- a/config/global.php +++ b/config/global.php @@ -27,20 +27,20 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/'; /** 对应swoole的server->set参数 */ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', - 'worker_num' => 8, + 'worker_num' => swoole_cpu_num(), 'dispatch_mode' => 2, - 'max_coroutine' => 30000, + 'max_coroutine' => 300000, //'task_worker_num' => 4, //'task_enable_coroutine' => true ]; /** 轻量字符串缓存,默认开启 */ $config['light_cache'] = [ - "status" => true, - "size" => 2048, //最多允许储存的条数(需要2的倍数) - "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) + "size" => 1024, //最多允许储存的条数(需要2的倍数) + "max_strlen" => 8192, //单行字符串最大长度(需要2的倍数) "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) - "persistence_path" => $config['zm_data']."_cache.json" + "persistence_path" => $config['zm_data']."_cache.json", + 'auto_save_interval' => 900 ]; /** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ @@ -60,8 +60,17 @@ $config['sql_config'] = [ 'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6 ]; -/** CQHTTP连接约定的token */ -$config["access_token"] = ""; +/** Redis连接信息,host留空则启动时不创建Redis连接池 */ +$config['redis_config'] = [ + 'host' => '', + 'port' => 6379, + 'timeout' => 1, + 'db_index' => 0, + 'auth' => '' +]; + +/** onebot连接约定的token */ +$config["access_token"] = ''; /** HTTP服务器固定请求头的返回 */ $config['http_header'] = [ @@ -82,9 +91,6 @@ $config['init_atomics'] = [ /** 终端日志显示等级(0-4) */ $config["info_level"] = 2; -/** 自动保存计时器的缓存保存时间(秒) */ -$config['auto_save_interval'] = 900; - /** 上下文接口类 implemented from ContextInterface */ $config['context_class'] = \ZM\Context\Context::class; @@ -108,7 +114,7 @@ $config['command_register_class'] = [ ]; /** 服务器启用的外部第三方和内部插件 */ -$config['plugins'] = [ +$config['modules'] = [ 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index db3b718c..ad387249 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -2,16 +2,15 @@ namespace Module\Example; +use ZM\Annotation\CQ\CQMetaEvent; +use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\OnWorkerStart; -use ZM\Annotation\Swoole\OnTick; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Annotation\CQ\CQCommand; -use ZM\Annotation\Http\Middleware; use ZM\Annotation\Http\RequestMapping; -use ZM\Annotation\Swoole\SwooleEvent; use ZM\Store\LightCache; -use ZM\Store\ZMBuf; +use ZM\Store\Lock\SpinLock; use ZM\Utils\ZMUtil; /** @@ -23,7 +22,7 @@ class Hello { /** * 在机器人连接后向终端输出信息 - * @SwooleEvent("open",rule="connectIsQQ()") + * @OnSwooleEvent("open",rule="connectIsQQ()") * @param $conn */ public function onConnect(ConnectionObject $conn) { @@ -32,7 +31,7 @@ class Hello /** * 在机器人断开连接后向终端输出信息 - * @SwooleEvent("close",rule="connectIsQQ()") + * @OnSwooleEvent("close",rule="connectIsQQ()") * @param ConnectionObject $conn */ public function onDisconnect(ConnectionObject $conn) { @@ -59,6 +58,7 @@ class Hello * @CQCommand("随机数") * @CQCommand(regexMatch="*从*到*的随机数") * @param $arg + * @return string */ public function randNum($arg) { // 获取第一个数字类型的参数 @@ -68,7 +68,7 @@ class Hello $a = min(intval($num1), intval($num2)); $b = max(intval($num1), intval($num2)); // 回复用户结果 - ctx()->reply("随机数是:" . mt_rand($a, $b)); + return "随机数是:" . mt_rand($a, $b); } /** @@ -76,7 +76,6 @@ class Hello * @RequestMapping("/httpTimer") */ public function timer() { - ZMBuf::atomic("_tmp_2")->add(1); return "This page is used as testing TimerMiddleware! Do not use it in production."; } @@ -101,7 +100,7 @@ class Hello /** * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 - * @SwooleEvent(type="open",rule="connectIsDefault()") + * @OnSwooleEvent(type="open",rule="connectIsDefault()") */ public function closeUnknownConn() { Console::info("Unknown connection , I will close it."); diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php index 346fa8ce..61191446 100644 --- a/src/ZM/API/CQ.php +++ b/src/ZM/API/CQ.php @@ -5,7 +5,6 @@ namespace ZM\API; use ZM\Console\Console; -use ZM\Utils\ZMUtil; class CQ { diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 751a364a..d45540fd 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -6,9 +6,9 @@ namespace ZM\API; use Co; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; -use ZM\Event\EventHandler; -use ZM\Store\LightCache; -use ZM\Store\ZMBuf; +use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; +use ZM\Store\ZMAtomic; trait CQAPI { @@ -28,30 +28,28 @@ trait CQAPI } public function processWebsocketAPI($connection, $reply, $function = false) { - $api_id = ZMBuf::atomic("wait_msg_id")->get(); + $api_id = ZMAtomic::get("wait_msg_id")->add(1); $reply["echo"] = $api_id; - ZMBuf::atomic("wait_msg_id")->add(1); - EventHandler::callCQAPISend($reply, $connection); - if ($function === true) { - LightCache::set("sent_api_".$api_id, [ - "data" => $reply, - "time" => microtime(true), - "coroutine" => Co::getuid(), - "self_id" => $connection->getOption("connect_id") - ]); - } else { - LightCache::set("sent_api_".$api_id, [ - "data" => $reply, - "time" => microtime(true), - "self_id" => $connection->getOption("connect_id") - ]); - } - + //EventHandler::callCQAPISend($reply, $connection); + SpinLock::lock("wait_api"); + $r = LightCacheInside::get("wait_api", "wait_api"); + $r[$api_id] = [ + "data" => $reply, + "time" => microtime(true), + "self_id" => $connection->getOption("connect_id") + ]; + if ($function === true) $r[$api_id]["coroutine"] = Co::getuid(); + LightCacheInside::set("wait_api", "wait_api", $r); + SpinLock::unlock("wait_api"); if (server()->push($connection->getFd(), json_encode($reply))) { if ($function === true) { Co::suspend(); - $data = LightCache::get("sent_api_".$api_id); - LightCache::unset("sent_api_".$api_id); + SpinLock::lock("wait_api"); + $r = LightCacheInside::get("wait_api", "wait_api"); + $data = $r[$api_id]; + unset($r[$api_id]); + LightCacheInside::set("wait_api", "wait_api", $r); + SpinLock::unlock("wait_api"); return isset($data['result']) ? $data['result'] : null; } return true; @@ -63,15 +61,23 @@ trait CQAPI "data" => null, "self_id" => $connection->getOption("connect_id") ]; - $s = LightCache::get("sent_api_".$reply["echo"]); - if (($s["func"] ?? null) !== null) - call_user_func($s["func"], $response, $reply); - LightCache::unset("sent_api_".$reply["echo"]); + SpinLock::lock("wait_api"); + $r = LightCacheInside::get("wait_api", "wait_api"); + unset($r[$reply["echo"]]); + LightCacheInside::set("wait_api", "wait_api", $r); + SpinLock::unlock("wait_api"); if ($function === true) return $response; return false; } } + /** + * @param $connection + * @param $reply + * @param null $function + * @return bool + * @noinspection PhpUnusedParameterInspection + */ public function processHttpAPI($connection, $reply, $function = null) { return false; } diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index e5f40070..6bcb7eaf 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -201,6 +201,7 @@ class AnnotationParser $array[0]['method'] = $method; $array[0]['class'] = $class; $array[0]['request_method'] = $vss->request_method; + $array[0]['route'] = $vss->route; $this->req_mapping = $array; return; } diff --git a/src/ZM/Annotation/Swoole/SwooleEvent.php b/src/ZM/Annotation/Swoole/OnSwooleEvent.php similarity index 93% rename from src/ZM/Annotation/Swoole/SwooleEvent.php rename to src/ZM/Annotation/Swoole/OnSwooleEvent.php index 105125a4..d71f467b 100644 --- a/src/ZM/Annotation/Swoole/SwooleEvent.php +++ b/src/ZM/Annotation/Swoole/OnSwooleEvent.php @@ -10,12 +10,12 @@ use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Interfaces\Rule; /** - * Class SwooleEvent + * Class OnSwooleEvent * @Annotation * @Target("ALL") * @package ZM\Annotation\Swoole */ -class SwooleEvent extends AnnotationBase implements Rule, Level +class OnSwooleEvent extends AnnotationBase implements Rule, Level { /** * @var string diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php index d30ce4be..99c9114c 100644 --- a/src/ZM/Command/PureHttpCommand.php +++ b/src/ZM/Command/PureHttpCommand.php @@ -17,7 +17,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use ZM\Config\ZMConfig; use ZM\Console\Console; -use ZM\Store\ZMBuf; +use ZM\Store\ZMAtomic; use ZM\Utils\HttpUtil; class PureHttpCommand extends Command @@ -28,7 +28,7 @@ class PureHttpCommand extends Command protected function configure() { $this->setDescription("Run a simple http server | 启动一个简单的文件 HTTP 服务器"); $this->setHelp("直接运行可以启动"); - $this->addArgument('dir', InputArgument::OPTIONAL, 'Your directory'); + $this->addArgument('dir', InputArgument::REQUIRED, 'Your directory'); $this->addOption("host", 'H', InputOption::VALUE_REQUIRED, "启动监听地址"); $this->addOption("port", 'P', InputOption::VALUE_REQUIRED, "启动监听地址的端口"); // ... @@ -36,19 +36,23 @@ class PureHttpCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { $tty_width = explode(" ", trim(exec("stty size")))[1]; + if(realpath($input->getArgument('dir') ?? '.') === false) { + $output->writeln("Directory error(".($input->getArgument('dir') ?? '.')."): no such file or directory."); + return self::FAILURE; + } $global = ZMConfig::get("global"); $host = $input->getOption("host") ?? $global["host"]; $port = $input->getOption("port") ?? $global["port"]; $server = new Server($host, $port); $server->set(ZMConfig::get("global", "swoole")); Console::init(0, $server); - ZMBuf::$atomics["request"] = []; + ZMAtomic::$atomics["request"] = []; for ($i = 0; $i < 32; ++$i) { - ZMBuf::$atomics["request"][$i] = new Atomic(0); + ZMAtomic::$atomics["request"][$i] = new Atomic(0); } $index = ["index.html", "index.htm"]; $server->on("request", function (Request $request, Response $response) use ($input, $index, $server) { - ZMBuf::$atomics["request"][$server->worker_id]->add(1); + ZMAtomic::$atomics["request"][$server->worker_id]->add(1); HttpUtil::handleStaticPage( $request->server["request_uri"], $response, @@ -62,7 +66,7 @@ class PureHttpCommand extends Command Process::signal(SIGINT, function () use ($server) { Console::warning("Server interrupted by keyboard."); for ($i = 0; $i < 32; ++$i) { - $num = ZMBuf::$atomics["request"][$i]->get(); + $num = ZMAtomic::$atomics["request"][$i]->get(); if($num != 0) echo "[$i]: ".$num."\n"; } diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 9ffd6614..2f063f8a 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -15,19 +15,23 @@ class RunServerCommand extends Command protected static $defaultName = 'server'; protected function configure() { + $this->setDefinition([ + new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"), + new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"), + new InputOption("log-verbose", null, null, "调整消息等级到verbose (log-level=3)"), + new InputOption("log-info", null, null, "调整消息等级到info (log-level=2)"), + new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"), + new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"), + new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"), + new InputOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"), + new InputOption("disable-coroutine", null, null, "关闭协程Hook"), + new InputOption("daemon", null, null, "以守护进程的方式运行框架"), + new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"), + new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"), + ]); $this->setDescription("Run zhamao-framework | 启动框架"); $this->setHelp("直接运行可以启动"); - $this->addOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"); - $this->addOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"); - $this->addOption("log-verbose", null, null, "调整消息等级到verbose (log-level=3)"); - $this->addOption("log-info", null, null, "调整消息等级到info (log-level=2)"); - $this->addOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"); - $this->addOption("log-error", null, null, "调整消息等级到error (log-level=0)"); - $this->addOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"); - $this->addOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"); - $this->addOption("disable-coroutine", null, null, "关闭协程Hook"); - $this->addOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"); - $this->addOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"); + // ... } diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 67ea073d..dd6bb32b 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -31,7 +31,6 @@ class ConsoleApplication extends Application new PureHttpCommand() ]); //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 - if (LOAD_MODE === 0) define("WORKING_DIR", getcwd()); elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL; diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index f7d0ed0e..f721fdd2 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -14,8 +14,10 @@ use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; use ZM\API\ZMRobot; -use ZM\Store\LightCache; -use ZM\Store\ZMBuf; +use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; +use ZM\Store\ZMAtomic; +use ZM\Utils\CoMessage; class Context implements ContextInterface { @@ -137,12 +139,19 @@ class Context implements ContextInterface * @throws WaitTimeoutException */ public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") { - if ($prompt != "") $this->reply($prompt); if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"])) throw new InvalidArgumentException("协程等待参数缺失"); + + + if ($prompt != "") $this->reply($prompt); + + $r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]); + if($r === false) { + throw new WaitTimeoutException($this, $timeout_prompt); + } + $cid = Co::getuid(); - $api_id = ZMBuf::atomic("wait_msg_id")->get(); - ZMBuf::atomic("wait_msg_id")->add(1); + $api_id = ZMAtomic::get("wait_msg_id")->add(1); $hang = [ "coroutine" => $cid, "user_id" => $this->getData()["user_id"], @@ -154,17 +163,24 @@ class Context implements ContextInterface if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { $hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"]; } - LightCache::set("wait_api_".$api_id, $hang); + SpinLock::lock("wait_api"); + $hw = LightCacheInside::get("wait_api", "wait_api") ?? []; + $hw[$api_id] = $hang; + LightCacheInside::set("wait_api", "wait_api", $hw); + SpinLock::unlock("wait_api"); $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { - $r = LightCache::get("wait_api_".$api_id); + $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null; if (is_array($r)) { Co::resume($r["coroutine"]); } }); Co::suspend(); - $sess = LightCache::get("wait_api_".$api_id); - LightCache::unset("wait_api_".$api_id); + SpinLock::lock("wait_api"); + $hw = LightCacheInside::get("wait_api", "wait_api") ?? []; + $sess = $hw[$api_id]; + unset($hw[$api_id]); + LightCacheInside::set("wait_api", "wait_api", $hw); $result = $sess["result"]; if (isset($id)) swoole_timer_clear($id); if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index 9163c44d..0dd1c35d 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -7,7 +7,7 @@ namespace ZM\DB; use Exception; use ZM\Config\ZMConfig; use ZM\Console\Console; -use ZM\Store\ZMBuf; +use ZM\Store\MySQL\SqlPoolStorage; use PDOException; use PDOStatement; use Swoole\Database\PDOStatementProxy; @@ -35,11 +35,11 @@ class DB * @return Table * @throws DbException */ - public static function table($table_name, $enable_cache = null) { + public static function table($table_name) { if (Table::getTableInstance($table_name) === null) { if (in_array($table_name, self::$table_list)) - return new Table($table_name, $enable_cache ?? ZMConfig::get("global", "sql_config")["sql_enable_cache"]); - elseif(ZMBuf::$sql_pool !== null){ + return new Table($table_name); + elseif(SqlPoolStorage::$sql_pool !== null){ throw new DbException("Table " . $table_name . " not exist in database."); } else { throw new DbException("Database connection not exist or connect failed. Please check sql configuration"); @@ -63,13 +63,13 @@ class DB */ public static function unprepared($line) { try { - $conn = ZMBuf::$sql_pool->get(); + $conn = SqlPoolStorage::$sql_pool->get(); if ($conn === false) { - ZMBuf::$sql_pool->put(null); + SqlPoolStorage::$sql_pool->put(null); throw new DbException("无法连接SQL!" . $line); } $result = $conn->query($line) === false ? false : true; - ZMBuf::$sql_pool->put($conn); + SqlPoolStorage::$sql_pool->put($conn); return $result; } catch (DBException $e) { Console::warning($e->getMessage()); @@ -87,19 +87,19 @@ class DB public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) { Console::debug("MySQL: ".$line." | ". implode(", ", $params)); try { - $conn = ZMBuf::$sql_pool->get(); + $conn = SqlPoolStorage::$sql_pool->get(); if ($conn === false) { - ZMBuf::$sql_pool->put(null); + SqlPoolStorage::$sql_pool->put(null); throw new DbException("无法连接SQL!" . $line); } $ps = $conn->prepare($line); if ($ps === false) { - ZMBuf::$sql_pool->put(null); + SqlPoolStorage::$sql_pool->put(null); throw new DbException("SQL语句查询错误," . $line . ",错误信息:" . $conn->error); } else { if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) { var_dump($ps); - ZMBuf::$sql_pool->put(null); + SqlPoolStorage::$sql_pool->put(null); throw new DbException("语句查询错误!返回的不是 PDOStatement" . $line); } if ($params == []) $result = $ps->execute(); @@ -107,11 +107,11 @@ class DB $result = $ps->execute([$params]); } else $result = $ps->execute($params); if ($result !== true) { - ZMBuf::$sql_pool->put(null); + SqlPoolStorage::$sql_pool->put(null); throw new DBException("语句[$line]错误!" . $ps->errorInfo()[2]); //echo json_encode(debug_backtrace(), 128 | 256); } - ZMBuf::$sql_pool->put($conn); + SqlPoolStorage::$sql_pool->put($conn); return $ps->fetchAll($fetch_mode); } } catch (DbException $e) { diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php index a7c882af..903916db 100644 --- a/src/ZM/DB/SelectBody.php +++ b/src/ZM/DB/SelectBody.php @@ -4,7 +4,6 @@ namespace ZM\DB; -use ZM\Console\Console; use ZM\Exception\DbException; class SelectBody @@ -46,17 +45,7 @@ class SelectBody * @throws DbException */ public function fetchAll($fetch_mode = ZM_DEFAULT_FETCH_MODE) { - if ($this->table->isCacheEnabled()) { - $rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing)); - if (array_key_exists($rr, $this->table->cache)) { - Console::debug('SQL query cached: ' . $rr); - return $this->table->cache[$rr]->getResult(); - } - } $this->execute($fetch_mode); - if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) { - $this->table->cache[$rr] = $this; - } return $this->getResult(); } diff --git a/src/ZM/DB/Table.php b/src/ZM/DB/Table.php index cf85d4cd..939bc8ad 100644 --- a/src/ZM/DB/Table.php +++ b/src/ZM/DB/Table.php @@ -14,10 +14,7 @@ class Table private static $table_instance = []; - private $enable_cache; - - public function __construct($table_name, $enable_cache) { - $this->enable_cache = $enable_cache; + public function __construct($table_name) { $this->table_name = $table_name; self::$table_instance[$table_name] = $this; } @@ -75,5 +72,4 @@ class Table */ public function getTableName() { return $this->table_name; } - public function isCacheEnabled() { return $this->enable_cache; } -} \ No newline at end of file +} diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php deleted file mode 100644 index d128af8e..00000000 --- a/src/ZM/Event/CQ/MessageEvent.php +++ /dev/null @@ -1,169 +0,0 @@ -data = $data; - $this->connection = $conn_or_response; - $this->circle = $circle; - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onBefore() { - $dispatcher = new EventDispatcher(CQBefore::class . "::message"); - $dispatcher->setRuleFunction(function ($v) { - if($v->level < 200) EventDispatcher::interrupt(); - return true; - }); - $dispatcher->setReturnFunction(function($result){ - if(!$result) EventDispatcher::interrupt(); - }); - $dispatcher->dispatchEvents(); - - foreach (ZMBuf::get("wait_api", []) as $k => $v) { - if(zm_data_hash(ctx()->getData()) == $v["hash"]) { - $v["result"] = context()->getData()["message"]; - ZMBuf::appendKey("wait_api", $k, $v); - Co::resume($v["coroutine"]); - return false; - } - if (context()->getData()["user_id"] == $v["user_id"] && - context()->getData()["self_id"] == $v["self_id"] && - context()->getData()["message_type"] == $v["message_type"] && - (context()->getData()[context()->getData()["message_type"] . "_id"] ?? context()->getData()["user_id"]) == - ($v[$v["message_type"] . "_id"] ?? $v["user_id"])) { - $v["result"] = context()->getData()["message"]; - ZMBuf::appendKey("wait_api", $k, $v); - Co::resume($v["coroutine"]); - return false; - } - } - foreach (EventManager::$events[CQBefore::class]["message"] ?? [] as $v) { - if ($v->level >= 200) continue; - $c = $v->class; - if (ctx()->getCache("level") != 0) continue; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if (!$r) context()->setCache("block_continue", true); - } - ); - if (context()->getCache("block_continue") === true) return false; - } - return true; - } - - /** - * @noinspection PhpRedundantCatchClauseInspection - */ - public function onActivate() { - try { - $word = split_explode(" ", str_replace("\r", "", context()->getMessage())); - if (count(explode("\n", $word[0])) >= 2) { - $enter = explode("\n", context()->getMessage()); - $first = split_explode(" ", array_shift($enter)); - $word = array_merge($first, $enter); - foreach ($word as $k => $v) { - $word[$k] = trim($word[$k]); - } - } - - //分发CQCommand事件 - $dispatcher = new EventDispatcher(CQCommand::class); - $dispatcher->setRuleFunction(function ($v) use ($word) { - if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") return false; - elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) && - ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) && - ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType())) - ) { - if (($word[0] != "" && $v->match == $word[0]) || - in_array($word[0], $v->alias) || - ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, ctx()->getMessage())) !== false) || - ($v->fullMatch != "" && (preg_match("/" . $v->fullMatch . "/u", ctx()->getMessage(), $args)) != 0)) { - return true; - } - } - return false; - }); - $dispatcher->setReturnFunction(function ($result) { - if (is_string($result)) ctx()->reply($result); - EventDispatcher::interrupt(); - }); - $r = $dispatcher->dispatchEvents($word); - if ($r === null) return; - - //分发CQMessage事件 - $msg_dispatcher = new EventDispatcher(CQMessage::class); - $msg_dispatcher->setRuleFunction(function ($v) { - return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) && - ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) && - ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) && - ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) && - ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"])); - }); - $msg_dispatcher->setReturnFunction(function ($result) { - if (is_string($result)) ctx()->reply($result); - }); - $msg_dispatcher->dispatchEvents(ctx()->getMessage()); - } catch (WaitTimeoutException $e) { - $e->module->finalReply($e->getMessage()); - } - } - - /** - * 在调用完事件后执行的 - * @throws AnnotationException - */ - public function onAfter() { - context()->setCache("block_continue", null); - foreach (ZMBuf::$events[CQAfter::class]["message"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if (!$r) context()->setCache("block_continue", true); - } - ); - if (context()->getCache("block_continue") === true) return false; - } - return true; - } - - public function hasReply() { - return $this->function_call; - } -} diff --git a/src/ZM/Event/CQ/MetaEvent.php b/src/ZM/Event/CQ/MetaEvent.php deleted file mode 100644 index 78a7671e..00000000 --- a/src/ZM/Event/CQ/MetaEvent.php +++ /dev/null @@ -1,71 +0,0 @@ -data = $data; - $this->connection = $connection; - $this->circle = $circle; - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onBefore() { - foreach (ZMBuf::$events[CQBefore::class]["meta_event"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if(!$r) context()->setCache("block_continue", true); - } - ); - if(context()->getCache("block_continue") === true) return false; - } - return true; - } - - /** - * @throws AnnotationException - */ - public function onActivate() { - try { - $obj = []; - foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) { - /** @var CQMetaEvent $v */ - if ( - ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == $this->data["meta_event_type"])) && - ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) { - $c = $v->class; - if (!isset($obj[$c])) - $obj[$c] = new $c(); - EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { - if (is_string($r)) context()->reply($r); - }); - if (context()->getCache("block_continue") === true) return; - } - } - } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { - $e->module->finalReply($e->getMessage()); - } - } -} diff --git a/src/ZM/Event/CQ/NoticeEvent.php b/src/ZM/Event/CQ/NoticeEvent.php deleted file mode 100644 index 4ff05dcf..00000000 --- a/src/ZM/Event/CQ/NoticeEvent.php +++ /dev/null @@ -1,95 +0,0 @@ -data = $data; - $this->connection = $connection; - $this->circle = $circle; - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onBefore() { - foreach (ZMBuf::$events[CQBefore::class]["notice"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if(!$r) context()->setCache("block_continue", true); - } - ); - if(context()->getCache("block_continue") === true) return false; - } - return true; - } - - /** - * @throws AnnotationException - */ - public function onActivate() { - try { - $obj = []; - foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) { - /** @var CQNotice $v */ - if ( - ($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == $this->data["notice_type"])) && - ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) && - ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) && - ($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) { - $c = $v->class; - if (!isset($obj[$c])) - $obj[$c] = new $c(); - EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { - if (is_string($r)) context()->reply($r); - }); - if (context()->getCache("block_continue") === true) return; - } - } - } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { - $e->module->finalReply($e->getMessage()); - } - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onAfter() { - foreach (ZMBuf::$events[CQAfter::class]["notice"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if(!$r) context()->setCache("block_continue", true); - } - ); - if(context()->getCache("block_continue") === true) return false; - } - return true; - } -} diff --git a/src/ZM/Event/CQ/RequestEvent.php b/src/ZM/Event/CQ/RequestEvent.php deleted file mode 100644 index c600fb77..00000000 --- a/src/ZM/Event/CQ/RequestEvent.php +++ /dev/null @@ -1,96 +0,0 @@ -data = $data; - $this->connection = $connection; - $this->circle = $circle; - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onBefore() { - foreach (ZMBuf::$events[CQBefore::class]["request"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if(!$r) context()->setCache("block_continue", true); - } - ); - if(context()->getCache("block_continue") === true) return false; - } - return true; - } - - /** - * @throws AnnotationException - * @noinspection PhpRedundantCatchClauseInspection - */ - public function onActivate() { - try { - $obj = []; - foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) { - /** @var CQRequest $v */ - if ( - ($v->request_type == '' || ($v->request_type != '' && $v->request_type == $this->data["request_type"])) && - ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) && - ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ($this->data["user_id"] ?? 0))) && - ($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) { - $c = $v->class; - if (!isset($obj[$c])) - $obj[$c] = new $c(); - EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) { - if (is_string($r)) context()->reply($r); - }); - if (context()->getCache("block_continue") === true) return; - } - } - } catch (WaitTimeoutException $e) { - $e->module->finalReply($e->getMessage()); - } - } - - /** - * @return bool - * @throws AnnotationException - */ - public function onAfter() { - foreach (ZMBuf::$events[CQAfter::class]["request"] ?? [] as $v) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["data" => context()->getData(), "connection" => $this->connection], - [], - function ($r) { - if(!$r) context()->setCache("block_continue", true); - } - ); - if(context()->getCache("block_continue") === true) return false; - } - return true; - } -} diff --git a/src/ZM/Event/Event.php b/src/ZM/Event/Event.php deleted file mode 100644 index f78aff33..00000000 --- a/src/ZM/Event/Event.php +++ /dev/null @@ -1,11 +0,0 @@ -class] ?? [] as $v) { + + foreach ((EventManager::$events[$this->class] ?? []) as $v) { + if($this->class == CQMetaEvent::class) { + //eval(BP); + } $result = $this->dispatchEvent($v, $this->rule, ...$params); - if (is_callable($this->return_func)) ($this->return_func)($result); + if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result); } return true; } catch (InterruptException $e) { diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php deleted file mode 100644 index 6636d2e5..00000000 --- a/src/ZM/Event/EventHandler.php +++ /dev/null @@ -1,293 +0,0 @@ -fd); - set_coroutine_params(["server" => $param0, "frame" => $param1, "connection" => $conn]); - try { - (new MessageEvent($param0, $param1))->onActivate()->onAfter(); - } catch (Error $e) { - $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::trace(); - } - break; - case "request": - try { - set_coroutine_params(["request" => $param0, "response" => $param1]); - (new RequestEvent($param0, $param1))->onActivate()->onAfter(); - } catch (Exception $e) { - /** @var Response $param1 */ - $param1->status(500); - Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] . - " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"] - ); - if (!$param1->isEnd()) { - if (ZMConfig::get("global", "debug_mode")) - $param1->end("Internal server error: " . $e->getMessage()); - else - $param1->end("Internal server error."); - } - Console::error("Internal server exception (500), caused by " . get_class($e)); - Console::log($e->getTraceAsString(), "gray"); - } catch (Error $e) { - /** @var Response $param1 */ - $param1->status(500); - Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] . - " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"] - ); - $doc = "Internal server error
"; - $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - if (Console::getLevel() >= 4) $doc .= $error_msg; - if (!$param1->isEnd()) $param1->end($doc); - Console::error("Internal server error (500): " . $error_msg); - Console::log($e->getTraceAsString(), "gray"); - } - break; - case "open": - /** @var Request $param1 */ - set_coroutine_params(["server" => $param0, "request" => $param1, "fd" => $param1->fd]); - try { - (new WSOpenEvent($param0, $param1))->onActivate()->onAfter(); - } catch (Error $e) { - $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::trace(); - } - break; - case "close": - set_coroutine_params(["server" => $param0, "fd" => $param1]); - try { - (new WSCloseEvent($param0, $param1))->onActivate()->onAfter(); - } catch (Error $e) { - $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Fatal error when calling $event_name: " . $error_msg); - Console::trace(); - } - break; - } - //Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold")); - } - - /** - * @param $event_data - * @param $conn_or_response - * @param int $level - * @return bool - * @throws AnnotationException - */ - public static function callCQEvent($event_data, $conn_or_response, int $level = 0) { - ctx()->setCache("level", $level); - if ($level >= 5) { - Console::warning("Recursive call reached " . $level . " times"); - Console::trace(); - return false; - } - $starttime = microtime(true); - switch ($event_data["post_type"]) { - case "message": - $event = new CQ\MessageEvent($event_data, $conn_or_response, $level); - if ($event->onBefore()) $event->onActivate(); - $event->onAfter(); - return $event->hasReply(); - break; - case "notice": - $event = new CQ\NoticeEvent($event_data, $conn_or_response, $level); - if ($event->onBefore()) $event->onActivate(); - $event->onAfter(); - return true; - case "request": - $event = new CQ\RequestEvent($event_data, $conn_or_response, $level); - if ($event->onBefore()) $event->onActivate(); - $event->onAfter(); - return true; - case "meta_event": - $event = new CQ\MetaEvent($event_data, $conn_or_response, $level); - if ($event->onBefore()) $event->onActivate(); - return true; - } - unset($starttime); - return false; - } - - /** - * @param $req - * @throws AnnotationException - */ - public static function callCQResponse($req) { - Console::debug("收到来自API连接的回复:" . json_encode($req, 128 | 256)); - $status = $req["status"]; - $retcode = $req["retcode"]; - $data = $req["data"]; - if (isset($req["echo"]) && LightCache::isset("sent_api_" . $req["echo"])) { - $origin = LightCache::get("sent_api_" . $req["echo"]); - $self_id = $origin["self_id"]; - $response = [ - "status" => $status, - "retcode" => $retcode, - "data" => $data, - "self_id" => $self_id, - "echo" => $req["echo"] - ]; - set_coroutine_params(["cq_response" => $response]); - if (isset(ZMBuf::$events[CQAPIResponse::class][$req["retcode"]])) { - list($c, $method) = ZMBuf::$events[CQAPIResponse::class][$req["retcode"]]; - $class = new $c(["data" => $origin["data"]]); - call_user_func_array([$class, $method], [$origin["data"], $req]); - } - $origin_ctx = ctx()->copy(); - ctx()->setCache("action", $origin["data"]["action"] ?? "unknown"); - ctx()->setData($origin["data"]); - foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) { - if (($v->action == "" || $v->action == ctx()->getCache("action")) && $v->with_result) { - $c = $v->class; - self::callWithMiddleware($c, $v->method, context()->copy(), [ctx()->getCache("action"), $origin["data"]["params"] ?? [], ctx()->getRobotId()]); - if (context()->getCache("block_continue") === true) break; - } - } - set_coroutine_params($origin_ctx); - if (($origin["func"] ?? null) !== null) { - call_user_func($origin["func"], $response, $origin["data"]); - } elseif (($origin["coroutine"] ?? false) !== false) { - $r = LightCache::get("sent_api_" . $req["echo"]); - $r["result"] = $response; - LightCache::set("sent_api_" . $req["echo"], $r); - Co::resume($origin['coroutine']); - } - LightCache::unset("sent_api_" . $req["echo"]); - } - } - - public static function callCQAPISend($reply, ?ConnectionObject $connection) { - $action = $reply["action"] ?? null; - if ($action === null) { - Console::warning("API 激活事件异常!"); - return; - } - if (ctx() === null) $content = []; - else $content = ctx()->copy(); - go(function () use ($action, $reply, $connection, $content) { - set_coroutine_params($content); - context()->setCache("action", $action); - context()->setCache("reply", $reply); - foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) { - if (($v->action == "" || $v->action == $action) && !$v->with_result) { - $c = $v->class; - self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getOption('connect_id')]); - if (context()->getCache("block_continue") === true) break; - } - } - }); - } - - /** - * @param $c - * @param $method - * @param array $class_construct - * @param array $func_args - * @param null $after_call - * @return mixed|null - * @throws AnnotationException - * @throws Exception - */ - public static function callWithMiddleware($c, $method, array $class_construct, array $func_args, $after_call = null) { - $return_value = null; - $plain_class = is_object($c) ? get_class($c) : $c; - if (isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) { - $middlewares = ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method]; - $before_result = true; - $r = []; - foreach ($middlewares as $k => $middleware) { - if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!"); - $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middleware]; - $before = $middleware_obj["class"]; - //var_dump($middleware_obj); - $r[$k] = new $before(); - $r[$k]->class = is_object($c) ? get_class($c) : $c; - $r[$k]->method = $method; - if (isset($middleware_obj["before"])) { - $rs = $middleware_obj["before"]; - $before_result = $r[$k]->$rs(...$func_args); - if ($before_result === false) break; - } - } - if ($before_result) { - try { - if (is_object($c)) $class = $c; - elseif ($class_construct == []) $class = ZMUtil::getModInstance($c); - else $class = new $c($class_construct); - $result = $class->$method(...$func_args); - if (is_callable($after_call)) - $return_value = $after_call($result); - } catch (Exception $e) { - for ($i = count($middlewares) - 1; $i >= 0; --$i) { - $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]]; - if (!isset($middleware_obj["exceptions"])) continue; - foreach ($middleware_obj["exceptions"] as $name => $method) { - if ($e instanceof $name) { - $r[$i]->$method($e); - context()->setCache("block_continue", true); - } - } - if (context()->getCache("block_continue") === true) return $return_value; - } - throw $e; - } - } - for ($i = count($middlewares) - 1; $i >= 0; --$i) { - $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]]; - if (isset($middleware_obj["after"], $r[$i])) { - $r[$i]->{$middleware_obj["after"]}(...$func_args); - } - } - } else { - if (is_object($c)) $class = $c; - elseif ($class_construct == []) $class = ZMUtil::getModInstance($c); - else $class = new $c($class_construct); - $result = call_user_func_array([$class, $method], $func_args); - if (is_callable($after_call)) - $return_value = call_user_func_array($after_call, [$result]); - } - return $return_value; - } -} diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php index e6546abc..3fea9f3e 100644 --- a/src/ZM/Event/EventManager.php +++ b/src/ZM/Event/EventManager.php @@ -11,7 +11,7 @@ use ZM\Annotation\AnnotationBase; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Swoole\OnTick; use ZM\Console\Console; -use ZM\Store\ZMBuf; +use ZM\Store\ZMAtomic; class EventManager { @@ -20,7 +20,7 @@ class EventManager public static $middlewares = []; public static $req_mapping = []; - public static function addEvent($event_name, AnnotationBase $event_obj) { + public static function addEvent($event_name, ?AnnotationBase $event_obj) { self::$events[$event_name][] = $event_obj; } @@ -43,7 +43,7 @@ class EventManager Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method); Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) { set_coroutine_params([]); - if (ZMBuf::atomic("stop_signal")->get() != 0) { + if (ZMAtomic::get("stop_signal")->get() != 0) { Timer::clearAll(); return; } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 4551fedc..a7ebcebd 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -17,7 +17,7 @@ use Swoole\Timer; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Swoole\OnWorkerStart; -use ZM\Annotation\Swoole\SwooleEvent; +use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; use ZM\Console\Console; @@ -33,6 +33,8 @@ use ZM\Exception\DbException; use ZM\Framework; use ZM\Http\Response; use ZM\Module\QQBot; +use ZM\Store\MySQL\SqlPoolStorage; +use ZM\Store\Redis\ZMRedisPool; use ZM\Store\ZMBuf; use ZM\Utils\DataProvider; use ZM\Utils\HttpUtil; @@ -132,9 +134,9 @@ class ServerEventHandler foreach ($server->connections as $v) { $server->close($v); } - if (ZMBuf::$sql_pool !== null) { - ZMBuf::$sql_pool->close(); - ZMBuf::$sql_pool = null; + if (SqlPoolStorage::$sql_pool !== null) { + SqlPoolStorage::$sql_pool->close(); + SqlPoolStorage::$sql_pool = null; } // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器 @@ -175,7 +177,7 @@ class ServerEventHandler } } $sql = ZMConfig::get("global", "sql_config"); - ZMBuf::$sql_pool = new PDOPool((new PDOConfig()) + SqlPoolStorage::$sql_pool = new PDOPool((new PDOConfig()) ->withHost($sql["sql_host"]) ->withPort($sql["sql_port"]) // ->withUnixSocket('/tmp/mysql.sock') @@ -188,6 +190,13 @@ class ServerEventHandler DB::initTableList(); } + // 开箱即用的Redis + $redis = ZMConfig::get("global", "redis_config"); + if($redis !== null && $redis["host"] != "") { + if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n"); + else ZMRedisPool::init($redis); + } + $this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等 //echo json_encode(debug_backtrace(), 128|256); @@ -238,11 +247,11 @@ class ServerEventHandler * @param Frame $frame */ public function onMessage($server, Frame $frame) { - Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd); + Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET); unset(Context::$context[Co::getCid()]); $conn = ManagerGM::get($frame->fd); set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]); - $dispatcher = new EventDispatcher(SwooleEvent::class); + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { return strtolower($v->type) == 'message'; @@ -278,7 +287,7 @@ class ServerEventHandler Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd); set_coroutine_params(["request" => $request, "response" => $response]); - $dis = new EventDispatcher(); + $dis = new EventDispatcher(OnSwooleEvent::class); $dis->setRuleFunction(function ($v) { if ($v->getRule() == '') { return strtolower($v->type) == 'request'; @@ -322,7 +331,7 @@ class ServerEventHandler else $response->end("Internal server error."); } - Console::error("Internal server exception (500), caused by " . get_class($e)); + Console::error("Internal server exception (500), caused by " . get_class($e).": ".$e->getMessage()); Console::log($e->getTraceAsString(), "gray"); } catch (Error $e) { $response->status(500); @@ -354,7 +363,8 @@ class ServerEventHandler ManagerGM::pushConnect($request->fd, $type_conn); $conn = ManagerGM::get($request->fd); set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]); - $dispatcher = new EventDispatcher(SwooleEvent::class); + $conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? ""); + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { return strtolower($v->type) == 'open'; @@ -389,7 +399,7 @@ class ServerEventHandler if ($conn === null) return; Console::debug("Calling Swoole \"close\" event from fd=" . $fd); set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]); - $dispatcher = new EventDispatcher(SwooleEvent::class); + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { return strtolower($v->type) == 'close'; @@ -421,9 +431,13 @@ class ServerEventHandler */ public function onPipeMessage(Server $server, $src_worker_id, $data) { //var_dump($data, $server->worker_id); - unset(Context::$context[Co::getCid()]); + //unset(Context::$context[Co::getCid()]); $data = json_decode($data, true); - switch ($data["action"]) { + switch ($data["action"] ?? '') { + case "resume_ws_message": + $obj = $data["data"]; + Co::resume($obj["coroutine"]); + break; case "stop": Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器'); Timer::clearAll(); @@ -434,6 +448,9 @@ class ServerEventHandler case 'echo': Console::success('接收到来自 #' . $src_worker_id . ' 的消息'); break; + case 'send': + $server->sendMessage(json_encode(["action" => "echo"]), $data["target"]); + break; default: echo $data . PHP_EOL; } @@ -481,17 +498,17 @@ class ServerEventHandler } //加载插件 - $plugins = ZMConfig::get("global", "plugins") ?? []; + $plugins = ZMConfig::get("global", "modules") ?? []; if (!isset($plugins["qqbot"])) $plugins["qqbot"] = true; if ($plugins["qqbot"]) { - $obj = new SwooleEvent(); + $obj = new OnSwooleEvent(); $obj->class = QQBot::class; $obj->method = 'handle'; $obj->type = 'message'; $obj->level = 99999; $obj->rule = 'connectIsQQ()'; - EventManager::addEvent(SwooleEvent::class, $obj); + EventManager::addEvent(OnSwooleEvent::class, $obj); } //TODO: 编写加载外部插件的方式 diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php deleted file mode 100644 index 55fde62f..00000000 --- a/src/ZM/Event/Swoole/MessageEvent.php +++ /dev/null @@ -1,97 +0,0 @@ -server = $server; - $this->frame = $frame; - } - - /** - * @inheritDoc - */ - public function onActivate() { - $conn = ManagerGM::get(context()->getFrame()->fd); - try { - if ($conn->getName() == "qq") { - $data = json_decode(context()->getFrame()->data, true); - if (isset($data["post_type"])) { - set_coroutine_params(["data" => $data, "connection" => $conn]); - ctx()->setCache("level", 0); - Console::debug("Calling CQ Event from fd=" . $conn->getFd()); - EventHandler::callCQEvent($data, ManagerGM::get(context()->getFrame()->fd), 0); - } else{ - set_coroutine_params(["connection" => $conn]); - EventHandler::callCQResponse($data); - } - } - foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { - if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["server" => $this->server, "frame" => $this->frame, "connection" => $conn], - [$conn] - ); - if (context()->getCache("block_continue") === true) break; - } - } - } catch (Exception $e) { - Console::warning("Websocket message event exception: " . (($cs = $e->getMessage()) == "" ? get_class($e) : $cs)); - Console::warning("In ". $e->getFile() . " at line ".$e->getLine()); - } - return $this; - } - - /** - * @inheritDoc - */ - public function onAfter() { - foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { - if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) { - $c = $v->class; - $class = new $c(); - call_user_func_array([$class, $v->method], []); - if (context()->getCache("block_continue") === true) break; - } - } - return $this; - } - - private function parseSwooleRule($v) { - switch (explode(":", $v->rule)[0]) { - case "connectType": //websocket连接类型 - if ($v->callback instanceof Closure) return call_user_func($v->callback, ManagerGM::get($this->frame->fd)); - break; - case "dataEqual": //handle websocket message事件时才能用 - if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data); - break; - } - return true; - } -} diff --git a/src/ZM/Event/Swoole/RequestEvent.php b/src/ZM/Event/Swoole/RequestEvent.php deleted file mode 100644 index f05f7a54..00000000 --- a/src/ZM/Event/Swoole/RequestEvent.php +++ /dev/null @@ -1,186 +0,0 @@ -request = $request; - $this->response = $response; - } - - /** - * @return $this|SwooleEvent - * @throws Exception - */ - public function onActivate() { - foreach (ZMConfig::get("global", "http_header") as $k => $v) { - $this->response->setHeader($k, $v); - } - $uri = $this->request->server["request_uri"]; - Console::verbose($this->request->server["remote_addr"] . " request " . $uri); - $uri = explode("/", $uri); - $uri = array_diff($uri, ["..", "", "."]); - $node = EventManager::$req_mapping; - $params = []; - while (true) { - $r = array_shift($uri); - if ($r === null) break; - if (($cnt = count($node["son"] ?? [])) == 1) { - if (isset($node["param_route"])) { - foreach ($node["son"] as $k => $v) { - if ($v["id"] == $node["param_route"]) { - $node = $v; - $params[mb_substr($v["name"], 1, -1)] = $r; - continue 2; - } - } - } elseif ($node["son"][0]["name"] == $r) { - $node = $node["son"][0]; - continue; - } - } elseif ($cnt >= 1) { - if (isset($node["param_route"])) { - foreach ($node["son"] as $k => $v) { - if ($v["id"] == $node["param_route"]) { - $node = $v; - $params[mb_substr($v["name"], 1, -1)] = $r; - continue 2; - } - } - } - foreach ($node["son"] as $k => $v) { - if ($v["name"] == $r) { - $node = $v; - continue 2; - } - } - } - - if (ZMConfig::get("global", "static_file_server")["status"]) { - $base_dir = ZMConfig::get("global", "static_file_server")["document_root"]; - $base_index = ZMConfig::get("global", "static_file_server")["document_index"]; - $uri = $this->request->server["request_uri"]; - $path = realpath($base_dir . urldecode($uri)); - if ($path !== false) { - if (is_dir($path)) $path = $path . '/'; - $work = realpath(DataProvider::getWorkingDir()) . '/'; - if (strpos($path, $work) !== 0) { - $this->responseStatus(403); - return $this; - } - if (is_dir($path)) { - foreach ($base_index as $vp) { - if (is_file($path . $vp)) { - Console::info("[200] " . $uri . " (static)"); - $exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown"); - $this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); - $this->response->end(file_get_contents($path . $vp)); - return $this; - } - } - } elseif (is_file($path)) { - Console::info("[200] " . $uri . " (static)"); - $exp = strtolower(pathinfo($path)['extension'] ?? "unknown"); - $this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); - $this->response->end(file_get_contents($path)); - return $this; - } - } - } - $this->response->status(404); - $this->response->end(HttpUtil::getHttpCodePage(404)); - return $this; - } - context()->setCache("params", $params); - - if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面 - $c_name = $node["class"]; - EventHandler::callWithMiddleware( - $c_name, - $node["method"], - ["request" => $this->request, "response" => &$this->response, "params" => $params], - [$params], - function ($result) { - if (is_string($result) && !$this->response->isEnd()) $this->response->end($result); - if ($this->response->isEnd()) context()->setCache("block_continue", true); - } - ); - } - foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { - if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) { - $c = $v->class; - EventHandler::callWithMiddleware($c, $v->method, ["request" => $this->request, "response" => $this->response], []); - if (context()->getCache("block_continue") === true) break; - } - } - - if (!$this->response->isEnd()) { - $this->response->status(404); - $this->response->end(HttpUtil::getHttpCodePage(404)); - } - return $this; - } - - /** - * @inheritDoc - */ - public function onAfter() { - foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { - if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) { - $c = $v->class; - $class = new $c(["request" => $this->request, "response" => $this->response]); - call_user_func_array([$class, $v->method], []); - if ($class->block_continue) break; - } - } - return $this; - } - - private function responseStatus(int $int) { - $this->response->status($int); - $this->response->end(); - } - - private function parseSwooleRule($v) { - switch (explode(":", $v->rule)[0]) { - case "containsGet": - case "containsPost": - if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request); - break; - case "containsJson": - $content = $this->request->rawContent(); - $content = json_decode($content, true); - if ($content === null) return false; - if ($v->callback instanceof Closure) return call_user_func($v->callback, $content); - break; - } - return true; - } -} diff --git a/src/ZM/Event/Swoole/SwooleEventInterface.php b/src/ZM/Event/Swoole/SwooleEventInterface.php deleted file mode 100644 index 2918f3ff..00000000 --- a/src/ZM/Event/Swoole/SwooleEventInterface.php +++ /dev/null @@ -1,20 +0,0 @@ -server = $server; - $this->fd = $fd; - } - - /** - * @inheritDoc - * @throws AnnotationException - */ - public function onActivate() { - Console::debug("Websocket closed #{$this->fd}"); - set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ManagerGM::get($this->fd)]); - foreach(ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { - if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) { - $c = $v->class; - EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []); - if(context()->getCache("block_continue") === true) break; - } - } - ManagerGM::popConnect($this->fd); - return $this; - } - - /** - * @inheritDoc - * @throws AnnotationException - */ - public function onAfter() { - foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { - if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) { - $c = $v->class; - EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []); - if(context()->getCache("block_continue") === true) break; - } - } - return $this; - } - - private function parseSwooleRule($v) { - switch (explode(":", $v->rule)[0]) { - case "connectType": //websocket连接类型 - if ($v->callback instanceof Closure) return call_user_func($v->callback, ManagerGM::get($this->fd)); - break; - } - return true; - } -} diff --git a/src/ZM/Event/Swoole/WSOpenEvent.php b/src/ZM/Event/Swoole/WSOpenEvent.php deleted file mode 100644 index b0117435..00000000 --- a/src/ZM/Event/Swoole/WSOpenEvent.php +++ /dev/null @@ -1,106 +0,0 @@ -server = $server; - $this->request = $request; - } - - /** - * @inheritDoc - * @throws AnnotationException - */ - public function onActivate() { - $type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? ""); - $type_conn = $this->getTypeClassName($type); - ManagerGM::pushConnect($this->request->fd, $type_conn); - if ($type_conn == "qq") { - ManagerGM::setName($this->request->fd, "qq"); - $qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? ""; - $self_token = ZMConfig::get("global", "access_token") ?? ""; - if (isset($this->request->header["authorization"])) { - Console::debug($this->request->header["authorization"]); - } - $remote_token = $this->request->get["token"] ?? (isset($this->request->header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : ""); - if ($qq != "" && ($self_token == $remote_token)) { - ManagerGM::setOption($this->request->fd, "connect_id", $qq); - $this->conn = ManagerGM::get($this->request->fd); - } else { - $this->conn = ManagerGM::get($this->request->fd); - Console::warning("connection of CQ has invalid QQ or token!"); - Console::debug("Remote token: " . $remote_token); - } - } else { - $this->conn = ManagerGM::get($this->request->fd); - } - set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]); - foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) { - if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { - $c = $v->class; - EventHandler::callWithMiddleware( - $c, - $v->method, - ["server" => $this->server, "request" => $this->request, "connection" => $this->conn], - [$this->conn] - ); - if (context()->getCache("block_continue") === true) break; - } - } - return $this; - } - - /** - * @inheritDoc - */ - public function onAfter() { - if (!$this->server->exists($this->conn->getFd())) return $this; - foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) { - if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) { - $class = new $v["class"](); - call_user_func_array([$class, $v["method"]], [$this->conn]); - if (context()->getCache("block_continue") === true) break; - } - } - return $this; - } - - private function parseSwooleRule($v) { - switch (explode(":", $v->rule)[0]) { - case "connectType": //websocket连接类型 - if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->conn); - break; - } - return true; - } - - -} diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php new file mode 100644 index 00000000..0f30c04f --- /dev/null +++ b/src/ZM/Exception/NotInitializedException.php @@ -0,0 +1,12 @@ + ZMConfig::get("global", "port"), "log_level" => Console::getLevel(), "version" => ZM_VERSION, - "config" => $args["env"] === null ? 'global.php' : $args["env"], - "working_dir" => DataProvider::getWorkingDir() + "config" => $args["env"] === null ? 'global.php' : $args["env"] ]; if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) { $out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"]; @@ -94,6 +92,7 @@ class Framework if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) { $out["worker_num"] = $num; } + $out["working_dir"] = DataProvider::getWorkingDir(); Console::printProps($out, $tty_width); self::$server->set($this->server_set); @@ -112,12 +111,16 @@ class Framework $asd = get_included_files(); // 注册 Swoole Server 的事件 $this->registerServerEvents(); - LightCache::init(ZMConfig::get("global", "light_cache") ?? [ - "size" => 2048, - "max_strlen" => 4096, - "hash_conflict_proportion" => 0.6, - "persistence_path" => realpath(DataProvider::getDataFolder()."_cache.json") - ]); + $r = ZMConfig::get("global", "light_cache") ?? [ + "size" => 1024, + "max_strlen" => 8192, + "hash_conflict_proportion" => 0.6, + "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), + "auto_save_interval" => 900 + ]; + LightCache::init($r); + LightCacheInside::init(); + SpinLock::init($r["size"]); self::$server->start(); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); @@ -189,12 +192,12 @@ class Framework foreach ($args as $x => $y) { switch ($x) { case 'disable-coroutine': - if($y) { + if ($y) { $coroutine_mode = false; } break; case 'debug-mode': - if ($y) { + if ($y || ZMConfig::get("global", "debug_mode")) { $coroutine_mode = false; $terminal_id = null; Console::warning("You are in debug mode, do not use in production!"); @@ -243,11 +246,7 @@ class Framework if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); } - private function getTtyWidth() { + public static function getTtyWidth() { return explode(" ", trim(exec("stty size")))[1]; } - - public static function getServer() { - return self::$server; - } } diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 0de1be68..f1cd1de2 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -3,6 +3,23 @@ namespace ZM\Module; +use Swoole\Coroutine; +use ZM\Annotation\CQ\CQAPIResponse; +use ZM\Annotation\CQ\CQBefore; +use ZM\Annotation\CQ\CQCommand; +use ZM\Annotation\CQ\CQMessage; +use ZM\Annotation\CQ\CQMetaEvent; +use ZM\Annotation\CQ\CQNotice; +use ZM\Annotation\CQ\CQRequest; +use ZM\Console\Console; +use ZM\Console\TermColor; +use ZM\Event\EventDispatcher; +use ZM\Exception\InterruptException; +use ZM\Exception\WaitTimeoutException; +use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; +use ZM\Utils\CoMessage; + /** * Class QQBot * @package ZM\Module @@ -10,7 +27,170 @@ namespace ZM\Module; */ class QQBot { + /** + * @throws InterruptException + */ public function handle() { - //TODO: 写处理机器人事件的函数 + try { + $data = json_decode(context()->getFrame()->data, true); + if (isset($data["post_type"])) { + //echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL; + set_coroutine_params(["data" => $data]); + ctx()->setCache("level", 0); + //Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd()); + $this->dispatchBeforeEvents($data); // >= 200 的level before在这里执行 + if (false) { + Console::error("哦豁,停下了"); + EventDispatcher::interrupt(); + } + //Console::warning("最上数据包:".json_encode($data)); + $this->dispatchEvents($data); + } else { + $this->dispatchAPIResponse($data); + } + } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { + $e->module->finalReply($e->getMessage()); + } + } + + public function dispatchBeforeEvents($data) { + $before = new EventDispatcher(CQBefore::class); + $before->setRuleFunction(function ($v) use ($data) { + if ($v->level < 200) EventDispatcher::interrupt(); + elseif ($v->cq_event != $data["post_type"]) return false; + return true; + }); + $before->setReturnFunction(function ($result) { + if (!$result) EventDispatcher::interrupt(); + }); + $before->dispatchEvents($data); + } + + private function dispatchEvents($data) { + //Console::warning("最xia数据包:".json_encode($data)); + switch ($data["post_type"]) { + case "message": + $word = split_explode(" ", str_replace("\r", "", context()->getMessage())); + if (count(explode("\n", $word[0])) >= 2) { + $enter = explode("\n", context()->getMessage()); + $first = split_explode(" ", array_shift($enter)); + $word = array_merge($first, $enter); + foreach ($word as $k => $v) { + $word[$k] = trim($word[$k]); + } + } + + //分发CQCommand事件 + $dispatcher = new EventDispatcher(CQCommand::class); + $dispatcher->setRuleFunction(function ($v) use ($word) { + if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") return false; + elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) && + ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) && + ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType())) + ) { + if (($word[0] != "" && $v->match == $word[0]) || + in_array($word[0], $v->alias) || + ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, ctx()->getMessage())) !== false) || + ($v->fullMatch != "" && (preg_match("/" . $v->fullMatch . "/u", ctx()->getMessage(), $args)) != 0)) { + return true; + } + } + return false; + }); + $dispatcher->setReturnFunction(function ($result) { + if (is_string($result)) ctx()->reply($result); + EventDispatcher::interrupt(); + }); + $r = $dispatcher->dispatchEvents($word); + if ($r === null) EventDispatcher::interrupt(); + + //分发CQMessage事件 + $msg_dispatcher = new EventDispatcher(CQMessage::class); + $msg_dispatcher->setRuleFunction(function ($v) { + return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) && + ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) && + ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) && + ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) && + ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"])); + }); + $msg_dispatcher->setReturnFunction(function ($result) { + if (is_string($result)) ctx()->reply($result); + }); + $msg_dispatcher->dispatchEvents(ctx()->getMessage()); + return; + case "meta_event": + //Console::success("当前数据包:".json_encode(ctx()->getData())); + $dispatcher = new EventDispatcher(CQMetaEvent::class); + $dispatcher->setRuleFunction(function (CQMetaEvent $v) { + return ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == ctx()->getData()["meta_event_type"])) && + ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == (ctx()->getData()["sub_type"] ?? ''))); + }); + //eval(BP); + $dispatcher->dispatchEvents(ctx()->getData()); + return; + case "notice": + $dispatcher = new EventDispatcher(CQNotice::class); + $dispatcher->setRuleFunction(function (CQNotice $v) { + return + ($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == ctx()->getData()["notice_type"])) && + ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()["sub_type"])) && + ($v->group_id == '' || ($v->group_id != '' && $v->group_id == ctx()->getData()["group_id"])) && + ($v->operator_id == '' || ($v->operator_id != '' && $v->operator_id == ctx()->getData()["operator_id"])); + }); + $dispatcher->dispatchEvents(ctx()->getData()); + return; + case "request": + $dispatcher = new EventDispatcher(CQRequest::class); + $dispatcher->setRuleFunction(function (CQRequest $v) { + return ($v->request_type == '' || ($v->request_type != '' && $v->request_type == ctx()->getData()['request_type'])) && + ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()['sub_type'])) && + ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getData()["user_id"])) && + ($v->comment == '' || ($v->comment != '' && $v->comment == ctx()->getData()['comment'])); + }); + $dispatcher->dispatchEvents(ctx()->getData()); + return; + } + } + + private function dispatchAPIResponse($req) { + $status = $req["status"]; + $retcode = $req["retcode"]; + $data = $req["data"]; + if (isset($req["echo"]) && is_numeric($req["echo"])) { + $r = LightCacheInside::get("wait_api", "wait_api"); + if (isset($r[$req["echo"]])) { + $origin = $r[$req["echo"]]; + $self_id = $origin["self_id"]; + $response = [ + "status" => $status, + "retcode" => $retcode, + "data" => $data, + "self_id" => $self_id, + "echo" => $req["echo"] + ]; + set_coroutine_params(["cq_response" => $response]); + $dispatcher = new EventDispatcher(CQAPIResponse::class); + $dispatcher->setRuleFunction(function (CQAPIResponse $response) { + return $response->retcode == ctx()->getCQResponse()["retcode"]; + }); + $dispatcher->dispatchEvents($response); + + $origin_ctx = ctx()->copy(); + set_coroutine_params($origin_ctx); + if (($origin["coroutine"] ?? false) !== false) { + SpinLock::lock("wait_api"); + $r = LightCacheInside::get("wait_api", "wait_api"); + $r[$req["echo"]]["result"] = $response; + LightCacheInside::set("wait_api", "wait_api", $r); + SpinLock::unlock("wait_api"); + Coroutine::resume($origin['coroutine']); + } + SpinLock::lock("wait_api"); + $r = LightCacheInside::get("wait_api", "wait_api"); + unset($r[$req["echo"]]); + LightCacheInside::set("wait_api", "wait_api", $r); + SpinLock::unlock("wait_api"); + } + } } } diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 964f8f71..5607c2e3 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -12,6 +12,8 @@ class LightCache { /** @var Table|null */ private static $kv_table = null; + /** @var Table|null */ + private static $kv_lock = null; private static $config = []; @@ -22,17 +24,21 @@ class LightCache self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]); self::$kv_table->column("value", Table::TYPE_STRING, $config["max_strlen"]); self::$kv_table->column("expire", Table::TYPE_INT); - self::$kv_table->column("data_type", Table::TYPE_STRING, 12); + self::$kv_table->column("data_type", Table::TYPE_STRING, 8); $result = self::$kv_table->create(); + self::$kv_lock = new Table($config["size"], $config["hash_conflict_proportion"]); + $result = $result && self::$kv_lock->create(); if ($result === true && isset($config["persistence_path"])) { - $r = json_decode(file_get_contents($config["persistence_path"]), true); - if ($r === null) $r = []; - foreach ($r as $k => $v) { - $write = self::set($k, $v); - Console::debug("Writing LightCache: " . $k); - if ($write === false) { - self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; - return false; + if (file_exists($config["persistence_path"])) { + $r = json_decode(file_get_contents($config["persistence_path"]), true); + if ($r === null) $r = []; + foreach ($r as $k => $v) { + $write = self::set($k, $v, -2); + Console::verbose("Writing LightCache: " . $k); + if ($write === false) { + self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; + return false; + } } } } @@ -83,13 +89,46 @@ class LightCache $data_type = ""; } elseif (is_int($value)) { $data_type = "int"; - } else { + } elseif (is_bool($value)) { + $data_type = "bool"; + $value = json_encode($value); + }else { throw new Exception("Only can set string, array and int"); } try { return self::$kv_table->set($key, [ "value" => $value, - "expire" => $expire != -1 ? $expire + time() : -1, + "expire" => $expire >= 0 ? $expire + time() : $expire, + "data_type" => $data_type + ]); + } catch (Exception $e) { + return false; + } + } + + /** + * @param string $key + * @param $value + * @return bool|mixed + * @throws Exception + */ + public static function update(string $key, $value) { + if (self::$kv_table === null) throw new Exception("not initialized LightCache."); + if (is_array($value)) { + $value = json_encode($value, JSON_UNESCAPED_UNICODE); + if (strlen($value) >= self::$config["max_strlen"]) return false; + $data_type = "json"; + } elseif (is_string($value)) { + $data_type = ""; + } elseif (is_int($value)) { + $data_type = "int"; + } else { + throw new Exception("Only can set string, array and int"); + } + try { + if(self::$kv_table->get($key) === false) return false; + return self::$kv_table->set($key, [ + "value" => $value, "data_type" => $data_type ]); } catch (Exception $e) { @@ -131,14 +170,16 @@ class LightCache } public static function savePersistence() { + if(self::$kv_table === null) return; $r = []; foreach (self::$kv_table as $k => $v) { if ($v["expire"] === -2) { + Console::verbose("Saving " . $k); $r[$k] = self::parseGet($v); } } $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); - if($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); } private static function checkExpire($key) { @@ -153,6 +194,7 @@ class LightCache switch ($r["data_type"]) { case "json": case "int": + case "bool": return json_decode($r["value"], true); case "": default: diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php new file mode 100644 index 00000000..f3838cc7 --- /dev/null +++ b/src/ZM/Store/LightCacheInside.php @@ -0,0 +1,55 @@ +column("value", Table::TYPE_STRING, 65536); + $result = self::$kv_table["wait_api"]->create(); + if ($result === false) { + self::$last_error = '系统内存不足,申请失败'; + return $result; + } + return $result; + } + + public static function get(string $table, string $key) { + if (!isset(self::$kv_table[$table])) throw new Exception("not initialized LightCache"); + $r = self::$kv_table[$table]->get($key); + return $r === false ? null : json_decode($r["value"], true); + } + + /** + * @param string $table + * @param string $key + * @param string|array|int $value + * @return mixed + * @throws Exception + */ + public static function set(string $table, string $key, $value) { + if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + try { + return self::$kv_table[$table]->set($key, [ + "value" => json_encode($value, 256) + ]); + } catch (Exception $e) { + return false; + } + } + + public static function unset(string $table, string $key) { + return self::$kv_table[$table]->del($key); + } +} diff --git a/src/ZM/Store/Lock/SpinLock.php b/src/ZM/Store/Lock/SpinLock.php new file mode 100644 index 00000000..25b85fa4 --- /dev/null +++ b/src/ZM/Store/Lock/SpinLock.php @@ -0,0 +1,44 @@ +column('lock_num', Table::TYPE_INT, 8); + return self::$kv_lock->create(); + } + + public static function lock(string $key) + { + while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { //此资源已经被锁上了 + if(Coroutine::getCid() != -1) System::sleep(self::$delay / 1000); + else usleep(self::$delay * 1000); + } + } + + public static function tryLock(string $key) { + if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { + return false; + } + return true; + } + + public static function unlock(string $key) { + return self::$kv_lock->set($key, ['lock_num' => 0]); + } +} diff --git a/src/ZM/Store/MySQL/SqlPoolStorage.php b/src/ZM/Store/MySQL/SqlPoolStorage.php new file mode 100644 index 00000000..1df61eda --- /dev/null +++ b/src/ZM/Store/MySQL/SqlPoolStorage.php @@ -0,0 +1,13 @@ +get(); + $result = $callable($r); + if (isset($r->wasted)) ZMRedisPool::$pool->put(null); + else ZMRedisPool::$pool->put($r); + return $result; + } + + /** + * ZMRedis constructor. + * @throws NotInitializedException + */ + public function __construct() { + if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized."); + $this->conn = ZMRedisPool::$pool->get(); + } + + /** + * @return Redis + */ + public function get() { + return $this->conn; + } + + public function __destruct() { + if (isset($this->conn->wasted)) ZMRedisPool::$pool->put(null); + else ZMRedisPool::$pool->put($this->conn); + } +} diff --git a/src/ZM/Store/Redis/ZMRedisPool.php b/src/ZM/Store/Redis/ZMRedisPool.php new file mode 100644 index 00000000..2ca1473e --- /dev/null +++ b/src/ZM/Store/Redis/ZMRedisPool.php @@ -0,0 +1,37 @@ +withHost($config['host']) + ->withPort($config['port']) + ->withAuth($config['auth']) + ->withDbIndex($config['db_index']) + ->withTimeout($config['timeout'] ?? 1) + ); + try { + $r = self::$pool->get()->ping('123'); + if(strpos(strtolower($r), "123") !== false) { + Console::debug("成功连接redis连接池!"); + } else { + var_dump($r); + } + } catch (RedisException $e) { + Console::error("Redis init failed! ".$e->getMessage()); + self::$pool = null; + } + } +} diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php new file mode 100644 index 00000000..850a465e --- /dev/null +++ b/src/ZM/Store/ZMAtomic.php @@ -0,0 +1,38 @@ + $v) { + self::$atomics[$k] = new Atomic($v); + } + self::$atomics["stop_signal"] = new Atomic(0); + self::$atomics["wait_msg_id"] = new Atomic(0); + for ($i = 0; $i < 10; ++$i) { + self::$atomics["_tmp_" . $i] = new Atomic(0); + } + } + + +} diff --git a/src/ZM/Store/ZMBuf.php b/src/ZM/Store/ZMBuf.php index 25ef3a90..32c0621a 100755 --- a/src/ZM/Store/ZMBuf.php +++ b/src/ZM/Store/ZMBuf.php @@ -8,46 +8,10 @@ namespace ZM\Store; -use Swoole\Atomic; -use Swoole\Database\PDOPool; -use ZM\Config\ZMConfig; - class ZMBuf { - //读写的缓存数据,需要在worker_num = 1下才能正常使用 - //Swoole SQL连接池,多进程下每个进程一个连接池 - /** @var PDOPool */ - static $sql_pool = null;//保存sql连接池的类 - - /** @var array 事件注解的绑定对 */ public static $events = []; - - // 下面的有用,上面的没用了 - /** @var Atomic[] */ - public static $atomics; public static $instance = []; public static $context_class = []; public static $terminal = null; - - /** - * 初始化atomic计数器 - */ - public static function initAtomic() { - foreach (ZMConfig::get("global", "init_atomics") as $k => $v) { - self::$atomics[$k] = new Atomic($v); - } - self::$atomics["stop_signal"] = new Atomic(0); - self::$atomics["wait_msg_id"] = new Atomic(0); - for($i = 0; $i < 10; ++$i) { - self::$atomics["_tmp_".$i] = new Atomic(0); - } - } - - /** - * @param $name - * @return Atomic|null - */ - public static function atomic($name) { - return self::$atomics[$name] ?? null; - } } diff --git a/src/ZM/Utils/CoMessage.php b/src/ZM/Utils/CoMessage.php new file mode 100644 index 00000000..485229e0 --- /dev/null +++ b/src/ZM/Utils/CoMessage.php @@ -0,0 +1,80 @@ +add(1); + $hang["compare"] = $compare; + $hang["coroutine"] = $cid; + $hang["worker_id"] = server()->worker_id; + $hang["result"] = null; + SpinLock::lock("wait_api"); + $wait = LightCacheInside::get("wait_api", "wait_api"); + $wait[$api_id] = $hang; + LightCacheInside::set("wait_api", "wait_api", $wait); + SpinLock::unlock("wait_api"); + $id = swoole_timer_after($timeout * 1000, function () use ($api_id) { + $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null; + if (is_array($r)) { + Co::resume($r["coroutine"]); + } + }); + Co::suspend(); + SpinLock::lock("wait_api"); + $sess = LightCacheInside::get("wait_api", "wait_api"); + $result = $sess[$api_id]["result"]; + unset($sess[$api_id]); + LightCacheInside::set("wait_api", "wait_api", $sess); + SpinLock::unlock("wait_api"); + if (isset($id)) swoole_timer_clear($id); + if ($result === null) return false; + return $result; + } + + public static function resumeByWS() { + $dat = ctx()->getData(); + $last = null; + SpinLock::lock("wait_api"); + $all = LightCacheInside::get("wait_api", "wait_api") ?? []; + foreach ($all as $k => $v) { + foreach ($v["compare"] as $vs) { + if ($v[$vs] != ($dat[$vs] ?? null)) { + continue 2; + } + } + $last = $k; + } + if($last !== null) { + $all[$last]["result"] = $dat; + LightCacheInside::set("wait_api", "wait_api", $all); + SpinLock::unlock("wait_api"); + if ($all[$last]["worker_id"] != server()->worker_id) { + ZMUtil::sendActionToWorker($all[$k]["worker_id"], "resume_ws_message", $all[$last]); + } else { + Co::resume($all[$last]["coroutine"]); + } + return true; + } else { + SpinLock::unlock("wait_api"); + return false; + } + } +} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 8b2e1960..928369fe 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -10,15 +10,18 @@ use Swoole\Event; use Swoole\Timer; use ZM\Console\Console; use ZM\Store\LightCache; +use ZM\Store\LightCacheInside; +use ZM\Store\ZMAtomic; use ZM\Store\ZMBuf; class ZMUtil { public static function stop() { Console::warning(Console::setColor("Stopping server...", "red")); + LightCache::savePersistence(); if (ZMBuf::$terminal !== null) Event::del(ZMBuf::$terminal); - ZMBuf::atomic("stop_signal")->set(1); + ZMAtomic::get("stop_signal")->set(1); try { LightCache::set('stop', 'OK'); } catch (Exception $e) { @@ -30,9 +33,8 @@ class ZMUtil public static function reload($delay = 800) { Console::info(Console::setColor("Reloading server...", "gold")); usleep($delay * 1000); - foreach (LightCache::getAll() as $k => $v) { - if (mb_substr($k, 0, 8) == "wait_api") - if ($v["result"] === null) Co::resume($v["coroutine"]); + foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) { + if ($v["result"] === null && isset($v["coroutine"])) Co::resume($v["coroutine"]); } foreach (server()->connections as $v) { server()->close($v); @@ -50,4 +52,8 @@ class ZMUtil return ZMBuf::$instance[$class]; } } + + public static function sendActionToWorker($target_id, $action, $data) { + server()->sendMessage(json_encode(["action" => $action, "data" => $data]), $target_id); + } } diff --git a/src/ZM/Utils/terminal_listener.php b/src/ZM/Utils/terminal_listener.php deleted file mode 100644 index 24f72ac8..00000000 --- a/src/ZM/Utils/terminal_listener.php +++ /dev/null @@ -1,33 +0,0 @@ -set(['websocket_mask' => true]); - $client->setHeaders(["x-terminal-id" => $terminal_id, 'x-pid' => posix_getppid()]); - $ret = $client->upgrade("/?type=terminal"); - if ($ret) { - while (true) { - $line = fgets(STDIN); - if ($line !== false) { - $r = $client->push(trim($line)); - if (trim($line) == "reload" || trim($line) == "r" || trim($line) == "stop") { - break; - } - if($r === false) { - echo "Unable to connect framework terminal, connection closed. Trying to reconnect after 5s.\n"; - sleep(5); - goto hello; - } - } else { - break; - } - } - } else { - echo "Unable to connect framework terminal. port: $port\n"; - } -}); - diff --git a/src/ZM/global_defines.php b/src/ZM/global_defines.php index eaff3954..9612da44 100644 --- a/src/ZM/global_defines.php +++ b/src/ZM/global_defines.php @@ -5,10 +5,8 @@ use ZM\Config\ZMConfig; define("ZM_START_TIME", microtime(true)); define("ZM_DATA", ZMConfig::get("global", "zm_data")); define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown"); -define("CONFIG_DIR", ZMConfig::get("global", "config_dir")); define("CRASH_DIR", ZMConfig::get("global", "crash_dir")); @mkdir(ZM_DATA); -@mkdir(CONFIG_DIR); @mkdir(CRASH_DIR); define("CONN_WEBSOCKET", 0); diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index c0d462f5..605c74dd 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -1,5 +1,6 @@ 2048, + "size" => 2, "max_strlen" => 4096, - "hash_conflict_proportion" => 0.6, + "hash_conflict_proportion" => 0, + "persistence_path" => "../composer.json" ]); //LightCache::set("bool", true); $this->assertEquals(true, LightCache::set("2048", 123, 3)); $this->assertArrayHasKey("2048", LightCache::getAll()); sleep(3); $this->assertArrayNotHasKey("2048", LightCache::getAll()); + $this->assertEquals("Apache-2.0", LightCache::get("license")); + $this->assertEquals("zhamao/framework", LightCache::get("name")); + //$this->assertTrue(LightCache::set("storage", "asdasd", -2)); + //LightCache::savePersistence(); } } diff --git a/test/ZMTest/Mock/global.php b/test/ZMTest/Mock/global.php index f92f439c..c2f5915c 100644 --- a/test/ZMTest/Mock/global.php +++ b/test/ZMTest/Mock/global.php @@ -18,9 +18,6 @@ $config['debug_mode'] = false; /** 存放框架内文件数据的目录 */ $config['zm_data'] = realpath(__DIR__ . "/../") . '/zm_data/'; -/** 存放各个模块配置文件的目录 */ -$config['config_dir'] = $config['zm_data'] . 'config/'; - /** 存放崩溃和运行日志的目录 */ $config['crash_dir'] = $config['zm_data'] . 'crash/'; @@ -107,7 +104,7 @@ $config['command_register_class'] = [ ]; /** 服务器启用的外部第三方和内部插件 */ -$config['plugins'] = [ +$config['modules'] = [ 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; From deab5fd921fcfffe61415b84794496388aa02b48 Mon Sep 17 00:00:00 2001 From: jerry Date: Wed, 4 Nov 2020 18:43:50 +0800 Subject: [PATCH 07/32] initial 2.0.0-a5 commit fix waitMessage function fix CQCommand regexMatch and fullMatch it just works --- config/global.php | 4 ++-- src/Module/Example/Hello.php | 13 ++++--------- src/ZM/Annotation/CQ/CQCommand.php | 4 ++-- src/ZM/Context/Context.php | 14 +++++++++----- src/ZM/Context/ContextInterface.php | 3 +-- src/ZM/Event/EventDispatcher.php | 3 --- src/ZM/Event/ServerEventHandler.php | 4 ++++ src/ZM/Module/QQBot.php | 30 +++++++++++++++++------------ src/ZM/Utils/CoMessage.php | 1 + src/ZM/Utils/ZMUtil.php | 3 ++- 10 files changed, 43 insertions(+), 36 deletions(-) diff --git a/config/global.php b/config/global.php index 83379206..07c3d9e9 100644 --- a/config/global.php +++ b/config/global.php @@ -28,7 +28,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/'; $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', 'worker_num' => swoole_cpu_num(), - 'dispatch_mode' => 2, + 'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode 'max_coroutine' => 300000, //'task_worker_num' => 4, //'task_enable_coroutine' => true @@ -37,7 +37,7 @@ $config['swoole'] = [ /** 轻量字符串缓存,默认开启 */ $config['light_cache'] = [ "size" => 1024, //最多允许储存的条数(需要2的倍数) - "max_strlen" => 8192, //单行字符串最大长度(需要2的倍数) + "max_strlen" => 16384, //单行字符串最大长度(需要2的倍数) "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) "persistence_path" => $config['zm_data']."_cache.json", 'auto_save_interval' => 900 diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index ad387249..99c49bab 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -2,15 +2,11 @@ namespace Module\Example; -use ZM\Annotation\CQ\CQMetaEvent; use ZM\Annotation\Swoole\OnSwooleEvent; -use ZM\Annotation\Swoole\OnWorkerStart; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\RequestMapping; -use ZM\Store\LightCache; -use ZM\Store\Lock\SpinLock; use ZM\Utils\ZMUtil; /** @@ -56,15 +52,14 @@ class Hello /** * @CQCommand("随机数") - * @CQCommand(regexMatch="*从*到*的随机数") - * @param $arg + * @CQCommand(pattern="*从*到*的随机数") * @return string */ - public function randNum($arg) { + public function randNum() { // 获取第一个数字类型的参数 - $num1 = ctx()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第一个数字"); + $num1 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第一个数字"); // 获取第二个数字类型的参数 - $num2 = ctx()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第二个数字"); + $num2 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第二个数字"); $a = min(intval($num1), intval($num2)); $b = max(intval($num1), intval($num2)); // 回复用户结果 diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php index 6d468b6b..4f1d09c2 100644 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -18,9 +18,9 @@ class CQCommand extends AnnotationBase implements Level /** @var string */ public $match = ""; /** @var string */ - public $regexMatch = ""; + public $pattern = ""; /** @var string */ - public $fullMatch = ""; + public $regex = ""; /** @var string[] */ public $alias = []; /** @var string */ diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index f721fdd2..c158330f 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -10,6 +10,7 @@ use Swoole\WebSocket\Frame; use swoole_server; use ZM\ConnectionManager\ConnectionObject; use ZM\ConnectionManager\ManagerGM; +use ZM\Console\Console; use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; @@ -142,14 +143,15 @@ class Context implements ContextInterface if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"])) throw new InvalidArgumentException("协程等待参数缺失"); - + Console::debug("==== 开始等待输入 ===="); if ($prompt != "") $this->reply($prompt); $r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]); if($r === false) { throw new WaitTimeoutException($this, $timeout_prompt); } - + return $r["message"]; + /* $cid = Co::getuid(); $api_id = ZMAtomic::get("wait_msg_id")->add(1); $hang = [ @@ -184,18 +186,18 @@ class Context implements ContextInterface $result = $sess["result"]; if (isset($id)) swoole_timer_clear($id); if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); - return $result; + return $result;*/ } /** - * @param $arg * @param $mode * @param $prompt_msg * @return mixed|string * @throws InvalidArgumentException * @throws WaitTimeoutException */ - public function getArgs(&$arg, $mode, $prompt_msg) { + public function getArgs($mode, $prompt_msg) { + $arg = ctx()->getCache("match"); switch ($mode) { case ZM_MATCH_ALL: $p = $arg; @@ -205,6 +207,7 @@ class Context implements ContextInterface foreach ($arg as $k => $v) { if (is_numeric($v)) { array_splice($arg, $k, 1); + ctx()->setCache("match", $arg); return $v; } } @@ -213,6 +216,7 @@ class Context implements ContextInterface if (isset($arg[1])) { $a = $arg[1]; array_splice($arg, 1, 1); + ctx()->setCache("match", $arg); return $a; } else { return $this->waitMessage($prompt_msg); diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index 93d81542..0f76be9d 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -97,12 +97,11 @@ interface ContextInterface public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = ""); /** - * @param $arg * @param $mode * @param $prompt_msg * @return mixed */ - public function getArgs(&$arg, $mode, $prompt_msg); + public function getArgs($mode, $prompt_msg); public function setCache($key, $value); diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 561cacad..5f5339bf 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -45,9 +45,6 @@ class EventDispatcher try { foreach ((EventManager::$events[$this->class] ?? []) as $v) { - if($this->class == CQMetaEvent::class) { - //eval(BP); - } $result = $this->dispatchEvent($v, $this->rule, ...$params); if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result); } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index a7ebcebd..42637178 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -247,6 +247,7 @@ class ServerEventHandler * @param Frame $frame */ public function onMessage($server, Frame $frame) { + Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET); unset(Context::$context[Co::getCid()]); $conn = ManagerGM::get($frame->fd); @@ -264,7 +265,9 @@ class ServerEventHandler } }); try { + $starttime = microtime(true); $dispatcher->dispatchEvents($conn); + Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); @@ -274,6 +277,7 @@ class ServerEventHandler Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg); Console::trace(); } + } /** diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index f1cd1de2..e77d2ff1 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -11,8 +11,6 @@ use ZM\Annotation\CQ\CQMessage; use ZM\Annotation\CQ\CQMetaEvent; use ZM\Annotation\CQ\CQNotice; use ZM\Annotation\CQ\CQRequest; -use ZM\Console\Console; -use ZM\Console\TermColor; use ZM\Event\EventDispatcher; use ZM\Exception\InterruptException; use ZM\Exception\WaitTimeoutException; @@ -39,8 +37,7 @@ class QQBot ctx()->setCache("level", 0); //Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd()); $this->dispatchBeforeEvents($data); // >= 200 的level before在这里执行 - if (false) { - Console::error("哦豁,停下了"); + if (CoMessage::resumeByWS()) { EventDispatcher::interrupt(); } //Console::warning("最上数据包:".json_encode($data)); @@ -70,7 +67,7 @@ class QQBot //Console::warning("最xia数据包:".json_encode($data)); switch ($data["post_type"]) { case "message": - $word = split_explode(" ", str_replace("\r", "", context()->getMessage())); + $word = explodeMsg(str_replace("\r", "", context()->getMessage())); if (count(explode("\n", $word[0])) >= 2) { $enter = explode("\n", context()->getMessage()); $first = split_explode(" ", array_shift($enter)); @@ -82,17 +79,26 @@ class QQBot //分发CQCommand事件 $dispatcher = new EventDispatcher(CQCommand::class); - $dispatcher->setRuleFunction(function ($v) use ($word) { - if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") return false; + $dispatcher->setRuleFunction(function (CQCommand $v) use ($word) { + if ($v->match == "" && $v->pattern == "" && $v->regex == "") return false; elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) && ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) && ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType())) ) { - if (($word[0] != "" && $v->match == $word[0]) || - in_array($word[0], $v->alias) || - ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, ctx()->getMessage())) !== false) || - ($v->fullMatch != "" && (preg_match("/" . $v->fullMatch . "/u", ctx()->getMessage(), $args)) != 0)) { + if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) { + ctx()->setCache("match", $word); return true; + } elseif ($v->pattern != "") { + $match = matchArgs($v->pattern, ctx()->getMessage()); + if($match !== false) { + ctx()->setCache("match", $match); + return true; + } + } elseif ($v->regex != "") { + if(preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) { + ctx()->setCache("match", $word2); + return true; + } } } return false; @@ -101,7 +107,7 @@ class QQBot if (is_string($result)) ctx()->reply($result); EventDispatcher::interrupt(); }); - $r = $dispatcher->dispatchEvents($word); + $r = $dispatcher->dispatchEvents(); if ($r === null) EventDispatcher::interrupt(); //分发CQMessage事件 diff --git a/src/ZM/Utils/CoMessage.php b/src/ZM/Utils/CoMessage.php index 485229e0..637f56f7 100644 --- a/src/ZM/Utils/CoMessage.php +++ b/src/ZM/Utils/CoMessage.php @@ -55,6 +55,7 @@ class CoMessage SpinLock::lock("wait_api"); $all = LightCacheInside::get("wait_api", "wait_api") ?? []; foreach ($all as $k => $v) { + if(!isset($v["compare"])) continue; foreach ($v["compare"] as $vs) { if ($v[$vs] != ($dat[$vs] ?? null)) { continue 2; diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 928369fe..6cf136f4 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -34,8 +34,9 @@ class ZMUtil Console::info(Console::setColor("Reloading server...", "gold")); usleep($delay * 1000); foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) { - if ($v["result"] === null && isset($v["coroutine"])) Co::resume($v["coroutine"]); + if (($v["result"] ?? false) === null && isset($v["coroutine"])) Co::resume($v["coroutine"]); } + LightCacheInside::unset("wait_api", "wait_api"); foreach (server()->connections as $v) { server()->close($v); } From 3b90bf6245e97770d1d60b94f9eba411aaed0a2c Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 8 Nov 2020 19:40:16 +0800 Subject: [PATCH 08/32] initial 2.0.0-b1 commit --- README.md | 2 +- composer.json | 2 +- config/global.php | 4 +- src/Module/Example/Hello.php | 68 ++++++++++++++++++++--------- src/ZM/Context/Context.php | 12 +++-- src/ZM/Context/ContextInterface.php | 2 + src/ZM/Event/ServerEventHandler.php | 22 +++++++--- src/ZM/Module/QQBot.php | 2 +- test/ZMTest/Mock/global.php | 2 +- 9 files changed, 79 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 799ebb42..905b33c3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE) [![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework) [![Banner](https://img.shields.io/badge/CQHTTP-v11-black)]() -[![dev-version](https://img.shields.io/badge/dev--version-v2.0.0--a1-green)]() +[![dev-version](https://img.shields.io/badge/dev--version-v2.0.0--beta1-green)]() [![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid) [![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO) diff --git a/composer.json b/composer.json index 7b0b8bce..29aa2d21 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-a2", + "version": "2.0.0-b1", "authors": [ { "name": "whale", diff --git a/config/global.php b/config/global.php index 07c3d9e9..00bffce1 100644 --- a/config/global.php +++ b/config/global.php @@ -27,7 +27,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/'; /** 对应swoole的server->set参数 */ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', - 'worker_num' => swoole_cpu_num(), + 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量 'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode 'max_coroutine' => 300000, //'task_worker_num' => 4, @@ -115,7 +115,7 @@ $config['command_register_class'] = [ /** 服务器启用的外部第三方和内部插件 */ $config['modules'] = [ - 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 + 'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; return $config; diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 99c49bab..40a41545 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -2,12 +2,13 @@ namespace Module\Example; +use ZM\Annotation\Http\Middleware; use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\RequestMapping; -use ZM\Utils\ZMUtil; +use ZM\Store\Redis\ZMRedis; /** * Class Hello @@ -17,25 +18,38 @@ use ZM\Utils\ZMUtil; class Hello { /** - * 在机器人连接后向终端输出信息 - * @OnSwooleEvent("open",rule="connectIsQQ()") - * @param $conn + * 一个简单的redis连接池使用demo,将下方user_id改为你自己的QQ号即可(为了不被不法分子利用) + * @CQCommand("redis_test",user_id=627577391) */ - public function onConnect(ConnectionObject $conn) { - Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!"); + public function testCase() { + $a = new ZMRedis(); + $redis = $a->get(); + $r1 = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想设置的操作[r/w]"); + switch ($r1) { + case "r": + $k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想读取的键名"); + $result = $redis->get($k); + ctx()->reply("结果:" . $result); + break; + case "w": + $k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的键名"); + $v = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的字符串"); + $result = $redis->set($k, $v); + ctx()->reply("结果:" . ($result ? "成功" : "失败")); + break; + } } /** - * 在机器人断开连接后向终端输出信息 - * @OnSwooleEvent("close",rule="connectIsQQ()") - * @param ConnectionObject $conn + * @CQCommand("我是谁") */ - public function onDisconnect(ConnectionObject $conn) { - Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); + public function whoami() { + $user = ctx()->getRobot()->setCallback(true)->getLoginInfo(); + return "你是" . $user["data"]["nickname"] . ",QQ号是" . $user["data"]["user_id"]; } /** - * 向机器人发送"你好",即可回复这句话 + * 向机器人发送"你好啊",也可回复这句话 * @CQCommand(match="你好",alias={"你好啊","你是谁"}) */ public function hello() { @@ -43,14 +57,9 @@ class Hello } /** - * @CQCommand(".reload") - */ - public function reload() { - context()->reply("reloading..."); - ZMUtil::reload(); - } - - /** + * 一个简单随机数的功能demo + * 问法1:随机数 1 20 + * 问法2:从1到20的随机数 * @CQCommand("随机数") * @CQCommand(pattern="*从*到*的随机数") * @return string @@ -69,6 +78,7 @@ class Hello /** * 中间件测试的一个示例函数 * @RequestMapping("/httpTimer") + * @Middleware("timer") */ public function timer() { return "This page is used as testing TimerMiddleware! Do not use it in production."; @@ -93,6 +103,24 @@ class Hello return "Your name: {$param["name"]}"; } + /** + * 在机器人连接后向终端输出信息 + * @OnSwooleEvent("open",rule="connectIsQQ()") + * @param $conn + */ + public function onConnect(ConnectionObject $conn) { + Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!"); + } + + /** + * 在机器人断开连接后向终端输出信息 + * @OnSwooleEvent("close",rule="connectIsQQ()") + * @param ConnectionObject $conn + */ + public function onDisconnect(ConnectionObject $conn) { + Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); + } + /** * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 * @OnSwooleEvent(type="open",rule="connectIsDefault()") diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index c158330f..c5cc0c0f 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -5,6 +5,7 @@ namespace ZM\Context; use Co; +use Exception; use Swoole\Http\Request; use Swoole\WebSocket\Frame; use swoole_server; @@ -15,9 +16,6 @@ use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; use ZM\API\ZMRobot; -use ZM\Store\LightCacheInside; -use ZM\Store\Lock\SpinLock; -use ZM\Store\ZMAtomic; use ZM\Utils\CoMessage; class Context implements ContextInterface @@ -146,7 +144,11 @@ class Context implements ContextInterface Console::debug("==== 开始等待输入 ===="); if ($prompt != "") $this->reply($prompt); - $r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]); + try { + $r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]); + } catch (Exception $e) { + $r = false; + } if($r === false) { throw new WaitTimeoutException($this, $timeout_prompt); } @@ -231,4 +233,6 @@ class Context implements ContextInterface } public function copy() { return self::$context[$this->cid]; } + + public function getOption() { return self::getCache("match"); } } diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index 0f76be9d..94689d37 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -114,4 +114,6 @@ interface ContextInterface public function cloneFromParent(); public function copy(); + + public function getOption(); } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 42637178..6783e1be 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -134,10 +134,7 @@ class ServerEventHandler foreach ($server->connections as $v) { $server->close($v); } - if (SqlPoolStorage::$sql_pool !== null) { - SqlPoolStorage::$sql_pool->close(); - SqlPoolStorage::$sql_pool = null; - } + // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器 /*if ($server->worker_id === 0) { @@ -163,6 +160,10 @@ class ServerEventHandler }*/ //TODO: 单独抽出来MySQL和Redis连接池 if (ZMConfig::get("global", "sql_config")["sql_host"] != "") { + if (SqlPoolStorage::$sql_pool !== null) { + SqlPoolStorage::$sql_pool->close(); + SqlPoolStorage::$sql_pool = null; + } Console::info("新建SQL连接池中"); ob_start(); phpinfo(); @@ -503,9 +504,9 @@ class ServerEventHandler //加载插件 $plugins = ZMConfig::get("global", "modules") ?? []; - if (!isset($plugins["qqbot"])) $plugins["qqbot"] = true; + if (!isset($plugins["onebot"])) $plugins["onebot"] = true; - if ($plugins["qqbot"]) { + if ($plugins["onebot"]) { $obj = new OnSwooleEvent(); $obj->class = QQBot::class; $obj->method = 'handle'; @@ -516,7 +517,7 @@ class ServerEventHandler } //TODO: 编写加载外部插件的方式 - //$this->loadExternalModules(); + $this->loadExternalModules($plugins); } private function addWatcher($maindir, $fd) { @@ -530,4 +531,11 @@ class ServerEventHandler } } } + + private function loadExternalModules($plugins) { + foreach ($plugins as $k => $v) { + if ($k == "onebot") continue; + + } + } } diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index e77d2ff1..13f75c7d 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -21,7 +21,7 @@ use ZM\Utils\CoMessage; /** * Class QQBot * @package ZM\Module - * @ExternalModule("qqbot") + * @ExternalModule("onebot") */ class QQBot { diff --git a/test/ZMTest/Mock/global.php b/test/ZMTest/Mock/global.php index c2f5915c..e076b681 100644 --- a/test/ZMTest/Mock/global.php +++ b/test/ZMTest/Mock/global.php @@ -105,7 +105,7 @@ $config['command_register_class'] = [ /** 服务器启用的外部第三方和内部插件 */ $config['modules'] = [ - 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 + 'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; return $config; From 7fe405d0afcb3d56a29d30d516eae27d41bb08fd Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 8 Nov 2020 20:14:08 +0800 Subject: [PATCH 09/32] initial 2.0.0-b2 commit --- Dockerfile | 19 +++++++++---------- README.md | 2 +- composer.json | 6 +++--- docker_mixed/Dockerfile | 17 ++++++++--------- 4 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8130fa82..c1cc7cfb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,16 +8,15 @@ ENV LANGUAGE C.UTF-8 RUN apt-get update && apt-get install -y software-properties-common tzdata RUN dpkg-reconfigure -f noninteractive tzdata VOLUME ["/app/zhamao-framework/"] -RUN add-apt-repository ppa:ondrej/php && \ - apt-get update && \ - apt-get install php php-dev php-mbstring gcc make openssl \ - php-mbstring php-json php-curl php-mysql -y && \ - apt-get install wget composer -y && \ - wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ - tar -zxvf v4.5.0.tar.gz && \ - cd swoole-src-4.5.0/ && \ - phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ - (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) +RUN add-apt-repository ppa:ondrej/php && + apt-get update && \ + apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && \ + apt-get install wget composer -y && \ + wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ + tar -zxvf v4.5.0.tar.gz && \ + cd swoole-src-4.5.0/ && \ + phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ + (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) ADD . /app/zhamao-framework diff --git a/README.md b/README.md index 905b33c3..36f322f1 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ **2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。** -[![dev-version2](https://img.shields.io/badge/process-30%25-green)]() +[![dev-version2](https://img.shields.io/badge/process-95%25-green)]() diff --git a/composer.json b/composer.json index 29aa2d21..351e3bdd 100644 --- a/composer.json +++ b/composer.json @@ -29,10 +29,10 @@ "psy/psysh": "@stable", "symfony/console": "^5.1", "symfony/polyfill-ctype": "^1.18", - "zhamao/connection-manager": "^1.0", + "zhamao/connection-manager": "*@dev", "zhamao/console": "*@dev", - "zhamao/config": "^1.0", - "zhamao/request": "^1.0", + "zhamao/config": "*@dev", + "zhamao/request": "*@dev", "symfony/routing": "^5.1" }, "suggest": { diff --git a/docker_mixed/Dockerfile b/docker_mixed/Dockerfile index b85a7dea..ca2291e8 100644 --- a/docker_mixed/Dockerfile +++ b/docker_mixed/Dockerfile @@ -2,15 +2,14 @@ FROM richardchien/cqhttp:latest RUN apt-get update && apt-get install -y software-properties-common tzdata RUN dpkg-reconfigure -f noninteractive tzdata RUN add-apt-repository ppa:ondrej/php && \ - apt-get update && \ - apt-get install php php-dev php-mbstring gcc make openssl \ - php-mbstring php-json php-curl php-mysql -y && \ - apt-get install wget composer -y && \ - wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ - tar -zxvf v4.5.0.tar.gz && \ - cd swoole-src-4.5.0/ && \ - phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ - (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) + apt-get update && \ + apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && \ + apt-get install wget composer -y && \ + wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ + tar -zxvf v4.5.0.tar.gz && \ + cd swoole-src-4.5.0/ && \ + phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ + (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) ADD start.sh /home/user/start.sh RUN chown user:user /home/user/start.sh && chmod +x /home/user/start.sh ADD https://github.com/zhamao-robot/zhamao-framework/archive/master.zip /home/user/master.zip From c460b37d142c9fcd708c6484cd4be09e25785713 Mon Sep 17 00:00:00 2001 From: jerry Date: Tue, 10 Nov 2020 23:16:55 +0800 Subject: [PATCH 10/32] add mkdocs documents --- docs/FAQ.md | 1 + docs/assets/extra.css | 9 +++++++++ docs/assets/logos.png | Bin 0 -> 28570 bytes docs/component/index.md | 1 + docs/event/index.md | 1 + docs/guide/OneBot实例.md | 23 ++++++++++++++++++++++ docs/guide/index.md | 17 +++++++++++++++++ docs/guide/我需要做什么.md | 3 +++ docs/index.md | 29 ++++++++++++++++++++++++++++ docs/javascripts/config.js | 2 ++ docs/test.md | 25 ++++++++++++++++++++++++ docs/update.md | 1 + mkdocs.yml | 38 +++++++++++++++++++++++++++++++++++++ 13 files changed, 150 insertions(+) create mode 100644 docs/FAQ.md create mode 100644 docs/assets/extra.css create mode 100644 docs/assets/logos.png create mode 100644 docs/component/index.md create mode 100644 docs/event/index.md create mode 100644 docs/guide/OneBot实例.md create mode 100644 docs/guide/index.md create mode 100644 docs/guide/我需要做什么.md create mode 100644 docs/index.md create mode 100644 docs/javascripts/config.js create mode 100644 docs/test.md create mode 100644 docs/update.md create mode 100644 mkdocs.yml diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000..4514b4c1 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1 @@ +# FAQ diff --git a/docs/assets/extra.css b/docs/assets/extra.css new file mode 100644 index 00000000..4b4b3cee --- /dev/null +++ b/docs/assets/extra.css @@ -0,0 +1,9 @@ +.md-header-nav__button.md-logo { + padding: .2rem; + margin: .2rem; +} + +.md-header-nav__button.md-logo img, .md-header-nav__button.md-logo svg { + width: 1.6rem; + height: 1.6rem; +} \ No newline at end of file diff --git a/docs/assets/logos.png b/docs/assets/logos.png new file mode 100644 index 0000000000000000000000000000000000000000..c3c668dd80f9e92a060789aebd1f50c541528bd5 GIT binary patch literal 28570 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 Date: Sun, 22 Nov 2020 19:18:23 +0800 Subject: [PATCH 11/32] update to 2.0.0-b3 --- .gitignore | 4 ++++ Dockerfile | 29 ++--------------------------- SECURITY.md | 5 +++-- composer.json | 6 +----- docker_mixed/Dockerfile | 17 ----------------- docker_mixed/start.sh | 6 ------ docs/guide/我需要做什么.md | 3 --- docs/index.md | 33 +++++++++++++++++++++++++-------- docs/javascripts/config.js | 7 +++++++ mkdocs.yml | 11 ++++++++++- src/ZM/Utils/Terminal.php | 3 +-- 11 files changed, 53 insertions(+), 71 deletions(-) delete mode 100644 docker_mixed/Dockerfile delete mode 100644 docker_mixed/start.sh delete mode 100644 docs/guide/我需要做什么.md diff --git a/.gitignore b/.gitignore index 8823c742..c44fa39b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ zm.json /zm_data/ composer.lock /resources/server.phar +/distribute/ +/bin/.phpunit.result.cache +/resources/zhamao.service +.phpunit.result.cache diff --git a/Dockerfile b/Dockerfile index c1cc7cfb..0225d218 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,3 @@ -FROM ubuntu:18.04 -WORKDIR /app/ -RUN echo "Asia/Shanghai" > /etc/timezone -ENV LANG C.UTF_8 -ENV LC_ALL C.UTF-8 -ENV LANGUAGE C.UTF-8 +FROM zmbot/swoole:latest -RUN apt-get update && apt-get install -y software-properties-common tzdata -RUN dpkg-reconfigure -f noninteractive tzdata -VOLUME ["/app/zhamao-framework/"] -RUN add-apt-repository ppa:ondrej/php && - apt-get update && \ - apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && \ - apt-get install wget composer -y && \ - wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ - tar -zxvf v4.5.0.tar.gz && \ - cd swoole-src-4.5.0/ && \ - phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ - (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) - - -ADD . /app/zhamao-framework -ADD . /app/zhamao-framework-bak -#RUN cd /app/zhamao-framework && composer update && composer clearcache -#RUN mv zhamao-framework-master zhamao-framework -WORKDIR /app/zhamao-framework - -CMD ["/bin/bash", "-i", "/app/zhamao-framework-bak/.entry.sh"] +# TODO: auto-setup entrypoint diff --git a/SECURITY.md b/SECURITY.md index 8b5a29f6..ceccc54f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,8 +4,9 @@ | Version | Supported | | ------- | ------------------ | -| 1.2.x | :white_check_mark: | -| 1.1.x | :x: | +| 2.0 | :white_check_mark: | +| 1.6.x | :white_check_mark: | +| 1.1.x | :x: | | 1.0.x | :x: | ## Reporting a Vulnerability diff --git a/composer.json b/composer.json index 351e3bdd..4df1d2fb 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-b1", + "version": "2.0.0-b3", "authors": [ { "name": "whale", @@ -66,10 +66,6 @@ { "type": "path", "url": "/Users/jerry/project/git-project/zhamao-console" - }, - { - "type": "path", - "url": "/Users/jerry/project/git-project/zhamao-lock" } ] } diff --git a/docker_mixed/Dockerfile b/docker_mixed/Dockerfile deleted file mode 100644 index ca2291e8..00000000 --- a/docker_mixed/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM richardchien/cqhttp:latest -RUN apt-get update && apt-get install -y software-properties-common tzdata -RUN dpkg-reconfigure -f noninteractive tzdata -RUN add-apt-repository ppa:ondrej/php && \ - apt-get update && \ - apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && \ - apt-get install wget composer -y && \ - wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \ - tar -zxvf v4.5.0.tar.gz && \ - cd swoole-src-4.5.0/ && \ - phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \ - (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) -ADD start.sh /home/user/start.sh -RUN chown user:user /home/user/start.sh && chmod +x /home/user/start.sh -ADD https://github.com/zhamao-robot/zhamao-framework/archive/master.zip /home/user/master.zip -RUN chown user:user /home/user/master.zip && chmod 777 /home/user/master.zip -VOLUME ["/home/user/coolq","/home/user/zhamao-framework"] diff --git a/docker_mixed/start.sh b/docker_mixed/start.sh deleted file mode 100644 index adf73f0f..00000000 --- a/docker_mixed/start.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -unzip master.zip -mv zhamao-framework-master/* zhamao-framework/ -cd zhamao-framework -php bin/start diff --git a/docs/guide/我需要做什么.md b/docs/guide/我需要做什么.md deleted file mode 100644 index 7aaa2981..00000000 --- a/docs/guide/我需要做什么.md +++ /dev/null @@ -1,3 +0,0 @@ -# 我需要做什么 - -文档还在写,不急!! \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index f8c5dc91..83711962 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,22 +1,39 @@ # 介绍 -炸毛框架由 php-swoole 构建而来,主要面向 API 服务,聊天机器人(CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,可以方便地编写各类功能。 +炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。 框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 在 HTTP 和 WebSocket 服务器上,PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。 -此外,QQ 机器人方面此框架基于 OneBot 标准 的反向 WebSocket 连接,比传统 HTTP 通信更快,未来也会兼容微信公众号开发者模式。 +此外,QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快,未来也会兼容微信公众号开发者模式。 + +```php +/** + * @CQCommand("你好") + */ +public function hello() { + ctx()->reply("你好,我是炸毛!"); +} +/** + * @RequestMapping("/index") + */ +public function index() { + return "

hello!

"; +} +``` + + ## 开始前 -因为框架采用的是 Swoole 的方式来运作的,所以和传统的 php-fpm 处理机制 **完全不同**。你可以参考 Python 的 [aiocqhttp](https://github.com/richardchien/python-aiocqhttp) SDK,运行方式为 cli 环境。 -在项目运行过程中,会启动 Master、Reactor、Worker 和 TaskWorker 多个进程,在一般情况下,**你不需要知道这些进程是什么,有什么区别**,除非想学习进程间通信和将框架设为多进程运行模式(框架默认采用单进程多协程运行)。 +首先,你需要了解你需要知道哪些事情才能开始着手使用框架: -若使用本框架只作为机器人框架的话,建议将本框架部署到和 OneBot 标准的客户端同一台服务器上,以减少延迟和断链等网络问题带来的效率影响。 +1. Linux 命令行基础 +2. php 7.2+ 开发环境 +3. HTTP 协议(可选) +4. OneBot 机器人聊天接口标准(可选) -!!! note "给开发者的提示" - - 本文档的 **指南** 部分是对框架内各个部分的详细说明,请仔细阅读指南的各个部分后,根据自己的需要选择阅读 **框架组件** 和 **注解事件** 部分。如果对框架还迷糊,可以在阅读完 **指南** 部分后阅读 **进阶** 篇,去了解怎么完成一个功能。 +需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。 ## 框架特色 diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index 4141e136..24b17acc 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -1,2 +1,9 @@ hljs.initHighlighting() +var _hmt = _hmt || []; +(function() { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?3d2aafad13637de271f50351e873b776"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); +})(); \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 3716de98..0d191fd5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,14 +18,23 @@ extra_css: markdown_extensions: - admonition - pymdownx.tabbed + - pymdownx.superfences + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.highlight: + linenums: true + linenums_style: pymdownx.inline copyright: 'Copyright © 2019 - 2020 CrazyBot Team' nav: - 指南: - 介绍: index.md - - 我需要做什么: guide/我需要做什么.md + - 安装框架: guide/安装.md + - 快速上手(机器人篇): guide/快速上手-机器人.md + - 快速上手(HTTP篇): guide/快速上手-http.md - 选择聊天机器人实例: guide/OneBot实例.md + - 基本配置: guide/基本配置.md - 事件和注解: - 事件和注解: event/index.md - 框架组件: diff --git a/src/ZM/Utils/Terminal.php b/src/ZM/Utils/Terminal.php index 142a429b..9d41eeb2 100644 --- a/src/ZM/Utils/Terminal.php +++ b/src/ZM/Utils/Terminal.php @@ -38,8 +38,7 @@ class Terminal case 'psysh': if (Framework::$argv["disable-coroutine"]) { (new Shell())->run(); - } - else + } else Console::error("Only \"--disable-coroutine\" mode can use psysh!!!"); return true; case 'bc': From 4a4bc697d6488c5bcefdb20eb0e568bcb97ceaac Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 22 Nov 2020 19:21:29 +0800 Subject: [PATCH 12/32] update docs --- docs/guide/基本配置.md | 142 +++++++++++++++++++++ docs/guide/安装.md | 111 +++++++++++++++++ docs/guide/快速上手-http.md | 3 + docs/guide/快速上手-机器人.md | 226 ++++++++++++++++++++++++++++++++++ 4 files changed, 482 insertions(+) create mode 100644 docs/guide/基本配置.md create mode 100644 docs/guide/安装.md create mode 100644 docs/guide/快速上手-http.md create mode 100644 docs/guide/快速上手-机器人.md diff --git a/docs/guide/基本配置.md b/docs/guide/基本配置.md new file mode 100644 index 00000000..f0ee5cd1 --- /dev/null +++ b/docs/guide/基本配置.md @@ -0,0 +1,142 @@ +# 基本配置 + +到目前为止,炸毛框架的配置文件还没有任何变更,是默认的行为。在本章内容中,将列举出炸毛框架的配置文件的规则和使用。 + +!!! error "警告" + + 因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将将敏感信息写入配置文件并提交至开源仓库! + +## 全局配置文件 global.php + +框架的全局配置文件在 `config/global.php` 文件中。下面是配置文件的各个选项,请根据自己的需要自行配置。 + +| 配置名称 | 说明 | 默认值 | +| :--------------------------- | ------------------------------------------------ | ---------------------------- | +| `host` | 框架监听的地址 | 0.0.0.0 | +| `port` | 框架监听的端口 | 20001 | +| `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 | +| `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` | +| `debug_mode` | 框架是否启动 debug 模式 | false | +| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` | +| `swoole` | 对应 Swoole server 中 set 的参数,参考Swoole文档 | 见子表 `swoole` | +| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` | +| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` | +| `redis_config` | Redis 连接信息 | 见子表 `redis_config` | +| `access_token` | OneBot 客户端连接约定的token,留空则无 | 空 | +| `http_header` | HTTP 请求自定义返回的header | 见配置文件 | +| `http_default_code_page` | HTTP服务器在指定状态码下回复的默认页面 | 见配置文件 | +| `init_atomics` | 框架启动时初始化的原子计数器列表 | 见配置文件 | +| `info_level` | 终端日志显示等级(0-4) | 2 | +| `context_class` | 上下文所定义的类,待上下文完善后见对应文档 | `\ZM\Context\Context::class` | +| `static_file_server` | 静态文件服务器配置项 | 见子表 `static_file_server` | +| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表 | 见配置文件 | +| `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 | +| `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` | + +### 子表 **swoole** + +| 配置名称 | 说明 | 默认值 | +| --------------- | ------------------------------------------------------------ | ----------------------------------- | +| `log_file` | Swoole 的日志文件 | `crash_dir` 下的 `swoole_error.log` | +| `worker_num` | Worker 工作进程数 | 运行框架的主机 CPU 核心数 | +| `dispatch_mode` | 数据包分发策略,见 [文档](https://wiki.swoole.com/#/server/setting?id=dispatch_mode) | 2 | +| `max_coroutine` | 最大协程并发数 | 300000 | + +### 子表 **light_cache** + +| 配置名称 | 说明 | 默认值 | +| -------------------------- | ----------------------------------------------- | ---------------------------- | +| `size` | 最多可以缓存的 k-v 条目数(必须是 2 的 n 次方) | 1024 | +| `max_strlen` | 作为 value 字符串的最大长度 | 16384 | +| `hash_conflict_proportion` | Hash冲突率(越大越好,但是需要的内存更多) | 0.6 | +| `persistence_path` | 持久化的键值对的存储路径 | `zm_data` 下的 `_cache.json` | +| `auto_save_interval` | 持久化的键值对自动保存时间间隔(秒) | 900 | + +### 子表 **sql_config** + +| 配置名称 | 说明 | 默认值 | +| ------------------------ | ------------------------------ | ------------------------------------------------------------ | +| `sql_host` | 数据库地址(留空则不使用数据库) | 空 | +| `sql_port` | 数据库端口 | 3306 | +| `sql_username` | 连接数据库的用户名 | | +| `sql_database` | 要连接的数据库名 | | +| `sql_password` | 数据库连接密码 | | +| `sql_options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_EMULATE_PREPARES => false]` | +| `sql_default_fetch_mode` | PDO 的 fetch 模式 | `PDO::FETCH_ASSOC` | + +### 子表 **redis_config** + +| 配置名称 | 说明 | 默认值 | +| ---------- | ------------------------------------------ | ------ | +| `host` | Redis 服务器地址,留空则启动时不创建连接池 | 空 | +| `port` | Redis 服务器端口 | 6379 | +| `timeout` | Redis 超时时间 | 1 | +| `db_index` | Redis 要连接的数据库 index | 0 | +| `auth` | 认证字符串 | 空 | + +### 子表 static_file_server + +| 配置名称 | 说明 | 默认值 | +| ---------------- | ---------------------- | ------------------------------ | +| `status` | 是否开启静态文件服务器 | false | +| `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` | +| `document_index` | 默认索引的文件名列表 | `["index.html"]` | + +## 多环境下的配置文件 + +炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(production / development / staging)来编写配置文件。 + +### 使用环境参数 + +在启动框架时,额外增加参数 `--env` 可以指定当前的环境,从而使用不同的配置文件。现在框架支持以下几种环境: `production`,`staging`,`development`。 + +```bash +vendor/bin/start server --env=development +``` + +### 不同环境配置文件 + +由于框架默认只带有 `global.php` 文件,所以假设你现在需要区分开发环境和生产环境的配置,将 `global.php` 文件复制或改名为 `global.development.php` 或 `global.production.php` 即可。 + +### 优先级 + +如果指定了 `--env` 环境参数:`global.{对应环境}.php` > `global.php`,如果两个配置文件都找不到则报错。 + +如果未指定 `--env` 环境参数:`global.php` > `global.development.php` > `global.staging.php` > `global.production.php`。 + +## 其他自定义配置文件 + +炸毛框架的全局配置文件为 `global.php`,为了让不同的开发者更好的二次开发或者集成更多功能,炸毛框架的配置文件模块也支持自己编写的其他 `*.php` 或 `*.json` 格式的配置文件。例如炸毛框架默认附带了 `file_header.json` 这个配置文件(用来返回各类文件扩展名对应的 `Content-Type` 头参数的表)。 + +使用也非常简单,我们先以 `.json` 格式为例,我们创建一个 `example_a.json` 文件在 `config/` 目录(和 `global.php` 一个文件夹下),并编写自己的任意配置内容: + +```json +{ + "key1": "value1" +} +``` + +在框架中,启动后就会默认加载,使用只需要用以下方式即可: + +```php +use ZM\Config\ZMConfig; # 先 use 再使用! +$r = ZMConfig::get("example_a", "key1"); # $r == "value1" +``` + +如果需要用到变量或其他动态的内容,可以使用 `.php` 格式的配置文件。这里还是以 `example_a.php` 来举例: + +```php + 这篇为炸毛框架以及环境的部署教程。 + +## 主机手动部署环境 + +### Ubuntu / Debian / Kali + +需要的系统内软件包为:`php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql wget composer` + +下面是一个一键安装的命令行(最小安装,需 root 权限): + +```bash +apt-get update && apt-get install -y software-properties-common && add-apt-repository ppa:ondrej/php && apt-get update && apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && apt-get install wget composer -y && wget https://github.com/swoole/swoole-src/archive/v4.5.7.tar.gz && tar -zxvf v4.5.7.tar.gz && cd swoole-src-4.5.7/ && phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) +``` + +### macOS (with Homebrew) + +macOS 系统下的部署相对简单很多,只需要使用 Homebrew 安装以下包和执行安装命令即可 + +!!! note "给 macOS 开发者的提示" + + 因为苹果新的 Apple Sillicon 对 Homebrew 的支持目前仅限于 Rosetta2 转译版, + 所以在使用 M1-based Mac 时出现问题暂时无解。 + 使用以下指令可能会遇到报错等问题,如有疑问可直接使用 Docker 或咨询我(炸毛框架开发者)。 + +```bash +brew install php composer +pecl install swoole +``` + +## 手动部署框架 + +```bash +# 方法1: 从 composer 模板项目快速新建(前提是主机有安装 composer) +composer create-project zhamao/framework-starter 你的项目名称 +# 方法2: 从 composer 引入项目(前提是主机有安装 composer) +mkdir 你的项目名称 && cd 你的项目名称 +composer require zhamao/framework +vendor/bin/start init +``` + +## Docker 部署环境和框架 + +对于不想部署到主机环境的情况,推荐直接使用 Docker。 + +!!! Note "提示" + + 本框架内安装的 Docker 容器包括框架本身和开发者开发的模块代码等,此镜像也可用于其他 Swoole 框架的启动,框架本身是需要从 GitHub 拉取或 composer 拉取的。 + +### Docker 容器拉取和构建 + +```bash +# 自行构建 Swoole 环境容器 +git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git +cd zhamao-swoole-docker/ +docker build -t zm . +# 或者使用 dockerhub 构建好的镜像构建 Swoole 环境容器 +docker pull zmbot/swoole +``` + +### 使用 Docker 容器初始化 composer 和框架代码 + +```bash +# 使用 GitHub 克隆快速开始项目仓库 +git clone https://github.com/zhamao-robot/zhamao-framework-starter.git +cd zhamao-framework-starter +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zm composer update +# 如果使用 dockerhub 拉取的,把 `zm` 换成 `zmbot/swoole` 即可 +``` + +## 启动框架 + +```bash +# 本地环境拉取 zhamao-framework-starter 后启动方式 +cd zhamao-framework-starter +vendor/bin/start server + +# 使用 Docker 启动 +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zm vendor/bin/start server # 如果使用 dockerhub 拉取的,把 `zm` 换成 `zmbot/swoole` 即可 +``` + +启动后你会看到和下方类似的初始化内容,表明启动成功了 + +```verilog +$ vendor/bin/start server +host: 0.0.0.0 | port: 20001 +log_level: 2 | version: 2.0.0 +config: global.php | worker_num: 4 +working_dir: /Users/jerry/project/git-project/zhamao-framework + ______ +|__ / |__ __ _ _ __ ___ __ _ ___ + / /| '_ \ / _` | '_ ` _ \ / _` |/ _ \ + / /_| | | | (_| | | | | | | (_| | (_) | +/____|_| |_|\__,_|_| |_| |_|\__,_|\___/ + +[14:27:31] [I] [#0] Worker #0 启动中 +[14:27:31] [I] [#2] Worker #2 启动中 +[14:27:31] [I] [#1] Worker #1 启动中 +[14:27:31] [I] [#3] Worker #3 启动中 +[14:27:31] [S] [#3] Worker #3 已启动 +[14:27:31] [S] [#0] Worker #0 已启动 +[14:27:31] [S] [#2] Worker #2 已启动 +[14:27:31] [S] [#1] Worker #1 已启动 +``` + +单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问:http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。 + +## 使用 IDE 等工具开发代码 + +我们使用文本编辑器进行炸毛框架开发,在使用集成开发环境 **IDEA** 或 **PhpStorm** 时,推荐通过插件市场搜索并安装 **PHP Annotations** 插件以提供注解命名空间自动补全、注解属性代码提醒、注解类跳转等,非常有助于提升开发效率的功能。 \ No newline at end of file diff --git a/docs/guide/快速上手-http.md b/docs/guide/快速上手-http.md new file mode 100644 index 00000000..6e9a1d1d --- /dev/null +++ b/docs/guide/快速上手-http.md @@ -0,0 +1,3 @@ +# 快速上手 - HTTP 服务器篇 + +HTTP 服务器篇暂时先放一放,大家应该主要都是奔着机器人开发来的吧~ \ No newline at end of file diff --git a/docs/guide/快速上手-机器人.md b/docs/guide/快速上手-机器人.md new file mode 100644 index 00000000..9a5674c4 --- /dev/null +++ b/docs/guide/快速上手-机器人.md @@ -0,0 +1,226 @@ +# 快速上手 - 机器人篇 + + + +## 简介 + +看到这里,你已经完成了前面的环境部署,到了最关键的第一步了! + +一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 **机器人问答模块** 的准备。 + +炸毛框架和机器人客户端是什么关系呢?炸毛框架就好比我们传统的一系列例如 Spring 框架、ThinkPHP 框架等,是服务端,而机器人客户端是一个 HTTP / WebSocket 客户端,时刻准备着连接到炸毛框架的。 + +## 机器人客户端 + +机器人客户端是炸毛框架以外的程序或软件,目前炸毛框架支持的机器人客户端通信标准为 OneBot 标准,只要你的机器人客户端是 OneBot 标准的,就可以和炸毛框架进行无缝对接。 + +OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)。 + +这里以炸毛框架开发过程中使用的 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 来举例进行第一个机器人的配置工作。 + +简要步骤描述为: + +1. 下载 go-cqhttp 对应平台的 [release 文件](https://github.com/Mrs4s/go-cqhttp/releases) +2. 双击 exe 文件或者使用 `./go-cqhttp` 启动 +3. 生成默认配置文件并修改默认配置 + +!!! warning "注意" + + 由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`。 + +=== "config.json(旧格式)" + + ``` json hl_lines="2 3 30 31" + { + "uin": 你的QQ号, + "password": "你的密码", + "encrypt_password": false, + "password_encrypted": "", + "enable_db": true, + "access_token": "", + "relogin": { + "enabled": true, + "relogin_delay": 3, + "max_relogin_times": 0 + }, + "ignore_invalid_cqcode": false, + "force_fragmented": true, + "heartbeat_interval": 0, + "http_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 5700, + "timeout": 0, + "post_urls": {} + }, + "ws_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 6700 + }, + "ws_reverse_servers": [ + { + "enabled": true, + "reverse_url": "ws://127.0.0.1:20001/", + "reverse_api_url": "", + "reverse_event_url": "", + "reverse_reconnect_interval": 3000 + } + ], + "post_message_format": "string", + "debug": false, + "log_level": "" + } + ``` + +=== "config.hjson(新格式)" + + ``` json hl_lines="3 5 81 84" + { + // QQ号 + uin: 你的机器人QQ + // QQ密码 + password: "你的QQ密码" + // 是否启用密码加密 + encrypt_password: false + // 加密后的密码, 如未启用密码加密将为空, 请勿随意修改. + password_encrypted: "" + // 是否启用内置数据库 + // 启用将会增加10-20MB的内存占用和一定的磁盘空间 + // 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能 + enable_db: true + // 访问密钥, 强烈推荐在公网的服务器设置 + access_token: "" + // 重连设置 + relogin: { + // 是否启用自动重连 + // 如不启用掉线后将不会自动重连 + enabled: true + // 重连延迟, 单位秒 + relogin_delay: 3 + // 最大重连次数, 0为无限制 + max_relogin_times: 0 + } + // API限速设置 + // 该设置为全局生效 + // 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配 + // 目前该限速设置为令牌桶算法, 请参考: + //https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin + _rate_limit: { + // 是否启用限速 + enabled: false + // 令牌回复频率, 单位秒 + frequency: 1 + // 令牌桶大小 + bucket_size: 1 + } + // 是否忽略无效的CQ码 + // 如果为假将原样发送 + ignore_invalid_cqcode: false + // 是否强制分片发送消息 + // 分片发送将会带来更快的速度 + // 但是兼容性会有些问题 + force_fragmented: false + // 心跳频率, 单位秒 + // -1 为关闭心跳 + heartbeat_interval: 0 + // HTTP设置 + http_config: { + // 是否启用正向HTTP服务器 + enabled: true + // 服务端监听地址 + host: 0.0.0.0 + // 服务端监听端口 + port: 5700 + // 反向HTTP超时时间, 单位秒 + // 最小值为5,小于5将会忽略本项设置 + timeout: 0 + // 反向HTTP POST地址列表 + // 格式: + // { + // 地址: secret + // } + post_urls: {} + } + // 正向WS设置 + ws_config: { + // 是否启用正向WS服务器 + enabled: true + // 正向WS服务器监听地址 + host: 0.0.0.0 + // 正向WS服务器监听端口 + port: 6700 + } + // 反向WS设置 + ws_reverse_servers: [ + // 可以添加多个反向WS推送 + { + // 是否启用该推送 + enabled: true + // 反向WS Universal 地址 + // 注意 设置了此项地址后下面两项将会被忽略 + reverse_url: ws://127.0.0.1:20001/ + // 反向WS API 地址 + reverse_api_url: "" + // 反向WS Event 地址 + reverse_event_url: "" + // 重连间隔 单位毫秒 + reverse_reconnect_interval: 3000 + } + ] + // 上报数据类型 + // 可选: string array + post_message_format: string + // 是否使用服务器下发的新地址进行重连 + // 注意, 此设置可能导致在海外服务器上连接情况更差 + use_sso_address: false + // 是否启用 DEBUG + debug: false + // 日志等级 + // WebUi 设置 + web_ui: { + // 是否启用 WebUi + enabled: true + // 监听地址 + host: 127.0.0.1 + // 监听端口 + web_ui_port: 9999 + // 是否接收来自web的输入 + web_input: false + } + } + ``` + +其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT + +## 第一次对话 + +一旦新的配置文件正确生效之后,所在的控制台(如果正在运行的话)应该会输出类似下面的内容: + +```verilog +[15:26:34] [I] [#2] 机器人 你的QQ号 已连接! +``` + +表明机器人已成功连接到炸毛框架了! + +这时,如果你是根据安装教程走下来并且未编写任何模块,炸毛自带一个示例模块,里面含有命令:`你好`,`随机数`。如果你对机器人回复:`你好`,它会回复你 `你好啊,我是由炸毛框架构建的机器人!`。这一历史性的对话标志着你已经成功地运行了炸毛框架,开始了编写更强大的 QQ 机器人的创意之旅! + +## 编写一个命令 + +让我们转到框架的模块源代码部分,目录是 `src/Module/Example`,文件是 `Hello.php`。我们插入一段这样的代码: + +```php +/** + * @CQCommand("echo") + */ +public function repeat() { + $repeat = ctx()->getArgs(ZM_MATCH_ALL, "请输入你要回复的内容"); + ctx()->reply($repeat); + //return $repeat; // 这样的效果等同于 ctx()->reply() +} +``` + +这样,一个简易的复读机就做好了!回到 QQ 机器人聊天,向机器人发送 `echo 你好啊`,它会回复你 `你好啊`。 + +> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。 + From 63e05941994be6e0a2a13ab06a634a29bd358965 Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 22 Nov 2020 19:26:35 +0800 Subject: [PATCH 13/32] update README.md --- README.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 36f322f1..5b670b3b 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE) [![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework) [![Banner](https://img.shields.io/badge/CQHTTP-v11-black)]() -[![dev-version](https://img.shields.io/badge/dev--version-v2.0.0--beta1-green)]() +[![dev-version](https://img.shields.io/badge/dev--version-v2.0.0--b3-green)]() [![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid) [![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO) @@ -15,8 +15,6 @@ ## 开发者注意 -**此版本目前无法正常运行,还在持续开发中,请勿使用!!!** - **2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。** [![dev-version2](https://img.shields.io/badge/process-95%25-green)]() @@ -35,9 +33,9 @@ zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 3. 还可以使用 Dockerfile 构建 Docker 容器 ## 文档 -Pages托管:[https://framework.zhamao.xin/](https://framework.zhamao.xin/) +查看文档:[https://zhamao.xin/docs-v2/](https://zhamao.xin/docs-v2/) -国内服务器:[https://framework2.zhamao.xin/](https://framework2.zhamao.xin/) +自行构建文档:`mkdocs build -d distribute` ## 特点 - 支持多账号 @@ -65,12 +63,12 @@ Pages托管:[https://framework.zhamao.xin/](https://framework.zhamao.xin/) - [ ] 图灵机器人/腾讯AI 聊天模块 - [ ] 分词模块(可能会放弃计划,因为目前好用的分词都是其他语言的) - [ ] HTTP 过滤器、Auth 模块、完整的 MVC 兼容(可能会放弃计划,因为框架主打机器人开发) -- [ ] Redis 连接池或开箱即用的相应功能内置 +- [X] Redis 连接池或开箱即用的相应功能内置 - [X] 1.3 版本使用上下文代替 - [X] 更好的 Logger,稳定和漂亮的控制台输出 - [ ] 日志服务 - [X] 框架支持 Phar 打包(可能会比较靠后支持) -- [ ] 完整的单元测试(如果有需求则尽快开发) +- [X] 完整的单元测试(如果有需求则尽快开发) - [X] 静态文件服务器 ## 从 cqbot-swoole 升级 From dbfe2c9c1779812e65d2e224226b416c7301c085 Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 23 Nov 2020 00:24:33 +0800 Subject: [PATCH 14/32] update documents --- docs/assets/favicon.png | Bin 0 -> 20337 bytes docs/component/index.md | 4 +++- docs/event/index.md | 5 ++++- docs/javascripts/config.js | 6 +++--- docs/test.md | 2 +- docs/update.md | 2 ++ mkdocs.yml | 2 ++ 7 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 docs/assets/favicon.png diff --git a/docs/assets/favicon.png b/docs/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..fa2ccbd0e35625c8688aac6a95acdb2d594e3d31 GIT binary patch literal 20337 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 eXP{qZ;Qt2;+|`4dF$@v_0000 Date: Mon, 23 Nov 2020 01:09:57 +0800 Subject: [PATCH 15/32] update documents --- docs/index.md | 59 +++++++++++++++++++++++++++++++++++++- docs/javascripts/config.js | 38 ++++++++++++++++++++---- mkdocs.yml | 24 ++++++++++++++-- 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 83711962..0341512a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -43,4 +43,61 @@ public function index() { - 支持多个机器人账号负载均衡 - 协程 + TaskWorker 进程重度任务处理机制,保证高效,单个请求响应时间为 0.1 ms 左右 - 模块分离和自由组合,可根据自身需求自己建立模块内的目录结构和代码结构 -- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾 \ No newline at end of file +- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾 + +## 文档主题 + +
+ + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index 0e13c35f..067ae85b 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -1,9 +1,37 @@ hljs.initHighlighting() var _hmt = _hmt || []; -(function() { - var hm = document.createElement("script"); - hm.src = "https://hm.baidu.com/hm.js?f0f276cefa10aa31a20ae3815a50b795"; - var s = document.getElementsByTagName("script")[0]; - s.parentNode.insertBefore(hm, s); +(function () { + var hm = document.createElement("script"); + hm.src = "https://hm.baidu.com/hm.js?f0f276cefa10aa31a20ae3815a50b795"; + var s = document.getElementsByTagName("script")[0]; + s.parentNode.insertBefore(hm, s); })(); + +function getCookie(name) { + var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); + + if (arr = document.cookie.match(reg)) + + return unescape(arr[2]); + else + return null; +} + +function setCookie(name, value) { + var Days = 30; + var exp = new Date(); + exp.setTime(exp.getTime() + Days * 24 * 60 * 60 * 1000); + document.cookie = name + "=" + escape(value) + ";expires=" + exp.toGMTString(); +} + +s_theme=getCookie("_theme"); +if(s_theme === undefined) s_theme = "default"; +document.body.setAttribute("data-md-color-scheme", s_theme) +var name = document.querySelector("#__code_0 code span:nth-child(7)") +name.textContent = s_theme + +s_primary=getCookie("_primary_color"); +document.body.setAttribute("data-md-color-primary", s_primary); +var name2 = document.querySelector("#__code_2 code span:nth-child(7)"); +name2.textContent = s_primary.replace("-", " "); diff --git a/mkdocs.yml b/mkdocs.yml index 2ed82926..c44b981a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,7 +2,7 @@ site_name: 炸毛框架 v2 repo_name: '炸毛框架' repo_url: 'https://github.com/zhamao-robot/zhamao-framework' -edit_uri: 'blob/master/docs/' +edit_uri: 'blob/2.0-dev/docs/' theme: name: material @@ -23,11 +23,31 @@ markdown_extensions: - pymdownx.superfences - pymdownx.inlinehilite - pymdownx.snippets + - abbr - pymdownx.highlight: linenums: true linenums_style: pymdownx.inline +extra: + version: + method: mike -copyright: 'Copyright © 2019 - 2020 CrazyBot Team' +copyright: 'Copyright © 2019 - 2020 CrazyBot Team      + + + + +
蒙ICP备18000198号-1' nav: - 指南: From 944a9e849bb19c81b67b84a425b7dd4320fa4ebf Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 23 Nov 2020 01:47:37 +0800 Subject: [PATCH 16/32] update documents --- README.md | 4 +++- docs/index.md | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5b670b3b..2c608d05 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,9 @@ zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 3. 还可以使用 Dockerfile 构建 Docker 容器 ## 文档 -查看文档:[https://zhamao.xin/docs-v2/](https://zhamao.xin/docs-v2/) +查看文档:[https://docs-v2.zhamao.xin/](https://docs-v2.zhamao.xin/) + +备用链接:[http://docs-v2.zhamao.me/](http://docs-v2.zhamao.me/) 自行构建文档:`mkdocs build -d distribute` diff --git a/docs/index.md b/docs/index.md index 0341512a..930d6aa4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,7 @@ # 介绍 + +> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。 + 炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。 框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 @@ -35,6 +38,10 @@ public function index() { 需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。 +!!! bug "文档提示" + + 此文档采用 MkDocs 驱动,但因为本文档的搜索组件原生不支持中文搜索,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西哦! + ## 框架特色 - 支持MySQL数据库(连接池),自带查询缓存提高多查询时的效率 From 1ffb30a471db1ac2375a8594afb169683b6b48a9 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 10 Dec 2020 16:37:04 +0800 Subject: [PATCH 17/32] update to v2.0.0-b4 version change global.php config load time and logic set context get server function available more time delete unused comment and @CQAPISend @CQCommand add start_with and end_with set exceptions extended by ZMException rename @SwooleSetup to @ZMSetup fix quotes for global.php fix LightCache empty presistence_path error remove RemoteShell --- composer.json | 31 +-- config/global.php | 8 +- src/ZM/API/CQAPI.php | 1 - src/ZM/Annotation/CQ/CQAPISend.php | 43 --- src/ZM/Annotation/CQ/CQCommand.php | 4 + .../Swoole/{SwooleSetup.php => ZMSetup.php} | 4 +- src/ZM/Command/RunServerCommand.php | 2 +- src/ZM/ConsoleApplication.php | 69 +++-- src/ZM/Context/Context.php | 2 +- src/ZM/Exception/DbException.php | 2 +- src/ZM/Exception/InterruptException.php | 2 +- src/ZM/Exception/InvalidArgumentException.php | 2 +- src/ZM/Exception/NotInitializedException.php | 2 +- src/ZM/Exception/RobotNotFoundException.php | 2 +- src/ZM/Exception/WaitTimeoutException.php | 2 +- src/ZM/Exception/ZMException.php | 12 + src/ZM/Framework.php | 30 +- src/ZM/Module/QQBot.php | 8 +- src/ZM/Store/LightCache.php | 1 + src/ZM/Utils/RemoteShell.php | 263 ------------------ src/ZM/Utils/ZMUtil.php | 1 + 21 files changed, 110 insertions(+), 381 deletions(-) delete mode 100644 src/ZM/Annotation/CQ/CQAPISend.php rename src/ZM/Annotation/Swoole/{SwooleSetup.php => ZMSetup.php} (77%) create mode 100644 src/ZM/Exception/ZMException.php delete mode 100644 src/ZM/Utils/RemoteShell.php diff --git a/composer.json b/composer.json index 4df1d2fb..a043fc23 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,8 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-b3", + "version": "2.0.0-b4", + "extra": {}, "authors": [ { "name": "whale", @@ -21,30 +22,24 @@ ], "require": { "php": ">=7.2", - "ext-mbstring": "*", "doctrine/annotations": "~1.10", "ext-json": "*", - "ext-posix": "*", - "ext-ctype": "*", "psy/psysh": "@stable", + "symfony/polyfill-ctype": "^1.20", + "symfony/polyfill-mbstring": "^1.20", "symfony/console": "^5.1", - "symfony/polyfill-ctype": "^1.18", "zhamao/connection-manager": "*@dev", "zhamao/console": "*@dev", "zhamao/config": "*@dev", "zhamao/request": "*@dev", - "symfony/routing": "^5.1" - }, - "suggest": { - "ext-pdo": "Allows framework connecting with mysql server", - "ext-redis": "Allows framework connecting with redis server", - "ext-inotify": "Enable file watcher feature in framework" + "symfony/routing": "^5.1", + "symfony/polyfill-php80": "^1.20" }, "autoload": { "psr-4": { - "Custom\\": "src/Custom", "ZM\\": "src/ZM", - "Module\\": "src/Module" + "Module\\": "src/Module", + "Custom\\": "src/Custom" }, "files": [ "src/ZM/global_functions.php" @@ -61,11 +56,5 @@ "require-dev": { "phpunit/phpunit": "^9.3", "swoole/ide-helper": "@dev" - }, - "repositories": [ - { - "type": "path", - "url": "/Users/jerry/project/git-project/zhamao-console" - } - ] -} + } +} \ No newline at end of file diff --git a/config/global.php b/config/global.php index 00bffce1..eed3fef0 100644 --- a/config/global.php +++ b/config/global.php @@ -36,10 +36,10 @@ $config['swoole'] = [ /** 轻量字符串缓存,默认开启 */ $config['light_cache'] = [ - "size" => 1024, //最多允许储存的条数(需要2的倍数) - "max_strlen" => 16384, //单行字符串最大长度(需要2的倍数) - "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) - "persistence_path" => $config['zm_data']."_cache.json", + 'size' => 1024, //最多允许储存的条数(需要2的倍数) + 'max_strlen' => 16384, //单行字符串最大长度(需要2的倍数) + 'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) + 'persistence_path' => $config['zm_data'].'_cache.json', 'auto_save_interval' => 900 ]; diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index d45540fd..b4b35baa 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -30,7 +30,6 @@ trait CQAPI public function processWebsocketAPI($connection, $reply, $function = false) { $api_id = ZMAtomic::get("wait_msg_id")->add(1); $reply["echo"] = $api_id; - //EventHandler::callCQAPISend($reply, $connection); SpinLock::lock("wait_api"); $r = LightCacheInside::get("wait_api", "wait_api"); $r[$api_id] = [ diff --git a/src/ZM/Annotation/CQ/CQAPISend.php b/src/ZM/Annotation/CQ/CQAPISend.php deleted file mode 100644 index bf4429c0..00000000 --- a/src/ZM/Annotation/CQ/CQAPISend.php +++ /dev/null @@ -1,43 +0,0 @@ -level; - } - - /** - * @param mixed $level - */ - public function setLevel($level) { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php index 4f1d09c2..6a6d4021 100644 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -21,6 +21,10 @@ class CQCommand extends AnnotationBase implements Level public $pattern = ""; /** @var string */ public $regex = ""; + /** @var string */ + public $start_with = ""; + /** @var string */ + public $end_with = ""; /** @var string[] */ public $alias = []; /** @var string */ diff --git a/src/ZM/Annotation/Swoole/SwooleSetup.php b/src/ZM/Annotation/Swoole/ZMSetup.php similarity index 77% rename from src/ZM/Annotation/Swoole/SwooleSetup.php rename to src/ZM/Annotation/Swoole/ZMSetup.php index d0037379..b3165937 100644 --- a/src/ZM/Annotation/Swoole/SwooleSetup.php +++ b/src/ZM/Annotation/Swoole/ZMSetup.php @@ -8,11 +8,11 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class SwooleSetup + * Class ZMSetup * @package ZM\Annotation\Swoole * @Annotation * @Target("METHOD") */ -class SwooleSetup extends AnnotationBase +class ZMSetup extends AnnotationBase { } diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 2f063f8a..965f0765 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -45,7 +45,7 @@ class RunServerCommand extends Command // ... put here the code to run in your command // this method must return an integer number with the "exit status code" // of the command. You can also use these constants to make code more readable - new Framework($input->getOptions()); + (new Framework($input->getOptions()))->start(); // return this if there was no problem running the command // (it's equivalent to returning int(0)) return Command::SUCCESS; diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index dd6bb32b..08bece9a 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -18,18 +18,16 @@ use ZM\Utils\DataProvider; class ConsoleApplication extends Application { - public function __construct(string $name = 'UNKNOWN') { + public function __construct(string $name = 'UNKNOWN') + { $version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN"; parent::__construct($name, $version); } - public function initEnv() { + public function initEnv() + { $this->selfCheck(); - $this->addCommands([ - new RunServerCommand(), //运行主服务的指令控制器 - new InitCommand(), //初始化用的,用于项目初始化和phar初始化 - new PureHttpCommand() - ]); + //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 if (LOAD_MODE === 0) define("WORKING_DIR", getcwd()); elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); @@ -47,20 +45,49 @@ class ConsoleApplication extends Application * @noinspection RedundantSuppression */ require_once WORKING_DIR . "/vendor/autoload.php"; + echo "* This is repository mode.\n"; + $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); + if (!isset($composer["autoload"]["psr-4"]["Module\\"])) { + echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] "; + $r = strtolower(trim(fgets(STDIN))); + if ($r === "" || $r === "y") { + $composer["autoload"]["psr-4"]["Module\\"] = "src/Module"; + $composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom"; + $r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); + if ($r !== false) { + echo "成功添加!请重新进行 composer update !\n"; + exit(1); + } else { + echo "添加失败!请按任意键继续!"; + fgets(STDIN); + exit(1); + } + } else { + exit(1); + } + } } if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { die("Unable to find source directory.\nMaybe you need to run \"init\"?"); } - ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config'); + ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::env($args["env"] ?? ""); - if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error); - - $command_register = ZMConfig::get("global", "command_register_class") ?? []; - foreach($command_register as $v) { - $obj = new $v(); - if(!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); - $this->add($obj); + if (ZMConfig::get("global") === false) { + echo ("Global config load failed: " . ZMConfig::$last_error."\nPlease init first!\n"); + $this->add(new InitCommand()); + } else { + $this->addCommands([ + new RunServerCommand(), //运行主服务的指令控制器 + new InitCommand(), //初始化用的,用于项目初始化和phar初始化 + new PureHttpCommand() //纯HTTP服务器指令 + ]); + $command_register = ZMConfig::get("global", "command_register_class") ?? []; + foreach ($command_register as $v) { + $obj = new $v(); + if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); + $this->add($obj); + } } } @@ -69,7 +96,8 @@ class ConsoleApplication extends Application * @param OutputInterface|null $output * @return int */ - public function run(InputInterface $input = null, OutputInterface $output = null) { + public function run(InputInterface $input = null, OutputInterface $output = null) + { try { return parent::run($input, $output); } catch (Exception $e) { @@ -77,12 +105,13 @@ class ConsoleApplication extends Application } } - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + private function selfCheck() + { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19"); if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (substr(PHP_VERSION, 0, 1) < "7") die("PHP >=7 required.\n"); //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index c5cc0c0f..6ad6fa12 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -28,7 +28,7 @@ class Context implements ContextInterface /** * @return swoole_server|null */ - public function getServer() { return self::$context[$this->cid]["server"] ?? null; } + public function getServer() { return self::$context[$this->cid]["server"] ?? server(); } /** * @return Frame|null diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php index f5575ce2..d663eacc 100644 --- a/src/ZM/Exception/DbException.php +++ b/src/ZM/Exception/DbException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class DbException extends Exception +class DbException extends ZMException { } \ No newline at end of file diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php index b11016cd..f5d00aa8 100644 --- a/src/ZM/Exception/InterruptException.php +++ b/src/ZM/Exception/InterruptException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class InterruptException extends Exception +class InterruptException extends ZMException { } diff --git a/src/ZM/Exception/InvalidArgumentException.php b/src/ZM/Exception/InvalidArgumentException.php index eb4b47a5..7ccc3b70 100644 --- a/src/ZM/Exception/InvalidArgumentException.php +++ b/src/ZM/Exception/InvalidArgumentException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class InvalidArgumentException extends Exception +class InvalidArgumentException extends ZMException { } \ No newline at end of file diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php index 0f30c04f..f1bf753d 100644 --- a/src/ZM/Exception/NotInitializedException.php +++ b/src/ZM/Exception/NotInitializedException.php @@ -6,7 +6,7 @@ namespace ZM\Exception; use Exception; -class NotInitializedException extends Exception +class NotInitializedException extends ZMException { } diff --git a/src/ZM/Exception/RobotNotFoundException.php b/src/ZM/Exception/RobotNotFoundException.php index 7e63c524..9b734260 100644 --- a/src/ZM/Exception/RobotNotFoundException.php +++ b/src/ZM/Exception/RobotNotFoundException.php @@ -12,7 +12,7 @@ use Throwable; * @package ZM\Exception * @since 1.2 */ -class RobotNotFoundException extends Exception +class RobotNotFoundException extends ZMException { public function __construct($message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index 561453d6..c9d18255 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -7,7 +7,7 @@ namespace ZM\Exception; use Exception; use Throwable; -class WaitTimeoutException extends Exception +class WaitTimeoutException extends ZMException { public $module; diff --git a/src/ZM/Exception/ZMException.php b/src/ZM/Exception/ZMException.php new file mode 100644 index 00000000..01d8bf96 --- /dev/null +++ b/src/ZM/Exception/ZMException.php @@ -0,0 +1,12 @@ +registerServerEvents(); $r = ZMConfig::get("global", "light_cache") ?? [ - "size" => 1024, - "max_strlen" => 8192, - "hash_conflict_proportion" => 0.6, - "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), - "auto_save_interval" => 900 - ]; + "size" => 1024, + "max_strlen" => 8192, + "hash_conflict_proportion" => 0.6, + "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), + "auto_save_interval" => 900 + ]; LightCache::init($r); LightCacheInside::init(); SpinLock::init($r["size"]); - self::$server->start(); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); Console::error($e->getMessage()); @@ -129,6 +127,10 @@ class Framework } } + public function start() { + self::$server->start(); + } + /** * 从全局配置文件里读取注入系统事件的类 * @throws ReflectionException @@ -152,7 +154,7 @@ class Framework $annotation->class = $v; $annotation->method = $vs->getName(); $event_list[strtolower($annotation->event)] = $annotation; - } elseif ($annotation instanceof SwooleSetup) { + } elseif ($annotation instanceof ZMSetup) { $annotation->class = $v; $annotation->method = $vs->getName(); $c = new $v(); @@ -213,14 +215,6 @@ class Framework case 'disable-console-input': if ($y) $terminal_id = null; break; - case 'remote-shell': - if ($y) { - $host = "127.0.0.1"; - $port = 9599; - RemoteShell::listen(self::$server, $host, $port); - Console::log(Console::setColor("正在监听" . $host . ":" . strval($port) . "的调试接口,请注意安全", "yellow")); - } - break; case 'log-error': if ($y) Console::setLevel(0); break; diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 13f75c7d..902c1db7 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -88,7 +88,13 @@ class QQBot if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) { ctx()->setCache("match", $word); return true; - } elseif ($v->pattern != "") { + } elseif ($v->start_with != "" && mb_strpos(ctx()->getMessage(), $v->start_with) === 0) { + ctx()->setCache("match", [mb_substr(ctx()->getMessage(), mb_strlen($v->start_with))]); + return true; + } elseif ($v->end_with != "" && strlen(ctx()->getMessage()) == (strripos(ctx()->getMessage(), $v->end_with) + strlen($v->end_with))) { + ctx()->setCache("match", [substr(ctx()->getMessage(), 0, strripos(ctx()->getMessage(), $v->end_with))]); + return true; + }elseif ($v->pattern != "") { $match = matchArgs($v->pattern, ctx()->getMessage()); if($match !== false) { ctx()->setCache("match", $match); diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 5607c2e3..b6f0c720 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -178,6 +178,7 @@ class LightCache $r[$k] = self::parseGet($v); } } + if(self::$config["persistence_path"] == "") return; $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); } diff --git a/src/ZM/Utils/RemoteShell.php b/src/ZM/Utils/RemoteShell.php deleted file mode 100644 index 701b3e74..00000000 --- a/src/ZM/Utils/RemoteShell.php +++ /dev/null @@ -1,263 +0,0 @@ -listen($host, $port, SWOOLE_SOCK_TCP); - if (!$port) { - throw new Exception("listen fail."); - } - $port->set(array( - "open_eof_split" => true, - 'package_eof' => "\r\n", - )); - $port->on("Connect", array(__CLASS__, 'onConnect')); - $port->on("Close", array(__CLASS__, 'onClose')); - $port->on("Receive", array(__CLASS__, 'onReceive')); - if (method_exists($serv, 'getCallback')) { - self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage'); - } - $serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage')); - self::$serv = $serv; - } - - static function onConnect($serv, $fd, $reactor_id) { - self::$contexts[$fd]['worker_id'] = $serv->worker_id; - self::output($fd, implode("\r\n", self::$menu)); - } - - static function output($fd, $msg) { - if (!isset(self::$contexts[$fd]['worker_id'])) { - $msg .= "\r\nworker#" . self::$serv->worker_id . "$ "; - } else { - $msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ "; - } - self::$serv->send($fd, $msg); - } - - static function onClose($serv, $fd, $reactor_id) { - unset(self::$contexts[$fd]); - } - - static function onPipeMessage($serv, $src_worker_id, $message) { - //不是 debug 消息 - if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) { - if (self::$oriPipeMessageCallback == null) { - trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING); - return true; - } - return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message); - } else { - $request = unserialize(substr($message, strlen(self::STX))); - self::call($request['fd'], $request['func'], $request['args']); - } - return true ; - } - - static protected function call($fd, $func, $args) { - ob_start(); - call_user_func_array($func, $args); - self::output($fd, ob_get_clean()); - } - - static protected function exec($fd, $func, $args) { - //不在当前Worker进程 - if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) { - self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']); - } else { - self::call($fd, $func, $args); - } - } - - static function getCoros() { - var_export(iterator_to_array(Coroutine::listCoroutines())); - } - - static function getBackTrace($_cid) { - $info = Co::getBackTrace($_cid); - if (!$info) { - echo "coroutine $_cid not found."; - } else { - echo get_debug_print_backtrace($info); - } - } - - static function printVariant($var) { - $var = ltrim($var, '$ '); - var_dump($var); - var_dump($$var); - } - - static function evalCode($code) { - eval($code . ';'); - } - - /** - * @param $serv server - * @param $fd - * @param $reactor_id - * @param $data - */ - static function onReceive($serv, $fd, $reactor_id, $data) { - $args = explode(" ", $data, 2); - $cmd = trim($args[0]); - unset($args[0]); - switch ($cmd) { - case 'w': - case 'worker': - if (!isset($args[1])) { - self::output($fd, "invalid command."); - break; - } - $dstWorkerId = intval($args[1]); - self::$contexts[$fd]['worker_id'] = $dstWorkerId; - self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]"); - break; - case 'e': - case 'exec': - if (!isset($args[1])) { - self::output($fd, "invalid command."); - break; - } - $var = trim($args[1]); - self::exec($fd, 'self::evalCode', [$var]); - break; - case 'p': - case 'print': - $var = trim($args[1]); - self::exec($fd, 'self::printVariant', [$var]); - break; - case 'h': - case 'help': - self::output($fd, implode("\r\n", self::$menu)); - break; - case 's': - case 'stats': - $stats = $serv->stats(); - self::output($fd, var_export($stats, true)); - break; - case 'c': - case 'coros': - self::exec($fd, 'self::getCoros', []); - break; - /** - * 查看协程堆栈 - */ - case 'bt': - case 'b': - case 'backtrace': - if (empty($args[1])) { - self::output($fd, "invalid command [" . trim($args[1]) . "]."); - break; - } - $_cid = intval($args[1]); - self::exec($fd, 'self::getBackTrace', [$_cid]); - break; - case 'i': - case 'info': - if (empty($args[1])) { - self::output($fd, "invalid command [" . trim($args[1]) . "]."); - break; - } - $_fd = intval($args[1]); - $info = $serv->getClientInfo($_fd); - if (!$info) { - self::output($fd, "connection $_fd not found."); - } else { - self::output($fd, var_export($info, true)); - } - break; - case 'l': - case 'list': - $tmp = array(); - foreach ($serv->connections as $fd) { - $tmp[] = $fd; - if (count($tmp) > self::PAGESIZE) { - self::output($fd, json_encode($tmp)); - $tmp = array(); - } - } - if (count($tmp) > 0) { - self::output($fd, json_encode($tmp)); - } - break; - case 'q': - case 'quit': - $serv->close($fd); - break; - default: - self::output($fd, "unknow command[$cmd]"); - break; - } - } -} - -function get_debug_print_backtrace($traces) { - $ret = array(); - foreach ($traces as $i => $call) { - $object = ''; - if (isset($call['class'])) { - $object = $call['class'] . $call['type']; - if (is_array($call['args'])) { - foreach ($call['args'] as &$arg) { - get_arg($arg); - } - } - } - $ret[] = '#' . str_pad($i, 3, ' ') - . $object . $call['function'] . '(' . implode(', ', $call['args']) - . ') called at [' . $call['file'] . ':' . $call['line'] . ']'; - } - return implode("\n", $ret); -} - -function get_arg(&$arg) { - if (is_object($arg)) { - $arr = (array)$arg; - $args = array(); - foreach ($arr as $key => $value) { - if (strpos($key, chr(0)) !== false) { - $key = ''; // Private variable found - } - $args[] = '[' . $key . '] => ' . get_arg($value); - } - $arg = get_class($arg) . ' Object (' . implode(',', $args) . ')'; - } -} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 6cf136f4..cfa26725 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -40,6 +40,7 @@ class ZMUtil foreach (server()->connections as $v) { server()->close($v); } + LightCache::savePersistence(); //DataProvider::saveBuffer(); Timer::clearAll(); server()->reload(); From ba5b793db7d9bfbf8f7a00d586460574b1bda1d6 Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 14 Dec 2020 01:24:34 +0800 Subject: [PATCH 18/32] update to 2.0.0-b5 version set modules config to array add subdir index.html update Example of Hello.php add Exception tester for TimerMiddleware.php add keyword for @CQCommand rename OnWorkerStart.php to OnStart.php remove SwooleEventAfter.php rename HandleEvent.php to SwooleHandler.php set ZMRobot callback mode default to true add getNextArg() and getFullArg() add EventDispatcher.php logger set Exception all based from ZMException fix recursive bug for Response.php add single_bot_mode add SingletonTrait.php add bot() function --- composer.json | 8 +- config/global.php | 7 +- resources/html/subdir/index.html | 10 +++ src/Module/Example/Hello.php | 21 ++++- src/Module/Middleware/TimerMiddleware.php | 16 +++- src/ZM/API/ZMRobot.php | 5 +- src/ZM/Annotation/AnnotationParser.php | 6 +- src/ZM/Annotation/CQ/CQCommand.php | 2 + .../Http/{After.php => HandleAfter.php} | 6 +- .../Http/{Before.php => HandleBefore.php} | 7 +- .../Swoole/{ZMSetup.php => OnSetup.php} | 2 +- .../Swoole/{OnWorkerStart.php => OnStart.php} | 2 +- src/ZM/Annotation/Swoole/SwooleEventAfter.php | 76 ------------------- .../{HandleEvent.php => SwooleHandler.php} | 4 +- src/ZM/ConsoleApplication.php | 3 - src/ZM/Context/Context.php | 25 ++++-- src/ZM/Context/ContextInterface.php | 4 + src/ZM/Event/EventDispatcher.php | 68 +++++++++++++++-- src/ZM/Event/ServerEventHandler.php | 53 ++++++++----- src/ZM/Exception/DbException.php | 4 +- src/ZM/Exception/InterruptException.php | 2 - src/ZM/Exception/InvalidArgumentException.php | 4 +- src/ZM/Exception/NotInitializedException.php | 2 - src/ZM/Exception/RobotNotFoundException.php | 1 - src/ZM/Exception/WaitTimeoutException.php | 1 - src/ZM/Framework.php | 10 +-- src/ZM/Http/Response.php | 6 +- src/ZM/Module/QQBot.php | 7 +- src/ZM/Store/LightCache.php | 12 +-- src/ZM/Store/LightCacheInside.php | 19 +++-- src/ZM/Store/ZMAtomic.php | 1 + src/ZM/Utils/SingletonTrait.php | 24 ++++++ src/ZM/Utils/ZMUtil.php | 3 - src/ZM/global_functions.php | 32 ++++++-- .../AnnotationParserRegisterTest.php | 6 +- 35 files changed, 285 insertions(+), 174 deletions(-) create mode 100644 resources/html/subdir/index.html rename src/ZM/Annotation/Http/{After.php => HandleAfter.php} (80%) rename src/ZM/Annotation/Http/{Before.php => HandleBefore.php} (65%) rename src/ZM/Annotation/Swoole/{ZMSetup.php => OnSetup.php} (85%) rename src/ZM/Annotation/Swoole/{OnWorkerStart.php => OnStart.php} (86%) delete mode 100644 src/ZM/Annotation/Swoole/SwooleEventAfter.php rename src/ZM/Annotation/Swoole/{HandleEvent.php => SwooleHandler.php} (83%) create mode 100644 src/ZM/Utils/SingletonTrait.php diff --git a/composer.json b/composer.json index a043fc23..852f7116 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-b4", + "version": "2.0.0-b5", "extra": {}, "authors": [ { @@ -35,6 +35,10 @@ "symfony/routing": "^5.1", "symfony/polyfill-php80": "^1.20" }, + "suggest": { + "ext-ctype": "*", + "ext-mbstring": "*" + }, "autoload": { "psr-4": { "ZM\\": "src/ZM", @@ -57,4 +61,4 @@ "phpunit/phpunit": "^9.3", "swoole/ide-helper": "@dev" } -} \ No newline at end of file +} diff --git a/config/global.php b/config/global.php index eed3fef0..03832714 100644 --- a/config/global.php +++ b/config/global.php @@ -50,8 +50,6 @@ $config['sql_config'] = [ 'sql_username' => 'name', 'sql_database' => 'db_name', 'sql_password' => '', - 'sql_enable_cache' => true, - 'sql_reset_cache' => '0300', 'sql_options' => [ PDO::ATTR_STRINGIFY_FETCHES => false, PDO::ATTR_EMULATE_PREPARES => false @@ -115,7 +113,10 @@ $config['command_register_class'] = [ /** 服务器启用的外部第三方和内部插件 */ $config['modules'] = [ - 'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 + 'onebot' => [ + 'status' => true, + 'single_bot_mode' => false + ], // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; return $config; diff --git a/resources/html/subdir/index.html b/resources/html/subdir/index.html new file mode 100644 index 00000000..5c370979 --- /dev/null +++ b/resources/html/subdir/index.html @@ -0,0 +1,10 @@ + + + + + Example page + + +
+ + diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 40a41545..858ffe88 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -8,7 +8,9 @@ use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\RequestMapping; +use ZM\Event\EventDispatcher; use ZM\Store\Redis\ZMRedis; +use ZM\Utils\ZMUtil; /** * Class Hello @@ -40,11 +42,20 @@ class Hello } } + /** + * 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ + * @CQCommand(".reload",user_id=627577391) + */ + public function reload() { + ctx()->reply("重启中..."); + ZMUtil::reload(); + } + /** * @CQCommand("我是谁") */ public function whoami() { - $user = ctx()->getRobot()->setCallback(true)->getLoginInfo(); + $user = ctx()->getRobot()->getLoginInfo(); return "你是" . $user["data"]["nickname"] . ",QQ号是" . $user["data"]["user_id"]; } @@ -121,6 +132,14 @@ class Hello Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); } + /** + * 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰 + * @OnSwooleEvent("request",rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200) + */ + public function onRequest() { + EventDispatcher::interrupt(); + } + /** * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 * @OnSwooleEvent(type="open",rule="connectIsDefault()") diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php index 34e21c7b..d90d2570 100644 --- a/src/Module/Middleware/TimerMiddleware.php +++ b/src/Module/Middleware/TimerMiddleware.php @@ -2,8 +2,9 @@ namespace Module\Middleware; -use ZM\Annotation\Http\After; -use ZM\Annotation\Http\Before; +use ZM\Annotation\Http\HandleAfter; +use ZM\Annotation\Http\HandleBefore; +use ZM\Annotation\Http\HandleException; use ZM\Annotation\Http\MiddlewareClass; use ZM\Console\Console; use ZM\Http\MiddlewareInterface; @@ -19,7 +20,7 @@ class TimerMiddleware implements MiddlewareInterface private $starttime; /** - * @Before() + * @HandleBefore() * @return bool */ public function onBefore() { @@ -28,9 +29,16 @@ class TimerMiddleware implements MiddlewareInterface } /** - * @After() + * @HandleAfter() */ public function onAfter() { Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms."); } + + /** + * @HandleException(\Exception::class) + */ + public function onException() { + Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred."); + } } diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php index 126382bb..45230948 100644 --- a/src/ZM/API/ZMRobot.php +++ b/src/ZM/API/ZMRobot.php @@ -24,7 +24,7 @@ class ZMRobot /** @var ConnectionObject|null */ private $connection; - private $callback = null; + private $callback = true; private $prefix = 0; /** @@ -50,6 +50,9 @@ class ZMRobot return new ZMRobot($r[array_rand($r)]); } + public static function getFirst() { + } + /** * @return ZMRobot[] */ diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 6bcb7eaf..0024768a 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -9,7 +9,7 @@ use ZM\Console\Console; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; +use ZM\Annotation\Http\{HandleAfter, HandleBefore, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Module\Closed; use ZM\Utils\DataProvider; @@ -287,8 +287,8 @@ class AnnotationParser foreach ($reflection_class->getMethods() as $vss) { $method_annotations = $this->reader->getMethodAnnotations($vss); foreach ($method_annotations as $vsss) { - if ($vsss instanceof Before) $result["before"] = $vss->getName(); - if ($vsss instanceof After) $result["after"] = $vss->getName(); + if ($vsss instanceof HandleBefore) $result["before"] = $vss->getName(); + if ($vsss instanceof HandleAfter) $result["after"] = $vss->getName(); if ($vsss instanceof HandleException) { $result["exceptions"][$vsss->class_name] = $vss->getName(); } diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php index 6a6d4021..a7ec162b 100644 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -25,6 +25,8 @@ class CQCommand extends AnnotationBase implements Level public $start_with = ""; /** @var string */ public $end_with = ""; + /** @var string */ + public $keyword = ""; /** @var string[] */ public $alias = []; /** @var string */ diff --git a/src/ZM/Annotation/Http/After.php b/src/ZM/Annotation/Http/HandleAfter.php similarity index 80% rename from src/ZM/Annotation/Http/After.php rename to src/ZM/Annotation/Http/HandleAfter.php index ef24db54..9d2e43a4 100644 --- a/src/ZM/Annotation/Http/After.php +++ b/src/ZM/Annotation/Http/HandleAfter.php @@ -9,11 +9,11 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class After + * Class HandleAfter * @package ZM\Annotation\Http * @Annotation * @Target("METHOD") */ -class After extends AnnotationBase +class HandleAfter extends AnnotationBase { -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Http/Before.php b/src/ZM/Annotation/Http/HandleBefore.php similarity index 65% rename from src/ZM/Annotation/Http/Before.php rename to src/ZM/Annotation/Http/HandleBefore.php index f09cfc40..90a092a5 100644 --- a/src/ZM/Annotation/Http/Before.php +++ b/src/ZM/Annotation/Http/HandleBefore.php @@ -4,16 +4,15 @@ namespace ZM\Annotation\Http; -use Doctrine\Common\Annotations\Annotation\Required; use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class Before + * Class HandleBefore * @package ZM\Annotation\Http * @Annotation * @Target("METHOD") */ -class Before extends AnnotationBase +class HandleBefore extends AnnotationBase { -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Swoole/ZMSetup.php b/src/ZM/Annotation/Swoole/OnSetup.php similarity index 85% rename from src/ZM/Annotation/Swoole/ZMSetup.php rename to src/ZM/Annotation/Swoole/OnSetup.php index b3165937..654002b6 100644 --- a/src/ZM/Annotation/Swoole/ZMSetup.php +++ b/src/ZM/Annotation/Swoole/OnSetup.php @@ -13,6 +13,6 @@ use ZM\Annotation\AnnotationBase; * @Annotation * @Target("METHOD") */ -class ZMSetup extends AnnotationBase +class OnSetup extends AnnotationBase { } diff --git a/src/ZM/Annotation/Swoole/OnWorkerStart.php b/src/ZM/Annotation/Swoole/OnStart.php similarity index 86% rename from src/ZM/Annotation/Swoole/OnWorkerStart.php rename to src/ZM/Annotation/Swoole/OnStart.php index 1eb0f14d..e95e6275 100644 --- a/src/ZM/Annotation/Swoole/OnWorkerStart.php +++ b/src/ZM/Annotation/Swoole/OnStart.php @@ -12,7 +12,7 @@ use ZM\Annotation\AnnotationBase; * @Annotation * @Target("ALL") */ -class OnWorkerStart extends AnnotationBase +class OnStart extends AnnotationBase { /** * @var int diff --git a/src/ZM/Annotation/Swoole/SwooleEventAfter.php b/src/ZM/Annotation/Swoole/SwooleEventAfter.php deleted file mode 100644 index 6626a69e..00000000 --- a/src/ZM/Annotation/Swoole/SwooleEventAfter.php +++ /dev/null @@ -1,76 +0,0 @@ -type; - } - - /** - * @param string $type - */ - public function setType(string $type) { - $this->type = $type; - } - - /** - * @return string - */ - public function getRule(): string { - return $this->rule; - } - - /** - * @param string $rule - */ - public function setRule(string $rule) { - $this->rule = $rule; - } - - /** - * @return int - */ - public function getLevel(): int { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel(int $level) { - $this->level = $level; - } - - -} \ No newline at end of file diff --git a/src/ZM/Annotation/Swoole/HandleEvent.php b/src/ZM/Annotation/Swoole/SwooleHandler.php similarity index 83% rename from src/ZM/Annotation/Swoole/HandleEvent.php rename to src/ZM/Annotation/Swoole/SwooleHandler.php index 80a0fac1..d70fb7d0 100644 --- a/src/ZM/Annotation/Swoole/HandleEvent.php +++ b/src/ZM/Annotation/Swoole/SwooleHandler.php @@ -9,12 +9,12 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class HandleEvent + * Class SwooleHandler * @package ZM\Annotation\Swoole * @Annotation * @Target("METHOD") */ -class HandleEvent extends AnnotationBase +class SwooleHandler extends AnnotationBase { /** * @var string diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 08bece9a..f19ab656 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -68,9 +68,6 @@ class ConsoleApplication extends Application } } - if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { - die("Unable to find source directory.\nMaybe you need to run \"init\"?"); - } ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::env($args["env"] ?? ""); if (ZMConfig::get("global") === false) { diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 6ad6fa12..e1de0e1e 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -149,7 +149,7 @@ class Context implements ContextInterface } catch (Exception $e) { $r = false; } - if($r === false) { + if ($r === false) { throw new WaitTimeoutException($this, $timeout_prompt); } return $r["message"]; @@ -203,7 +203,6 @@ class Context implements ContextInterface switch ($mode) { case ZM_MATCH_ALL: $p = $arg; - array_shift($p); return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p)); case ZM_MATCH_NUMBER: foreach ($arg as $k => $v) { @@ -215,9 +214,9 @@ class Context implements ContextInterface } return $this->waitMessage($prompt_msg); case ZM_MATCH_FIRST: - if (isset($arg[1])) { - $a = $arg[1]; - array_splice($arg, 1, 1); + if (isset($arg[0])) { + $a = $arg[0]; + array_splice($arg, 0, 1); ctx()->setCache("match", $arg); return $a; } else { @@ -227,6 +226,22 @@ class Context implements ContextInterface throw new InvalidArgumentException(); } + /** + * @param string $prompt_msg + * @return int|mixed|string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function getNextArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_FIRST, $prompt_msg); } + + /** + * @param string $prompt_msg + * @return int|mixed|string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function getFullArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_ALL, $prompt_msg); } + public function cloneFromParent() { set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); return context(); diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index 94689d37..00cbda60 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -103,6 +103,10 @@ interface ContextInterface */ public function getArgs($mode, $prompt_msg); + public function getNextArg($prompt_msg = ""); + + public function getFullArg($prompt_msg = ""); + public function setCache($key, $value); /** diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 5f5339bf..8bbd3654 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -7,8 +7,12 @@ namespace ZM\Event; use Doctrine\Common\Annotations\AnnotationException; use Exception; use ZM\Annotation\AnnotationBase; -use ZM\Annotation\CQ\CQMetaEvent; +use ZM\Console\Console; use ZM\Exception\InterruptException; +use ZM\Exception\ZMException; +use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; +use ZM\Store\ZMAtomic; use ZM\Utils\ZMUtil; class EventDispatcher @@ -19,6 +23,10 @@ class EventDispatcher private $rule = null; /** @var null|callable */ private $return_func = null; + /** @var bool */ + private $log = false; + /** @var int */ + private $eid = 0; /** * @throws InterruptException @@ -27,8 +35,32 @@ class EventDispatcher throw new InterruptException('interrupt'); } + public static function enableEventTrace($event_class) { + SpinLock::lock("_event_trace"); + $list = LightCacheInside::get("wait_api", "event_trace"); + $list[$event_class] = true; + LightCacheInside::set("wait_api", "event_trace", $list); + SpinLock::unlock("_event_trace"); + } + + public static function disableEventTrace($event_class) { + SpinLock::lock("_event_trace"); + $list = LightCacheInside::get("wait_api", "event_trace"); + unset($list[$event_class]); + LightCacheInside::set("wait_api", "event_trace", $list); + SpinLock::unlock("_event_trace"); + } + public function __construct(string $class = '') { $this->class = $class; + try { + $this->eid = ZMAtomic::get("_event_id")->add(1); + $list = LightCacheInside::get("wait_api", "event_trace"); + } catch (ZMException $e) { + $list = []; + } + if (isset($list[$class])) $this->log = true; + if ($this->log) Console::verbose("[事件分发{$this->eid}] 开始分发事件: " . $class); } public function setRuleFunction(callable $rule = null) { @@ -43,10 +75,13 @@ class EventDispatcher public function dispatchEvents(...$params) { try { - foreach ((EventManager::$events[$this->class] ?? []) as $v) { $result = $this->dispatchEvent($v, $this->rule, ...$params); - if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result); + if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。"); + if ($result !== false && is_callable($this->return_func)) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ..."); + ($this->return_func)($result); + } } return true; } catch (InterruptException $e) { @@ -67,9 +102,15 @@ class EventDispatcher public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { $q_c = $v->class; $q_f = $v->method; - if ($rule_func !== null && !$rule_func($v)) return false; + if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在判断 " . $q_c . "::" . $q_f . " 方法下的 rule ..."); + if ($rule_func !== null && !$rule_func($v)) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 判断为 false, 拒绝执行此方法。"); + return false; + } + if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 为真,继续执行方法本身 ..."); if (isset(EventManager::$middleware_map[$q_c][$q_f])) { $middlewares = EventManager::$middleware_map[$q_c][$q_f]; + if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware:" . implode(", ", $middlewares)); $before_result = true; $r = []; foreach ($middlewares as $k => $middleware) { @@ -81,22 +122,34 @@ class EventDispatcher $r[$k]->class = $q_c; $r[$k]->method = $q_f; if (isset($middleware_obj["before"])) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ..."); $rs = $middleware_obj["before"]; $before_result = $r[$k]->$rs(...$params); - if ($before_result === false) break; + if ($before_result === false) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 false,停止执行原事件,开始执行下一事件。"); + break; + } else { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 true,继续执行原事件。"); + } } } if ($before_result) { try { $q_o = ZMUtil::getModInstance($q_c); + if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ..."); $result = $q_o->$q_f(...$params); } catch (Exception $e) { - if ($e instanceof InterruptException) throw $e; + if ($e instanceof InterruptException) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 检测到事件阻断调用,正在跳出事件分发器 ..."); + throw $e; + } + if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ..."); for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; if (!isset($middleware_obj["exceptions"])) continue; foreach ($middleware_obj["exceptions"] as $name => $method) { if ($e instanceof $name) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 的异常 " . get_class($e) . " 被 Middleware:" . $middlewares[$i] . " 下的 " . get_class($r[$i]) . "::" . $method . " 捕获。"); $r[$i]->$method($e); self::interrupt(); } @@ -107,7 +160,9 @@ class EventDispatcher for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; if (isset($middleware_obj["after"], $r[$i])) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ..."); $r[$i]->{$middleware_obj["after"]}(...$params); + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 后置事件执行完毕!"); } } return $result; @@ -115,6 +170,7 @@ class EventDispatcher return false; } else { $q_o = ZMUtil::getModInstance($q_c); + if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ..."); return $q_o->$q_f(...$params); } } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 6783e1be..ddb3827d 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -16,7 +16,7 @@ use Swoole\Process; use Swoole\Timer; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Http\RequestMapping; -use ZM\Annotation\Swoole\OnWorkerStart; +use ZM\Annotation\Swoole\OnStart; use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; @@ -24,7 +24,7 @@ use ZM\Console\Console; use Swoole\Http\Request; use Swoole\Server; use Swoole\WebSocket\Frame; -use ZM\Annotation\Swoole\HandleEvent; +use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\TermColor; use ZM\Context\Context; use ZM\Context\ContextInterface; @@ -33,6 +33,7 @@ use ZM\Exception\DbException; use ZM\Framework; use ZM\Http\Response; use ZM\Module\QQBot; +use ZM\Store\LightCacheInside; use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\Redis\ZMRedisPool; use ZM\Store\ZMBuf; @@ -44,7 +45,7 @@ use ZM\Utils\ZMUtil; class ServerEventHandler { /** - * @HandleEvent("start") + * @SwooleHandler("start") */ public function onStart() { global $terminal_id; @@ -87,14 +88,14 @@ class ServerEventHandler } /** - * @HandleEvent("shutdown") + * @SwooleHandler("shutdown") */ public function onShutdown() { Console::debug("正在关闭 Master 进程,pid=" . posix_getpid()); } /** - * @HandleEvent("WorkerStop") + * @SwooleHandler("WorkerStop") * @param $server * @param $worker_id */ @@ -103,7 +104,7 @@ class ServerEventHandler } /** - * @HandleEvent("WorkerStart") + * @SwooleHandler("WorkerStart") * @param Server $server * @param $worker_id */ @@ -205,12 +206,12 @@ class ServerEventHandler EventManager::registerTimerTick(); //启动计时器 //ZMBuf::unsetCache("wait_start"); set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); - $dispatcher = new EventDispatcher(OnWorkerStart::class); + $dispatcher = new EventDispatcher(OnStart::class); $dispatcher->setRuleFunction(function ($v) { return server()->worker_id === $v->worker_id || $v->worker_id === -1; }); $dispatcher->dispatchEvents($server, $worker_id); - Console::debug("@OnWorkerStart 执行完毕"); + Console::debug("@OnStart 执行完毕"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); @@ -243,7 +244,7 @@ class ServerEventHandler } /** - * @HandleEvent("message") + * @SwooleHandler("message") * @param $server * @param Frame $frame */ @@ -266,9 +267,9 @@ class ServerEventHandler } }); try { - $starttime = microtime(true); + //$starttime = microtime(true); $dispatcher->dispatchEvents($conn); - Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); + //Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); @@ -282,7 +283,7 @@ class ServerEventHandler } /** - * @HandleEvent("request") + * @SwooleHandler("request") * @param $request * @param $response */ @@ -356,7 +357,7 @@ class ServerEventHandler } /** - * @HandleEvent("open") + * @SwooleHandler("open") * @param $server * @param Request $request */ @@ -369,6 +370,7 @@ class ServerEventHandler $conn = ManagerGM::get($request->fd); set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]); $conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? ""); + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { @@ -380,6 +382,11 @@ class ServerEventHandler } }); try { + if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) { + if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", $request->fd); + } + } $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; @@ -394,7 +401,7 @@ class ServerEventHandler } /** - * @HandleEvent("close") + * @SwooleHandler("close") * @param $server * @param $fd */ @@ -404,6 +411,8 @@ class ServerEventHandler if ($conn === null) return; Console::debug("Calling Swoole \"close\" event from fd=" . $fd); set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]); + + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { @@ -415,6 +424,11 @@ class ServerEventHandler } }); try { + if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) { + if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", -1); + } + } $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; @@ -429,7 +443,7 @@ class ServerEventHandler } /** - * @HandleEvent("pipeMessage") + * @SwooleHandler("pipeMessage") * @param $server * @param $src_worker_id * @param $data @@ -462,7 +476,7 @@ class ServerEventHandler } /** - * @HandleEvent("task") + * @SwooleHandler("task") */ public function onTask() { } @@ -504,7 +518,7 @@ class ServerEventHandler //加载插件 $plugins = ZMConfig::get("global", "modules") ?? []; - if (!isset($plugins["onebot"])) $plugins["onebot"] = true; + if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false]; if ($plugins["onebot"]) { $obj = new OnSwooleEvent(); @@ -514,6 +528,11 @@ class ServerEventHandler $obj->level = 99999; $obj->rule = 'connectIsQQ()'; EventManager::addEvent(OnSwooleEvent::class, $obj); + if ($plugins["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", -1); + } else { + LightCacheInside::set("connect", "conn_fd", -2); + } } //TODO: 编写加载外部插件的方式 diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php index d663eacc..007d9ffe 100644 --- a/src/ZM/Exception/DbException.php +++ b/src/ZM/Exception/DbException.php @@ -4,9 +4,7 @@ namespace ZM\Exception; -use Exception; - class DbException extends ZMException { -} \ No newline at end of file +} diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php index f5d00aa8..b5b5a5f8 100644 --- a/src/ZM/Exception/InterruptException.php +++ b/src/ZM/Exception/InterruptException.php @@ -4,8 +4,6 @@ namespace ZM\Exception; -use Exception; - class InterruptException extends ZMException { diff --git a/src/ZM/Exception/InvalidArgumentException.php b/src/ZM/Exception/InvalidArgumentException.php index 7ccc3b70..0052a29e 100644 --- a/src/ZM/Exception/InvalidArgumentException.php +++ b/src/ZM/Exception/InvalidArgumentException.php @@ -4,9 +4,7 @@ namespace ZM\Exception; -use Exception; - class InvalidArgumentException extends ZMException { -} \ No newline at end of file +} diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php index f1bf753d..6f56e830 100644 --- a/src/ZM/Exception/NotInitializedException.php +++ b/src/ZM/Exception/NotInitializedException.php @@ -4,8 +4,6 @@ namespace ZM\Exception; -use Exception; - class NotInitializedException extends ZMException { diff --git a/src/ZM/Exception/RobotNotFoundException.php b/src/ZM/Exception/RobotNotFoundException.php index 9b734260..49880dda 100644 --- a/src/ZM/Exception/RobotNotFoundException.php +++ b/src/ZM/Exception/RobotNotFoundException.php @@ -4,7 +4,6 @@ namespace ZM\Exception; -use Exception; use Throwable; /** diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index c9d18255..76472561 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -4,7 +4,6 @@ namespace ZM\Exception; -use Exception; use Throwable; class WaitTimeoutException extends ZMException diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index b575733e..aef733f7 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -6,7 +6,7 @@ namespace ZM; use Doctrine\Common\Annotations\AnnotationReader; use Exception; -use ZM\Annotation\Swoole\ZMSetup; +use ZM\Annotation\Swoole\OnSetup; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; use ZM\Event\ServerEventHandler; @@ -20,7 +20,7 @@ use ReflectionException; use ReflectionMethod; use Swoole\Runtime; use Swoole\WebSocket\Server; -use ZM\Annotation\Swoole\HandleEvent; +use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\Console; use ZM\Utils\ZMUtil; @@ -67,7 +67,7 @@ class Framework self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); $this->server_set = ZMConfig::get("global", "swoole"); Console::init( - ZMConfig::get("global", "info_level"), + ZMConfig::get("global", "info_level") ?? 2, self::$server, $args["log-theme"] ?? "default", ($o = ZMConfig::get("console_color")) === false ? [] : $o @@ -150,11 +150,11 @@ class Framework $method_annotations = $reader->getMethodAnnotations($vs); if ($method_annotations != []) { $annotation = $method_annotations[0]; - if ($annotation instanceof HandleEvent) { + if ($annotation instanceof SwooleHandler) { $annotation->class = $v; $annotation->method = $vs->getName(); $event_list[strtolower($annotation->event)] = $annotation; - } elseif ($annotation instanceof ZMSetup) { + } elseif ($annotation instanceof OnSetup) { $annotation->class = $v; $annotation->method = $vs->getName(); $c = new $v(); diff --git a/src/ZM/Http/Response.php b/src/ZM/Http/Response.php index 2c937f89..c8cae4b8 100644 --- a/src/ZM/Http/Response.php +++ b/src/ZM/Http/Response.php @@ -4,6 +4,8 @@ namespace ZM\Http; +use ZM\Console\Console; + class Response { @@ -92,6 +94,7 @@ class Response */ public function status($http_code, $reason = null) { $this->status_code = $http_code; + Console::trace(); return $this->response->status($http_code, $reason); } @@ -184,7 +187,8 @@ class Response * @return mixed */ public function redirect($location, $http_code = null) { - return $this->redirect($location, $http_code); + $this->is_end = true; + return $this->response->redirect($location, $http_code); } /** diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 902c1db7..c37207a4 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -21,7 +21,6 @@ use ZM\Utils\CoMessage; /** * Class QQBot * @package ZM\Module - * @ExternalModule("onebot") */ class QQBot { @@ -80,12 +79,13 @@ class QQBot //分发CQCommand事件 $dispatcher = new EventDispatcher(CQCommand::class); $dispatcher->setRuleFunction(function (CQCommand $v) use ($word) { - if ($v->match == "" && $v->pattern == "" && $v->regex == "") return false; + if(array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) return false; elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) && ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) && ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType())) ) { if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) { + array_shift($word); ctx()->setCache("match", $word); return true; } elseif ($v->start_with != "" && mb_strpos(ctx()->getMessage(), $v->start_with) === 0) { @@ -94,6 +94,9 @@ class QQBot } elseif ($v->end_with != "" && strlen(ctx()->getMessage()) == (strripos(ctx()->getMessage(), $v->end_with) + strlen($v->end_with))) { ctx()->setCache("match", [substr(ctx()->getMessage(), 0, strripos(ctx()->getMessage(), $v->end_with))]); return true; + } elseif ($v->keyword != "" && mb_strpos(ctx()->getMessage(), $v->keyword) !== false) { + ctx()->setCache("match", explode($v->keyword, ctx()->getMessage())); + return true; }elseif ($v->pattern != "") { $match = matchArgs($v->pattern, ctx()->getMessage()); if($match !== false) { diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index b6f0c720..c28c61e5 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -92,7 +92,7 @@ class LightCache } elseif (is_bool($value)) { $data_type = "bool"; $value = json_encode($value); - }else { + } else { throw new Exception("Only can set string, array and int"); } try { @@ -126,7 +126,7 @@ class LightCache throw new Exception("Only can set string, array and int"); } try { - if(self::$kv_table->get($key) === false) return false; + if (self::$kv_table->get($key) === false) return false; return self::$kv_table->set($key, [ "value" => $value, "data_type" => $data_type @@ -170,7 +170,7 @@ class LightCache } public static function savePersistence() { - if(self::$kv_table === null) return; + if (self::$kv_table === null) return; $r = []; foreach (self::$kv_table as $k => $v) { if ($v["expire"] === -2) { @@ -179,8 +179,10 @@ class LightCache } } if(self::$config["persistence_path"] == "") return; - $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); - if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + if (file_exists(self::$config["persistence_path"])) { + $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); + if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + } } private static function checkExpire($key) { diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php index f3838cc7..c161eca0 100644 --- a/src/ZM/Store/LightCacheInside.php +++ b/src/ZM/Store/LightCacheInside.php @@ -6,6 +6,7 @@ namespace ZM\Store; use Exception; use Swoole\Table; +use ZM\Exception\ZMException; class LightCacheInside { @@ -15,9 +16,11 @@ class LightCacheInside public static $last_error = ''; public static function init() { - self::$kv_table["wait_api"] = new Table(2, 0); + self::$kv_table["wait_api"] = new Table(3, 0); self::$kv_table["wait_api"]->column("value", Table::TYPE_STRING, 65536); - $result = self::$kv_table["wait_api"]->create(); + self::$kv_table["connect"] = new Table(8, 0); + self::$kv_table["connect"]->column("value", Table::TYPE_STRING, 256); + $result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create(); if ($result === false) { self::$last_error = '系统内存不足,申请失败'; return $result; @@ -25,8 +28,14 @@ class LightCacheInside return $result; } + /** + * @param string $table + * @param string $key + * @return mixed|null + * @throws ZMException + */ public static function get(string $table, string $key) { - if (!isset(self::$kv_table[$table])) throw new Exception("not initialized LightCache"); + if (!isset(self::$kv_table[$table])) throw new ZMException("not initialized LightCache"); $r = self::$kv_table[$table]->get($key); return $r === false ? null : json_decode($r["value"], true); } @@ -36,10 +45,10 @@ class LightCacheInside * @param string $key * @param string|array|int $value * @return mixed - * @throws Exception + * @throws ZMException */ public static function set(string $table, string $key, $value) { - if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); try { return self::$kv_table[$table]->set($key, [ "value" => json_encode($value, 256) diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php index 850a465e..b4e88ff8 100644 --- a/src/ZM/Store/ZMAtomic.php +++ b/src/ZM/Store/ZMAtomic.php @@ -29,6 +29,7 @@ class ZMAtomic } self::$atomics["stop_signal"] = new Atomic(0); self::$atomics["wait_msg_id"] = new Atomic(0); + self::$atomics["_event_id"] = new Atomic(0); for ($i = 0; $i < 10; ++$i) { self::$atomics["_tmp_" . $i] = new Atomic(0); } diff --git a/src/ZM/Utils/SingletonTrait.php b/src/ZM/Utils/SingletonTrait.php new file mode 100644 index 00000000..a6649b61 --- /dev/null +++ b/src/ZM/Utils/SingletonTrait.php @@ -0,0 +1,24 @@ +connections as $v) { - server()->close($v); - } LightCache::savePersistence(); //DataProvider::saveBuffer(); Timer::clearAll(); diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index 605c74dd..3a4a6971 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -1,16 +1,20 @@ $v) { - foreach($v as $ks => $vs) { + foreach (EventManager::$events as $event => $v) { + foreach ($v as $ks => $vs) { //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL; - if($vs->class == $s["class"] && $vs->method == $s["function"]) { - $list[get_class($vs)][]=$vs; + if ($vs->class == $s["class"] && $vs->method == $s["function"]) { + $list[get_class($vs)][] = $vs; } } } @@ -227,7 +231,7 @@ function ctx() { } } -function debug($msg) { Console::debug($msg); } +function zm_debug($msg) { Console::debug($msg); } function onebot_target_id_name($message_type) { return ($message_type == "group" ? "group_id" : "user_id"); @@ -268,6 +272,22 @@ function server() { return Framework::$server; } +/** + * @return ZMRobot + * @throws RobotNotFoundException + * @throws ZMException + */ +function bot() { + if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) { + return ZMRobot::getRandom(); + } elseif ($conn != -1) { + if (($obj = ManagerGM::get($conn)) !== null) return new ZMRobot($obj); + else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!"); + } else { + throw new RobotNotFoundException("没有任何机器人连接到框架!"); + } +} + diff --git a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php index d09b2d4b..3ade094b 100644 --- a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php +++ b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php @@ -9,7 +9,7 @@ use Module\Example\Hello; use PHPUnit\Framework\TestCase; use ReflectionException; use ZM\Annotation\AnnotationParser; -use ZM\Annotation\Swoole\OnWorkerStart; +use ZM\Annotation\Swoole\OnStart; use ZM\Console\Console; class AnnotationParserRegisterTest extends TestCase @@ -34,8 +34,8 @@ class AnnotationParserRegisterTest extends TestCase public function testAnnotation() { ob_start(); $gen = $this->parser->generateAnnotationEvents(); - $m = $gen[OnWorkerStart::class][0]->method; - $class = $gen[OnWorkerStart::class][0]->class; + $m = $gen[OnStart::class][0]->method; + $class = $gen[OnStart::class][0]->class; $c = new $class(); try { $c->$m(); From 81365173d26ef8cbf48ca500003bf37a97266608 Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 20 Dec 2020 18:08:34 +0800 Subject: [PATCH 19/32] update to 2.0.0-b6 version add interrupt return_value to InterruptException --- src/ZM/Event/EventDispatcher.php | 9 +++++---- src/ZM/Exception/InterruptException.php | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 8bbd3654..0c5e8489 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -29,10 +29,11 @@ class EventDispatcher private $eid = 0; /** + * @param null $return_var * @throws InterruptException */ - public static function interrupt() { - throw new InterruptException('interrupt'); + public static function interrupt($return_var = null) { + throw new InterruptException($return_var); } public static function enableEventTrace($event_class) { @@ -85,7 +86,7 @@ class EventDispatcher } return true; } catch (InterruptException $e) { - return null; + return $e->return_var; } catch (AnnotationException $e) { return false; } @@ -95,9 +96,9 @@ class EventDispatcher * @param AnnotationBase|null $v * @param null $rule_func * @param mixed ...$params - * @return bool * @throws AnnotationException * @throws InterruptException + * @return mixed */ public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { $q_c = $v->class; diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php index b5b5a5f8..cb97d954 100644 --- a/src/ZM/Exception/InterruptException.php +++ b/src/ZM/Exception/InterruptException.php @@ -4,7 +4,14 @@ namespace ZM\Exception; +use Throwable; + class InterruptException extends ZMException { + public $return_var = null; + public function __construct($return_var = null, $message = "", $code = 0, Throwable $previous = null) { + parent::__construct($message, $code, $previous); + $this->return_var = $return_var; + } } From 42d9c97711f699b37ff88494501ea936cf6a79c1 Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 20 Dec 2020 18:40:26 +0800 Subject: [PATCH 20/32] update to 2.0.0-b7 version fix environment getter --- src/ZM/Command/RunServerCommand.php | 6 ++++-- src/ZM/ConsoleApplication.php | 2 +- src/ZM/Http/Response.php | 1 - 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 965f0765..1c8aadf6 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -7,6 +7,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use ZM\Config\ZMConfig; use ZM\Framework; class RunServerCommand extends Command @@ -37,10 +38,11 @@ class RunServerCommand extends Command protected function execute(InputInterface $input, OutputInterface $output) { if(($opt = $input->getOption("env")) !== null) { - if(!in_array($opt, ["production", "staging", "development"])) { - $output->writeln(" \"--env\" option only accept production, development and staging ! "); + if(!in_array($opt, ["production", "staging", "development", ""])) { + $output->writeln(" \"--env\" option only accept production, development, staging and [empty] ! "); return Command::FAILURE; } + ZMConfig::setEnv($opt); } // ... put here the code to run in your command // this method must return an integer number with the "exit status code" diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index f19ab656..f31d77a5 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -69,7 +69,7 @@ class ConsoleApplication extends Application } ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); - ZMConfig::env($args["env"] ?? ""); + ZMConfig::setEnv($args["env"] ?? ""); if (ZMConfig::get("global") === false) { echo ("Global config load failed: " . ZMConfig::$last_error."\nPlease init first!\n"); $this->add(new InitCommand()); diff --git a/src/ZM/Http/Response.php b/src/ZM/Http/Response.php index c8cae4b8..a304e88d 100644 --- a/src/ZM/Http/Response.php +++ b/src/ZM/Http/Response.php @@ -94,7 +94,6 @@ class Response */ public function status($http_code, $reason = null) { $this->status_code = $http_code; - Console::trace(); return $this->response->status($http_code, $reason); } From bc0bb9b6b0a84bb337101cc9572016ab5a82a5ac Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 20 Dec 2020 18:49:03 +0800 Subject: [PATCH 21/32] update to 2.0.0-b8 version fix environment conflict and remove custom commands --- config/global.php | 5 ---- src/ZM/Command/RunServerCommand.php | 2 -- src/ZM/ConsoleApplication.php | 45 ++++++++++------------------- src/ZM/Framework.php | 5 ++++ 4 files changed, 21 insertions(+), 36 deletions(-) diff --git a/config/global.php b/config/global.php index 03832714..ef75bf04 100644 --- a/config/global.php +++ b/config/global.php @@ -106,11 +106,6 @@ $config['server_event_handler_class'] = [ \ZM\Event\ServerEventHandler::class, ]; -/** 注册自定义指令的类 */ -$config['command_register_class'] = [ - //\Custom\Command\CustomCommand::class -]; - /** 服务器启用的外部第三方和内部插件 */ $config['modules'] = [ 'onebot' => [ diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 1c8aadf6..639b6091 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -7,7 +7,6 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Config\ZMConfig; use ZM\Framework; class RunServerCommand extends Command @@ -42,7 +41,6 @@ class RunServerCommand extends Command $output->writeln(" \"--env\" option only accept production, development, staging and [empty] ! "); return Command::FAILURE; } - ZMConfig::setEnv($opt); } // ... put here the code to run in your command // this method must return an integer number with the "exit status code" diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index f31d77a5..8e776c0a 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -5,27 +5,22 @@ namespace ZM; use Exception; -use Symfony\Component\Console\Command\Command; -use TypeError; use ZM\Command\InitCommand; use ZM\Command\PureHttpCommand; use ZM\Command\RunServerCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Config\ZMConfig; use ZM\Utils\DataProvider; class ConsoleApplication extends Application { - public function __construct(string $name = 'UNKNOWN') - { + public function __construct(string $name = 'UNKNOWN') { $version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN"; parent::__construct($name, $version); } - public function initEnv() - { + public function initEnv() { $this->selfCheck(); //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 @@ -68,24 +63,18 @@ class ConsoleApplication extends Application } } - ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); - ZMConfig::setEnv($args["env"] ?? ""); - if (ZMConfig::get("global") === false) { - echo ("Global config load failed: " . ZMConfig::$last_error."\nPlease init first!\n"); - $this->add(new InitCommand()); - } else { - $this->addCommands([ - new RunServerCommand(), //运行主服务的指令控制器 - new InitCommand(), //初始化用的,用于项目初始化和phar初始化 - new PureHttpCommand() //纯HTTP服务器指令 - ]); - $command_register = ZMConfig::get("global", "command_register_class") ?? []; - foreach ($command_register as $v) { - $obj = new $v(); - if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); - $this->add($obj); - } - } + $this->addCommands([ + new RunServerCommand(), //运行主服务的指令控制器 + new InitCommand(), //初始化用的,用于项目初始化和phar初始化 + new PureHttpCommand() //纯HTTP服务器指令 + ]); + /* + $command_register = ZMConfig::get("global", "command_register_class") ?? []; + foreach ($command_register as $v) { + $obj = new $v(); + if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); + $this->add($obj); + }*/ } /** @@ -93,8 +82,7 @@ class ConsoleApplication extends Application * @param OutputInterface|null $output * @return int */ - public function run(InputInterface $input = null, OutputInterface $output = null) - { + public function run(InputInterface $input = null, OutputInterface $output = null) { try { return parent::run($input, $output); } catch (Exception $e) { @@ -102,8 +90,7 @@ class ConsoleApplication extends Application } } - private function selfCheck() - { + private function selfCheck() { if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19"); if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index aef733f7..d9679063 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -48,6 +48,11 @@ class Framework include_once "global_defines.php"; ZMAtomic::init(); + ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); + ZMConfig::setEnv($args["env"] ?? ""); + if (ZMConfig::get("global") === false) { + die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + } try { ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [ [ From 619baf16913f1593289f6e489c60fa329125a389 Mon Sep 17 00:00:00 2001 From: jerry Date: Sun, 20 Dec 2020 19:15:28 +0800 Subject: [PATCH 22/32] update to 2.0.0-b9 version fix a little bug --- composer.json | 4 ++-- src/ZM/Framework.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 852f7116..69e11482 100644 --- a/composer.json +++ b/composer.json @@ -29,8 +29,8 @@ "symfony/polyfill-mbstring": "^1.20", "symfony/console": "^5.1", "zhamao/connection-manager": "*@dev", - "zhamao/console": "*@dev", - "zhamao/config": "*@dev", + "zhamao/console": "^1.0", + "zhamao/config": "^1.0", "zhamao/request": "*@dev", "symfony/routing": "^5.1", "symfony/polyfill-php80": "^1.20" diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index d9679063..b43512bb 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -47,12 +47,12 @@ class Framework //定义常量 include_once "global_defines.php"; - ZMAtomic::init(); ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::setEnv($args["env"] ?? ""); if (ZMConfig::get("global") === false) { die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); } + ZMAtomic::init(); try { ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [ [ From 4deb814ff2aceb7450488aa679a27b3e5d963aaf Mon Sep 17 00:00:00 2001 From: jerry Date: Mon, 21 Dec 2020 01:43:40 +0800 Subject: [PATCH 23/32] fix a little bug --- src/ZM/Event/ServerEventHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index ddb3827d..e1b75265 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -351,7 +351,7 @@ class ServerEventHandler else $response->end("Internal server error."); } - Console::error("Internal server error (500), caused by " . get_class($e)); + Console::error("Internal server error (500), caused by " . get_class($e).": ".$e->getMessage()); Console::log($e->getTraceAsString(), "gray"); } } From 9bd07e5a664e854d9f2f6624ff2904a0ff74a619 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 21 Dec 2020 16:56:36 +0800 Subject: [PATCH 24/32] update docs --- docs/guide/安装.md | 107 +++++++++++++++++++++------------- docs/guide/快速上手-机器人.md | 6 +- docs/guide/编写模块.md | 80 +++++++++++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 150 insertions(+), 44 deletions(-) create mode 100644 docs/guide/编写模块.md diff --git a/docs/guide/安装.md b/docs/guide/安装.md index 66ce0b4a..63f96036 100644 --- a/docs/guide/安装.md +++ b/docs/guide/安装.md @@ -2,9 +2,30 @@ > 这篇为炸毛框架以及环境的部署教程。 -## 主机手动部署环境 +框架部署分为环境部署和框架部署。框架部署非常简单,只需要通用的指令,下方主要说环境部署。 -### Ubuntu / Debian / Kali +## Docker 部署 PHP 环境 +如果你不想干扰主机的环境,可以使用 Docker 进行拉取框架适用的 PHP7 with Swoole Extension Docker Container。本框架安装教程中使用的 DockerHub 及 Dockerfile 构建文件所构建的容器均为独立的容器,和框架无关,此 Docker 也可以用作运行**其他基于 php-cli 模式的项目**。 + +方法一、直接拉取远程容器(推荐) +```bash +docker pull zmbot/swoole +``` + +方法二、从 Dockerfile 构建容器 +```bash +git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git +cd zhamao-swoole-docker/ +docker build -t zm . +``` + +!!! note "从 Dockerfile 构建容器的提示" + + 使用 Dockerfile 构建后,需要将下方所有的 `zmbot/swoole` 全部更换成 `zm`,或者你上方指令中的 `-t` 参数后方的名称,具体可以详情查阅 Docker 的文档。 + +## 主机部署 PHP 环境 + +### Debian 系列(Ubuntu、Kali ) 需要的系统内软件包为:`php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql wget composer` @@ -29,55 +50,50 @@ brew install php composer pecl install swoole ``` -## 手动部署框架 +### 其他 Linux 发行版 + +其他 Linux 发行版,如 CentOS,Fedora,Arch 等暂时还没有经过严格的测试需要哪些依赖,大体和 Ubuntu、Debian 系需要的依赖包差不多,可根据安装过程中报错提示依次安装,或者直接使用 Docker 环境。 + +## 安装框架 + +恭喜你,前方通过 Docker 或主机安装环境后可以开始构建框架的开发脚手架了! + +如果你是通过**主机安装 PHP 部署的环境**,下方是通过脚手架来构建项目的命令行。 ```bash -# 方法1: 从 composer 模板项目快速新建(前提是主机有安装 composer) -composer create-project zhamao/framework-starter 你的项目名称 -# 方法2: 从 composer 引入项目(前提是主机有安装 composer) -mkdir 你的项目名称 && cd 你的项目名称 -composer require zhamao/framework -vendor/bin/start init -``` - -## Docker 部署环境和框架 - -对于不想部署到主机环境的情况,推荐直接使用 Docker。 - -!!! Note "提示" - - 本框架内安装的 Docker 容器包括框架本身和开发者开发的模块代码等,此镜像也可用于其他 Swoole 框架的启动,框架本身是需要从 GitHub 拉取或 composer 拉取的。 - -### Docker 容器拉取和构建 - -```bash -# 自行构建 Swoole 环境容器 -git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git -cd zhamao-swoole-docker/ -docker build -t zm . -# 或者使用 dockerhub 构建好的镜像构建 Swoole 环境容器 -docker pull zmbot/swoole -``` - -### 使用 Docker 容器初始化 composer 和框架代码 - -```bash -# 使用 GitHub 克隆快速开始项目仓库 git clone https://github.com/zhamao-robot/zhamao-framework-starter.git -cd zhamao-framework-starter -docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zm composer update -# 如果使用 dockerhub 拉取的,把 `zm` 换成 `zmbot/swoole` 即可 +cd zhamao-framework-starter/ +composer update ``` +如果是通过 **Docker 部署的环境**,则需要在先克隆脚手架后在文件夹内使用 Docker 命令下的 `composer update`。 + +```bash +git clone https://github.com/zhamao-robot/zhamao-framework-starter.git +cd zhamao-framework-starter/ +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update +``` + +或者在 Docker 环境下,你可以直接使用如下方法拉取和快速启动一个最标准的框架。 + +```bash +git clone https://github.com/zhamao-robot/zhamao-framework-starter.git +cd zhamao-framework-starter +./run-docker.sh # 在正式版炸毛框架 v2 发布后可用,测试版暂不放出 +``` + + ## 启动框架 - +本地环境启动方式: ```bash -# 本地环境拉取 zhamao-framework-starter 后启动方式 cd zhamao-framework-starter vendor/bin/start server +``` -# 使用 Docker 启动 -docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zm vendor/bin/start server # 如果使用 dockerhub 拉取的,把 `zm` 换成 `zmbot/swoole` 即可 +使用 Docker 启动: +```bash +cd zhamao-framework-starter +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server ``` 启动后你会看到和下方类似的初始化内容,表明启动成功了 @@ -106,6 +122,13 @@ working_dir: /Users/jerry/project/git-project/zhamao-framework 单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问:http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。 +!!! note "安装和部署总结" + + 根据上方描述,此文档中剩余提到的所有 Bash 命令,如果使用 Docker 部署环境,则需要加上 Docker 环境的指令:`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`,如执行其他 Linux 指令(以查看 PHP 版本为例):`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole php -v`。 + ## 使用 IDE 等工具开发代码 -我们使用文本编辑器进行炸毛框架开发,在使用集成开发环境 **IDEA** 或 **PhpStorm** 时,推荐通过插件市场搜索并安装 **PHP Annotations** 插件以提供注解命名空间自动补全、注解属性代码提醒、注解类跳转等,非常有助于提升开发效率的功能。 \ No newline at end of file +我们使用文本编辑器进行炸毛框架开发,在使用集成开发环境 **IDEA** 或 **PhpStorm** 时,推荐通过插件市场搜索并安装 **PHP Annotations** 插件以提供注解命名空间自动补全、注解属性代码提醒、注解类跳转等,非常有助于提升开发效率的功能。 + +## 进阶环境部署和开发 +炸毛框架还支持更多种启动方式,如源码模式、守护进程模式,具体后续有关环境和部署的进阶教程,请查看 [进阶开发](/advanced/) 部分! \ No newline at end of file diff --git a/docs/guide/快速上手-机器人.md b/docs/guide/快速上手-机器人.md index 9a5674c4..f94df246 100644 --- a/docs/guide/快速上手-机器人.md +++ b/docs/guide/快速上手-机器人.md @@ -12,7 +12,9 @@ ## 机器人客户端 -机器人客户端是炸毛框架以外的程序或软件,目前炸毛框架支持的机器人客户端通信标准为 OneBot 标准,只要你的机器人客户端是 OneBot 标准的,就可以和炸毛框架进行无缝对接。 +开发之前请注意,**机器人客户端和框架相互独立!**故有关**机器人客户端**出现的问题请到对应机器人客户端开发者或 GitHub 项目中咨询和讨论,炸毛框架为对接机器人客户端的一个快速开发的框架。 + +机器人客户端是炸毛框架以外的程序或软件,目前炸毛框架支持的机器人客户端通信标准为 OneBot 标准(原 CQHTTP),只要你的机器人客户端是 OneBot 标准的,就可以和炸毛框架进行无缝对接。 OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)。 @@ -214,7 +216,7 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/) * @CQCommand("echo") */ public function repeat() { - $repeat = ctx()->getArgs(ZM_MATCH_ALL, "请输入你要回复的内容"); + $repeat = ctx()->getFullArg("请输入你要回复的内容"); ctx()->reply($repeat); //return $repeat; // 这样的效果等同于 ctx()->reply() } diff --git a/docs/guide/编写模块.md b/docs/guide/编写模块.md new file mode 100644 index 00000000..0d2453af --- /dev/null +++ b/docs/guide/编写模块.md @@ -0,0 +1,80 @@ +# 编写模块 + +到现在为止,我们还在使用框架的默认模块 `Example/Hello.php`,在开始编写自己的模块应用之前,我们先说明一些编写代码的约定。 + +## 加载模块 + +框架默认使用脚手架构建好后,目录结构大致为下面这样: + +```bash +zhamao-framework-starter/ +├── config/ # 项目的配置文件文件夹,如 global.php +├── src/ # 项目的主要源码目录 +│ ├── Module/ # 用户编写的模块目录 +│ │ └── Example/ # 模块文件夹名称 +│ │ └── Hello.php # 模块内的类 +│ └── Custom/ # 用户自定义的全局方法、全局注解类等存放的目录 +├── vendor/ # Composer 依赖加载目录 +└── composer.json # Composer 配置文件 +``` + +其中我们脚手架包含的默认模块 `Example` 下的 `Hello` 类,就是用户写模块的位置。你也可以根据实际情况,自行添加更多的模块文件夹甚至单文件模块。 + +需要注意的是,所有文件夹名称和 `.php` 文件必须遵循 [psr-4 规范](https://learnku.com/docs/psr/psr-4-autoloader/1608),简单来说,`src/` 目录下的文件夹,子文件夹要写成命名空间,比如默认框架中 `Example/` 下的 `.php` 文件的命名空间为 `namespace Module\Example;`,且一个 `.php` 文件推荐只包含一个 `class`、`trait` 或 `interface`。 + +```php +; +class ModuleA {} +``` + +!!! fail "警告" + 如果没有遵守上方的类和文件命名规则的话(文件名、文件夹名和命名空间的统一性),在加载框架时就会报错,无法找到对应的类。因为框架的注解解析依赖于 Composer 中 psr-4 规则的自动加载。 + +## 创建模块 +### 标准形式 +我们这里以 `Entertain` 娱乐模块的创建为例,新建一个内有 `Dice.php` 掷骰子功能的模块,目录结构如下,在 `Module/` 下新建文件夹 `Entertain/`,再在此子目录下新建 `Dice.php` 文件。 +```bash +zhamao-framework-starter/ +└── src/ + └── Module/ + └── Entertain/ + └── Dice.php +``` +新建的 PHP 文件按照如下方式编写: +```php + 机器人客户端 -> 连接客户端的框架(炸毛框架) +- 框架处理逻辑后返回给用户的消息 -> 机器人客户端 -> QQ用户 + +第一步,我们以框架这边的角度考虑,我们称之为“事件”,我们编写代码所做的就是要响应这一事件。 + +TODO:写到这里了。 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index c44b981a..6e29bef8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,6 +57,7 @@ nav: - 快速上手(HTTP篇): guide/快速上手-http.md - 选择聊天机器人实例: guide/OneBot实例.md - 基本配置: guide/基本配置.md + - 编写模块: guide/编写模块.md - 事件和注解: - 事件和注解: event/index.md - 框架组件: From e78485273d9b7cc11a4659abb95ee3f3caf176bd Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 22 Dec 2020 16:28:34 +0800 Subject: [PATCH 25/32] update docs --- docs/advanced/index.md | 3 ++ docs/advanced/to-v2.md | 39 +++++++++++++++++ docs/assets/css/extra.css | 85 +++++++++++++++++++++++++++++++++++++ docs/assets/extra.css | 9 ---- docs/event/index.md | 54 ++++++++++++++++++++++- docs/guide/快速上手-http.md | 3 +- docs/guide/注册事件响应.md | 66 ++++++++++++++++++++++++++++ docs/guide/编写模块.md | 12 ------ docs/index.md | 20 +++++++++ docs/javascripts/config.js | 50 +++++++++++++++++++++- mkdocs.yml | 6 ++- 11 files changed, 321 insertions(+), 26 deletions(-) create mode 100644 docs/advanced/index.md create mode 100644 docs/advanced/to-v2.md create mode 100644 docs/assets/css/extra.css delete mode 100644 docs/assets/extra.css create mode 100644 docs/guide/注册事件响应.md diff --git a/docs/advanced/index.md b/docs/advanced/index.md new file mode 100644 index 00000000..fa8e1a9d --- /dev/null +++ b/docs/advanced/index.md @@ -0,0 +1,3 @@ +# 进阶开发 +## 深入 +还没填坑,敬请期待! \ No newline at end of file diff --git a/docs/advanced/to-v2.md b/docs/advanced/to-v2.md new file mode 100644 index 00000000..5da3fc87 --- /dev/null +++ b/docs/advanced/to-v2.md @@ -0,0 +1,39 @@ +# 从炸毛框架 V1 升级 + +> 这里只写明可能在升级过程中会影响原先代码执行的部分,不包含新增的特性等。 + +### 需要改变命名空间的类 + +- `Framework\Console` -> `ZM\Console\Console` +- `Swlib\Util\SingletonTrait` -> `ZM\Utils\SingletonTrait` +- `ZM\Annotation\Http\Before` -> `ZM\Annotation\Http\HandleBefore` +- `ZM\Annotation\Http\After` -> `ZM\Annotation\Http\HandleAfter` +- `@SwooleEventAt` -> `@OnSwooleEvent` +- 删除 `@SwooleEventAfter` +- 删除 `ModBase` +- `@HandleEvent` -> `@SwooleHandler` +- `ZM\Utils\ZMRobot` -> `\ZM\API\ZMRobot` + +### 方法名称变更 + +- `ZM\Console::stackTrace()` -> `ZM\Console::trace()` + +### 注解的变化 + +`@OnSwooleEvent`(原 `@SwooleEventAt`)中,`rule` 参数不再是自定义语法的东西了(比如之前的 `connectType:qq` 之类的鸡肋语法),直接是可执行的 PHP 代码,比如 `3 == 4`,`connectIsQQ()` 之类的。 + +去除 `@CQAPISend`,因为目前没什么意义。 + +`@CQCommand` 中,`regexMatch` 变成 `pattern`,`fullMatch` 变成 `regex`,消除歧义(第一个是 * 号匹配符进行匹配的,第二个是标准的正则表达式匹配)。同时新增 `start_with`,`end_with`,`keyword` 平行选项。 + +`@OnTick` 注解新增第二个参数 `worker_id`,其中默认是 0,代表只在 `#0` 号工作进程上运行计时器。 + +### 中间件编写的改变 + +原先的 Middleware 是需要含有 `getName()` 方法才合法,现在不需要了,但是对 `@MiddlewareClass` 注解需要增加参数,也就是说原先 `getName()` 返回的名称现在需要写到 `@MiddlewareClass("xxx")` 这样的形式。 + +### ZMBuf 的变化 + +由于 2.0 框架使用了多进程模型,所以不能使用原先适用于单进程下全局变量的方式(ZMBuf)进行存取变量,所以 ZMBuf 下的所有方法都需要更改,其中 `get, set` 等对缓存操作的模型请根据 2.0 的文档变更使用 `Redis` 或内置的多进程共享内存可用的 `LightCache` 轻量缓存。 + +而获取全局配置文件,如 `global.php` 文件,也发生了变化,新框架引入了 `ZMConfig` 对象,可以快速地区分各类环境变量从而读取不同的配置文件。比如我们获取原先的 global 配置文件中的一项:`ZMBuf::globals("port")`,在 2.0 中需要使用 `ZMConfig::get("global", "port")` 方式。以此类推,`ZMBuf::config("xxx")` 也直接变为 `ZMConfig::get("xxx")` 了。 \ No newline at end of file diff --git a/docs/assets/css/extra.css b/docs/assets/css/extra.css new file mode 100644 index 00000000..585990c1 --- /dev/null +++ b/docs/assets/css/extra.css @@ -0,0 +1,85 @@ + +.md-header-nav__button.md-logo { + padding: .2rem; + margin: .2rem; +} + +.md-header-nav__button.md-logo img, .md-header-nav__button.md-logo svg { + width: 1.6rem; + height: 1.6rem; +} + +.doc-chat-container { + width: 100%; + min-height: 30px; + background-color: #f3f6f9; + padding: 12px; + margin-right: auto; + margin-left: auto; + box-shadow: 0 3px 1px -2px rgba(0,0,0,.2), 0 2px 2px 0 rgba(0,0,0,.14), 0 1px 5px 0 rgba(0,0,0,.12); +} + +.doc-chat-row { + margin: 0; + display: flex; + flex-wrap: wrap; + flex: 1 1 auto; + justify-content: flex-end; +} + +.doc-chat-row-robot { + justify-content: flex-start !important; +} + +.doc-chat-box { + position: relative; + width: fit-content; + max-width: 55%; + border-radius: .5rem; + padding: .4rem .6rem; + margin: .4rem .8rem; + background-color: #fff; + line-height: 1.5; + font-size: 16px; + outline: none; + overflow-wrap: break-word; + white-space: normal; + box-shadow: 0 2px 12px 0 rgba(0,0,0,.1); +} + +.doc-chat-box:after { + content: ""; + position: absolute; + right: auto; + top: 0; + width: 8px; + height: 12px; + color: #fff; + border: 0 solid transparent; + border-bottom: 7px solid; + border-radius: 0 0 8px 0; + left: calc(100% - 4px); + box-sizing: inherit; +} + +.doc-chat-box-robot:after { + content: ""; + position: absolute; + right: calc(100% - 4px); + top: 0; + width: 8px; + height: 12px; + color: #fff; + border: 0 solid transparent; + border-bottom: 7px solid; + border-radius: 0 0 0 8px; + left: auto; + box-sizing: inherit; +} + +.doc-chat-avatar { + background-color: aquamarine; + width: 36px !important; + height: 36px !important; + border-radius: 18px; +} \ No newline at end of file diff --git a/docs/assets/extra.css b/docs/assets/extra.css deleted file mode 100644 index 4b4b3cee..00000000 --- a/docs/assets/extra.css +++ /dev/null @@ -1,9 +0,0 @@ -.md-header-nav__button.md-logo { - padding: .2rem; - margin: .2rem; -} - -.md-header-nav__button.md-logo img, .md-header-nav__button.md-logo svg { - width: 1.6rem; - height: 1.6rem; -} \ No newline at end of file diff --git a/docs/event/index.md b/docs/event/index.md index 787224a4..c4955a2c 100644 --- a/docs/event/index.md +++ b/docs/event/index.md @@ -1,4 +1,56 @@ # 事件和注解 -还没写到这里,不着急 +## 注解事件概念 +我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 **你好** 的回复:**你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。 + +**注解**(Annotation)又称标注,Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 `ReflectionClass` 反射类解析 PHP 代码注释从而实现了自己的一套注解机制。如果你没有写过 Java,并且不了解注解是什么,你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP 代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。 + +在常见框架如 Spring,Swoft 等代码结构里面,注解更是其核心的存在。 + +在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如`message`,`notice`,`request` 等。 + +## 如何使用注解 + +就像我们日常开发写注释一样,只需在类、方法或成员变量上方按规则添加注释即可,这里以默认自带的 `Hello` 模块类为例子: + +```php + 机器人客户端 -> 连接客户端的框架(炸毛框架) +- 框架处理逻辑后返回给用户的消息 -> 机器人客户端 -> QQ用户 + +第一步,我们以框架这边的角度考虑,我们称之为“事件”,我们编写代码所做的就是要响应这一事件。 + +首先我们以一句简单的功能——查天气,我们要从零实现一个查天气的功能进行示范如何快捷有效地开发一个功能。 + +### 确定问法 + +我们首先要确定的用户问法是一般由我们自己定义,但最好贴合用户的自然语言来进行定义。比如我们这里提到的天气功能,用户一般就会询问“北京天气”,“北京天气怎么样”,“天气 北京”。 + +### 注册消息事件 + +我们以最简单的命令方式“天气 北京”进行处理。问法为参数化的,通过空格来分开,这也是炸毛框架默认支持最基本的聊天事件之一。我们通过上一部分的方式新建一个单文件模块 `Weather.php` 在 `src/Module` 目录下,并编写: + +```php +getNextArg("请告诉我你要查询的城市"); // 发送 “天气 北京”时,变量为“北京” + // 这里假设是天气API接口的对接,返回了天气的数据 + $weather = "2020年12月22日,晴,-2~9℃ blablabla"; + return "$city 天气情况:".$weather; + } +} +``` + +!!! note "提示" + 为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API,本教程的进阶一栏后期会详细编写如何对接一个天气 API 接口。 + +在上方代码编写完毕后,运行框架(或运行过程中终端输入 `reload`),然后使用机器人客户端连接到炸毛框架,即可实现我们的第一个功能。我们在代码中编写了一个**注解事件**:`@CQCommand`,此注解事件是用于接收用户的普通消息并切分成各类命令规则的一个注解事件绑定。代码中的注解事件省去了注解中的键名,也可以写作 `@CQCommand(match="天气")`。 + +这里注解事件的概念请看 [事件和注解](/event/) 一栏描述的概念即可。到这里,我们就完成一个可以处理命令 `天气 xxx` 的方法了! + +### 处理消息事件 + +第一行 `ctx()` 是炸毛框架内的上下文获取方式,每条用户聊天信息发过来,被炸毛框架收到,都会创建一次上下文,同时这次聊天的全部信息,比如用户的 ID(QQ 号码),发消息的时间,如果是群消息的话所在的群号等等,都被存到了上下文中。`ctx()` 获取的是一个上下文对象,内部有许多可操作上下文的方法,其中代码的 `getNextArg()`,作用是根据空格分隔获取命令中的下一个参数。 + +在之前我们知道:`天气 北京` 是我们发送的消息,我们要获取到用户发送的参数 `北京`。`getNextArg()` 是框架封装好的一个快速获取下一个参数的方法,我们这里直接使用它来获取。 + +对于 `getNextArg()` 中的文本,可为空,不为空的时候如果用户只发送天气两个字,机器人还是会响应,但是它会询问你这句话,然后你回复机器人“北京”,这里 `$city` 变量就接受到并赋值为“北京”了,代码会继续执行,和直接一次性发送机器人“天气 北京”是一个效果,此为框架封装的消息会话机制,以贴近自然会话的方式来编写代码逻辑。 + +最后,函数直接返回了一个字符串,作为事件的响应,炸毛框架会自动处理并调用机器人客户端的接口,最后返回给用户消息。这里也可以使用上下文的 `ctx()->reply("xxx")` 方法替代,不返回字符串。注意两者只能选择一种方式,取决于开发者的开发习惯。 + + +) 天气 北京 +( 北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla +) 天气 +( 请告诉我你要查询的城市 +) 北京 +( 北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla + diff --git a/docs/guide/编写模块.md b/docs/guide/编写模块.md index 0d2453af..d681d7d8 100644 --- a/docs/guide/编写模块.md +++ b/docs/guide/编写模块.md @@ -66,15 +66,3 @@ zhamao-framework-starter/ (暂未支持,敬请期待) -## 注册事件响应(机器人篇) - -现在模块已经创建完毕,我们可以开始编写实际代码了。本段以机器人会话为例子来讲述事件注册和响应,有关 HTTP 服务器等注册事件响应请看后面事件和注解章节。 - -首先知道,QQ 等聊天机器人的消息我们的处理逻辑为点到点的模式: - -- QQ 用户消息 -> 机器人客户端 -> 连接客户端的框架(炸毛框架) -- 框架处理逻辑后返回给用户的消息 -> 机器人客户端 -> QQ用户 - -第一步,我们以框架这边的角度考虑,我们称之为“事件”,我们编写代码所做的就是要响应这一事件。 - -TODO:写到这里了。 \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 930d6aa4..74ac20b5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,6 +2,8 @@ > 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。 +> 如果是从 v1.x 版本升级到 v2.x,[点我看升级指南](/advanced/to-v2/)。 + 炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。 框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 @@ -54,6 +56,7 @@ public function index() { ## 文档主题 +### 主题
@@ -72,6 +75,7 @@ public function index() { }) +### 主色调
@@ -96,6 +100,9 @@ public function index() {
+### 辅色调 +
+ + + diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index 067ae85b..d8bb350f 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -8,6 +8,26 @@ var _hmt = _hmt || []; s.parentNode.insertBefore(hm, s); })(); +function appendChatModule(id, chatDialogs) { + let insertDiv = document.getElementById(id); + let ss = ''; + ss += '
'; + for(let i of chatDialogs) { + if (i.role === 0) { + ss += '
\n' + + ' \n' + + '
' + i.msg + '
\n' + + '
'; + } else { + ss += '
\n' + + '
' + i.msg + '
\n' + + ' \n' + + '
'; + } + } + insertDiv.innerHTML = ss + '
'; +} + function getCookie(name) { var arr, reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)"); @@ -34,4 +54,32 @@ name.textContent = s_theme s_primary=getCookie("_primary_color"); document.body.setAttribute("data-md-color-primary", s_primary); var name2 = document.querySelector("#__code_2 code span:nth-child(7)"); -name2.textContent = s_primary.replace("-", " "); +if(s_primary !== null) name2.textContent = s_primary.replace("-", " "); + +s_accent=getCookie("_accent_color"); +document.body.setAttribute("data-md-color-accent", s_accent); +var name3 = document.querySelector("#__code_3 code span:nth-child(7)"); +if(s_accent !== null) name3.textContent = s_accent.replace("-", " "); + +setTimeout(() => { + let ls = document.querySelectorAll("chat-box"); + for(let i of ls) { + let final = '
'; + let dialogs = i.innerHTML.split("\n"); + for(let j of dialogs) { + if(j === '') continue; + if(j.substr(0, 2) === ') ') { + final += '
\n' + + '
' + j.substr(2) + '
\n' + + ' \n' + + '
'; + } else if (j.substr(0, 2) === '( ') { + final += '
\n' + + ' \n' + + '
' + j.substr(2) + '
\n' + + '
'; + } + } + i.innerHTML = final; + } +}, 500); diff --git a/mkdocs.yml b/mkdocs.yml index 6e29bef8..6ebf5149 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,7 +15,7 @@ extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js - javascripts/config.js extra_css: - - assets/extra.css + - assets/css/extra.css - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css markdown_extensions: - admonition @@ -58,12 +58,14 @@ nav: - 选择聊天机器人实例: guide/OneBot实例.md - 基本配置: guide/基本配置.md - 编写模块: guide/编写模块.md + - 注册事件响应: guide/注册事件响应.md - 事件和注解: - 事件和注解: event/index.md - 框架组件: - 框架组件: component/index.md - 进阶开发: - - Swoole 进阶: test.md + - 进阶开发: advanced/index.md + - 从 v1 升级: advanced/to-v2.md - FAQ: - FAQ: FAQ.md - 更新日志: From eeb952035ae02c86fc6a1e507158f59afb5365b3 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 22 Dec 2020 16:31:16 +0800 Subject: [PATCH 26/32] update docs --- docs/javascripts/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index d8bb350f..b6abc718 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -54,12 +54,12 @@ name.textContent = s_theme s_primary=getCookie("_primary_color"); document.body.setAttribute("data-md-color-primary", s_primary); var name2 = document.querySelector("#__code_2 code span:nth-child(7)"); -if(s_primary !== null) name2.textContent = s_primary.replace("-", " "); +if(s_primary !== null && name2 !== null) name2.textContent = s_primary.replace("-", " "); s_accent=getCookie("_accent_color"); document.body.setAttribute("data-md-color-accent", s_accent); var name3 = document.querySelector("#__code_3 code span:nth-child(7)"); -if(s_accent !== null) name3.textContent = s_accent.replace("-", " "); +if(s_accent !== null && name3 !== null) name3.textContent = s_accent.replace("-", " "); setTimeout(() => { let ls = document.querySelectorAll("chat-box"); From 64ec34d54d5216b7773a0445786b1111ba342d4a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 22 Dec 2020 16:35:22 +0800 Subject: [PATCH 27/32] update docs --- docs/assets/css/extra.css | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/assets/css/extra.css b/docs/assets/css/extra.css index 585990c1..08f85e37 100644 --- a/docs/assets/css/extra.css +++ b/docs/assets/css/extra.css @@ -32,6 +32,7 @@ } .doc-chat-box { + color: #000000de; position: relative; width: fit-content; max-width: 55%; From 58da6121216a9d2d277ce10e5d6f3167302b03b8 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 22 Dec 2020 16:39:37 +0800 Subject: [PATCH 28/32] update docs --- docs/assets/css/extra.css | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/assets/css/extra.css b/docs/assets/css/extra.css index 08f85e37..95e3a4da 100644 --- a/docs/assets/css/extra.css +++ b/docs/assets/css/extra.css @@ -10,6 +10,7 @@ } .doc-chat-container { + border-radius: 6px; width: 100%; min-height: 30px; background-color: #f3f6f9; From c15d320ef6b2b51d5ef73cb1e121f6a572aa5f98 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Tue, 22 Dec 2020 16:46:59 +0800 Subject: [PATCH 29/32] update docs --- docs/assets/css/extra.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/assets/css/extra.css b/docs/assets/css/extra.css index 95e3a4da..82895785 100644 --- a/docs/assets/css/extra.css +++ b/docs/assets/css/extra.css @@ -13,7 +13,8 @@ border-radius: 6px; width: 100%; min-height: 30px; - background-color: #f3f6f9; + /*noinspection CssUnresolvedCustomProperty*/ + background-color: var(--md-code-bg-color); padding: 12px; margin-right: auto; margin-left: auto; From 18c09beacbcf72fb3c492ded472c4b292632e4bf Mon Sep 17 00:00:00 2001 From: jerry Date: Wed, 23 Dec 2020 01:21:51 +0800 Subject: [PATCH 30/32] update docs --- docs/guide/index.md | 17 ---- docs/test.md | 25 ----- docs/update.md | 3 - docs/update/v1.md | 220 ++++++++++++++++++++++++++++++++++++++++++++ docs/update/v2.md | 3 + mkdocs.yml | 4 +- 6 files changed, 226 insertions(+), 46 deletions(-) delete mode 100644 docs/guide/index.md delete mode 100644 docs/test.md delete mode 100644 docs/update.md create mode 100644 docs/update/v1.md create mode 100644 docs/update/v2.md diff --git a/docs/guide/index.md b/docs/guide/index.md deleted file mode 100644 index 000ea345..00000000 --- a/docs/guide/index.md +++ /dev/null @@ -1,17 +0,0 @@ -# Welcome to MkDocs - -For full documentation visit [mkdocs.org](https://www.mkdocs.org). - -## Commands - -* `mkdocs new [dir-name]` - Create a new project. -* `mkdocs serve` - Start the live-reloading docs server. -* `mkdocs build` - Build the documentation site. -* `mkdocs -h` - Print help message and exit. - -## Project layout - - mkdocs.yml # The configuration file. - docs/ - index.md # The documentation homepage. - ... # Other markdown pages, images and other files. diff --git a/docs/test.md b/docs/test.md deleted file mode 100644 index 1096dab0..00000000 --- a/docs/test.md +++ /dev/null @@ -1,25 +0,0 @@ -# 还没写完 - -## 测试锚点1 - -哈哈哈哈哈 - -## 测试锚点2 - -哦吼 - -## 测试代码段 -```php -if(true) { - return "hahah"; -} -``` - -## 测试 MkDocs -!!! warning "Theme extension prerequisites" - - As the `custom_dir` variable is used for the theme extension process, - Material for MkDocs needs to be installed via `pip` and referenced with the - `name` parameter in `mkdocs.yml`. It will not work when cloning from `git`. - -哦豁 \ No newline at end of file diff --git a/docs/update.md b/docs/update.md deleted file mode 100644 index 70aca789..00000000 --- a/docs/update.md +++ /dev/null @@ -1,3 +0,0 @@ -# 更新日志 - -还没写到这里,不着急 \ No newline at end of file diff --git a/docs/update/v1.md b/docs/update/v1.md new file mode 100644 index 00000000..89d53221 --- /dev/null +++ b/docs/update/v1.md @@ -0,0 +1,220 @@ +# 更新日志(v1 版本) + +## v1.6.5 + +> 更新时间:2020.12.9 + +- 修复:版本号显示 +- 优化:依赖问题,减少对 PHP 扩展的依赖,转变为可选 + +## v1.6.4 + +> 更新时间:2020.12.9 + +- 修复:composer require模式下自动加载的问题 +- 优化:减少不是必需的依赖问题 + +## v1.6.3 + +> 更新时间:2020.11.15 + +- 修复:Response 对象使用 redirect 造成的递归报错 +- 修复:`document_index` 配置项在 `/` 路径下无法使用的 bug + +## v1.6.2 + +> 更新时间:2020.7.27 + +- 修复:不写配置 `server_event_handler_class` 项无法启动的 bug + +## v1.6.1 + +> 更新时间:2020.7.26 + +- 新增:`ZMRequest::downloadFile($url, $dst)` 方法,可直接将文件下载到本地 + +## v1.6 + +> 更新时间:2020.7.14 + +- 新增:现在可以对类修饰自定义的注解了 +- 修复:数据库操作 where 对象时产生的歧义 +- 新增:支持自定义任何 Swoole Server 事件的注解绑定,详见文档 +- 修复:多个中间件注解对类只生效最后一个的 bug + +❗ 下面是框架升级需要手动进行的变更: + +- 新版本由于引进了自定义 Swoole Server 事件的机制,对 global.php 全局配置文件有了变动,需要添加以下内容才能正常启动(旧版本升级新版本用户,新用户无需操作): + +```php +/** 注册 Swoole Server 事件注解的类列表 */ +$config['server_event_handler_class'] = [ + \Framework\ServerEventHandler::class, //默认不可删除,否则会不能使用框架 +]; +``` + +## v1.5.8 + +> 更新时间:2020.6.26 + +- 新增:`@CQCommand` 注解的 fullMatch 参数(全量正则表达式匹配) + +## v1.5.7 + +> 更新时间:2020.6.20 + +- 新增:ZM_BREAKPOINT 的短名称:BP +- 优化:终端连接器自动重连 +- 修复:语法错误时防止循环报错 + +## v1.5.6 + +> 更新时间:2020.6.15 + +- 新增:`@CQCommand` 注解支持 `message_type`,`user_id`,`group_id`,`discuss_id` 限定条件 +- 新增:PDO 数据库支持自定义 fetch_mode,可在 `global.php` 中的 `sql_config["sql_default_fetch_mode"]` 字段设置,也可以调用时 `DB::rawQuery("语句", [], PDO::FETCH_ASSOC);` 第三个参数可选 +- 🔴 废弃:`ModBase` 基类,基类继承机制将在 1.6 版本起完全删除 + +## v1.5.5 + +> 更新时间:2020.6.13 + +- 修复:`@SwooleEventAt("close")` 下不能使用 `ctx()->getConnection()` 获取链接对象的 bug +- 新增:init 命令,可在 `composer require zhamao/framework` 后使用 `vendor/bin/start init` 初始化项目目录结构和配置文件 +- 更新:默认模块新增机器人断开连接的回调事件 + +## v1.5.4 + +> 更新时间:2020.6.13 + +- 新增:`@CQCommand` 下支持 alias 参数 +- 更新:将 autoload 变为 composer autoload(需要重新 composer update) + +## v1.5.3 + +> 更新时间:2020.6.10 + +- 修复:在 Linux 系统下 Terminal 无法正常使用的 bug + +## v1.5.2 + +> 更新时间:2020.6.8 + +- 新增:`ZM_VERSION` 常量,对应为当前框架版本 +- 修复:部分链接不带 `/` 会导致 ZMRequest 模块报错的 bug + +## v1.5.1 + +> 更新时间:2020.6.5 + +- 新增:ZMRequest::request() 自定义构建 HTTP 请求方法 +- 修复:一个不会导致崩溃的 warning 提示 + +## v1.5 + +> 更新时间:2020.6.5 + +- 重要变更:支持从 composer 使用框架 +- 新增:数据库 Select 选择器支持 `count()` 方法 +- 修复:ZMRequest 中 https 和端口的指定顺序问题 +- 新增:ZMWebSocket 创建 WS 链接的轻量级客户端 +- 修复:数据库异常的捕获更改为 PDOException + +## v1.4 + +> 更新时间:2020.5.23 + +- 新增:自定义 motd +- 新增:debug_mode 下断点调试功能 +- 新增:`@OnSave` 注解,储存自动保存的变量时事件激活 +- 新增:Swoole 版本检测 +- 新增:全局函数,以 `zm_` 开头的,详情见文档 +- 新增:`@LoadBuffer` 注解,只加载内存不自动保存的变量 +- 新增:局部静态文件服务 +- 新增:mysqlnd 扩展状态检测 +- 更新:将终端输入更换为多进程 +- 更新:将数据库连接池变更为 Swoole 官方的连接池,需要 Swoole 版本 >= 4.4.13 +- 更新:提升注解绑定的事件函数的执行效率 +- 修复:上下文 `getConnection()` 的 fd 无法获取的 bug +- 修复:MySQL 长链接 gone away 自动重连的问题 +- 修复:MySQL 查询构造器无 WHERE 语句时会造成的 bug +- 修复:调整各项资源初始化前后顺序 + +不可逆修改:你需要重新执行一次 `composer update` 或重新拉取一次 Docker Image,因为 composer 依赖发生了变化。 + +## v1.3.1 + +> 更新时间:2020.5.10 + +- 修复:DataProvider 下 setJsonData 新建文件夹的问题 +- 优化:默认 / 页面显示 `Hello Zhamao!` 文字 +- 优化:Exception 和 Fatal error 报错机制的改进 +- 修复:计时器没有上下文环境,发不了 API 的 bug + +❗ 下面是框架升级需要手动进行的变更: + +- 更改 MySQL 客户端为原生 PDO mysqlnd,如果之前使用 Docker 启动,则需使用新的 Dockerfile 构建。如果安装在本机,需安装 php-mysql 扩展。本次更新不影响框架内的 API,不需要更改任何代码。 + +## v1.3.0 + +> 更新时间:2020.5.8 + +- 新增:上下文,具体更新都写到了文档里了! +- 修复:ZMRobot 的 `setPrefix()` 的严重错误 +- 优化:优化部分代码 +- 改动:现在你可以和任意事件的注解使用任意中间件啦,而且还支持多中间件 +- 新增:CQHTTP + 酷Q + 炸毛框架 的 Dockerfile +- 新增注解:`@CQAPISend`,`@CQAPIResponse`,是 API 调用后触发的事件,具体见文档说明 + +## v1.2.1 + +> 更新时间:2020.5.2 + +- 新增:phar 启动模式构建脚本,你可以直接拉取 phar 运行框架了! +- 优化:优化部分代码 + +## v1.2 + +> 更新时间:2020.4.29 + +- 新增:systemd 生成脚本、一键 daemonize 守护进程方式常驻后台 +- 新增:示例模块的注释 +- 重构:Console 模块,现在有准确的控制台输出分级功能了 +- 新增:`@OnTick` 注解,用于绑定定时器(毫秒级) +- 新增:`ZMRobot` 类,比调用 `CQAPI` 类发送 API 更方便,同时兼容最新版本的 `CQHTTP` 插件 +- 优化:使用键盘中断 `Ctrl+C`,不会丢失未保存的缓存数据了 +- 优化:完善上下文对象的方法 +- 新增:终端命令:`logtest`,测试输出的 log 类型 + +:exclamation:下面是框架模块开发中需要注意的或有不兼容的修改内容: + +- 修改:`global.php` 中原来的 `info_level` 默认数值需要改为 `2`,保证终端输出和原来一致 + +## v1.1.2 + +> 更新时间:2020.4.26 + +- 新增:静态文件服务器 +- 修复:`/` 路径的 Mapping 无法正常绑定的 bug + +## v1.1.1 + +> 更新时间:2020.4.26 + +- 新增:中间件对类的修饰 +- 新增:上下文对象对 IDE 的支持 +- 修复:数据库插入查询的愚蠢错误 +- 修复:数据库查询的 `value()` 不支持指定参数的 bug + +## v1.1.0 + +> 更新时间:2020.3.29 + +- 新增:中间件 `@Middleware` 功能 +- 修复:Websocket 链接关闭后未自动删除连接对象的bug + +## v1.0.0 + +> 更新时间:2020.3.19 + +正式版发布。 \ No newline at end of file diff --git a/docs/update/v2.md b/docs/update/v2.md new file mode 100644 index 00000000..dfe073bc --- /dev/null +++ b/docs/update/v2.md @@ -0,0 +1,3 @@ +# 更新日志(v2 版本) + +> 暂未发布正式版。 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 6ebf5149..d367b3ee 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -69,4 +69,6 @@ nav: - FAQ: - FAQ: FAQ.md - 更新日志: - - 更新日志: update.md + - 更新日志(v2): update/v2.md + - 更新日志(v1): update/v1.md + - 炸毛框架 v1: https://docs-v1.zhamao.xin/ From f361a675af5036cc924b8b89100b32019f45ddfb Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 23 Dec 2020 10:21:48 +0800 Subject: [PATCH 31/32] update README.md --- README.md | 76 +++++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 2c608d05..144f8e50 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,42 @@ [![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE) [![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework) [![Banner](https://img.shields.io/badge/CQHTTP-v11-black)]() -[![dev-version](https://img.shields.io/badge/dev--version-v2.0.0--b3-green)]() [![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid) [![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO) -
+
## 开发者注意 -**2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。** - -[![dev-version2](https://img.shields.io/badge/process-95%25-green)]() - +**开发者 QQ 群:670821194** +**当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!** +**2.0 版本如果有问题请第一时间加群反馈!** ## 简介 -zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 标准,它会对微信公众号等终端收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。 +炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。 -除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。 +框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 + +```php +/** + * @CQCommand("你好") + */ +public function hello() { + ctx()->reply("你好,我是炸毛!"); // 简单的命令式回复 +} +/** + * @RequestMapping("/index") + */ +public function index() { + return "

hello!

"; // 快速的 HTTP 服务开发 +} +``` ## 开始 -先安装环境,环境安装见下方文档。 -1. `composer create-project zhamao/framework-starter` 从模板新建基础文档结构进行使用 -2. 你也可以直接到 **Release** 中下载最新的 phar 包,放入文件夹后 `php server.phar` 快速启动框架 -3. 还可以使用 Dockerfile 构建 Docker 容器 +框架首先需要部署环境,可以参考下方文档中部署环境和框架的方法进行。 -## 文档 +## 文档(v2 版本) 查看文档:[https://docs-v2.zhamao.xin/](https://docs-v2.zhamao.xin/) 备用链接:[http://docs-v2.zhamao.me/](http://docs-v2.zhamao.me/) @@ -41,42 +51,20 @@ zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot ## 特点 - 支持多账号 +- 使用 Swoole 多工作进程机制和协程加持,尽可能简单的情况下提升了性能 - 灵活的注解事件绑定机制 - 支持下断点调试(Psysh) - 易用的上下文,模块内随处可用 -- 采用模块化编写,功能之间高内聚低耦合 +- 采用模块化编写,可单独拆装功能 - 常驻内存,全局缓存变量随处使用 -- 自带 MySQL 查询器、数据库连接池等数据库连接方案 +- 自带 MySQL、Refis 等数据库连接池等数据库连接方案 - 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口 - 静态文件服务器 -- 支持 phar 一键打包 -## 炸毛特色模块 +## 从 v1 升级 +炸毛框架 v2 相对 v1 版本改动了不少内容,其中包括框架底层机制、注解事件分发、调试、命名空间等变化,详情可查看上方文档。 -| 模块名称 | 说明 | 模块地址 | -| ------------------ | -------------------------------- | ------------------------------------------------------------ | -| 通用模块 | 图片上传和下载模块 | [zhamao-general-tools](https://github.com/zhamao-robot/zhamao-general-tools) | - -## 计划开发内容 -- [X] WebSocket测试脚本(客户端) -- [X] Session 和中间层管理模块 -- [X] 常驻服务脚本 -- [X] 一些常用的通用 API 例如经济(用户积分、亲密度等)的模块 -- [ ] 图灵机器人/腾讯AI 聊天模块 -- [ ] 分词模块(可能会放弃计划,因为目前好用的分词都是其他语言的) -- [ ] HTTP 过滤器、Auth 模块、完整的 MVC 兼容(可能会放弃计划,因为框架主打机器人开发) -- [X] Redis 连接池或开箱即用的相应功能内置 -- [X] 1.3 版本使用上下文代替 -- [X] 更好的 Logger,稳定和漂亮的控制台输出 -- [ ] 日志服务 -- [X] 框架支持 Phar 打包(可能会比较靠后支持) -- [X] 完整的单元测试(如果有需求则尽快开发) -- [X] 静态文件服务器 - -## 从 cqbot-swoole 升级 -目前新的框架采用了全新的注解机制,所以旧版的框架上写的模块到新框架需要重新编写。当然为了减少工作量,新的框架也最大限度地保留了旧版框架编写的风格,一般情况下根据新版框架的文档仅需修改少量地方即可完成重写。 - -旧版框架并入了 `old` 分支,如果想继续使用旧版框架请移步分支。升级过程中如果遇到问题可以找作者。 +如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。 ## 贡献和捐赠 如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。 @@ -88,6 +76,8 @@ zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot ### 支付宝 ![支付宝二维码](/resources/images/alipay_img.jpg) +如果你对我们的周边感兴趣,我们还有炸毛机器人定制 logo 的雨伞,详情咨询作者 QQ,我们会作为您捐助了本项目! + ## 关于 框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。 @@ -96,3 +86,7 @@ zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。 项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。 + +**注意**:在你使用 mirai 等 `AGPL-3.0` 协议的机器人软件与框架连接时,使用本框架需要将你编写或修改的部分使用 `AGPL-3.0` 协议重新分发。 + +![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg) \ No newline at end of file From 420f326f11aa3f29e84b5084e65f576d0395b056 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 23 Dec 2020 10:28:20 +0800 Subject: [PATCH 32/32] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 144f8e50..555caf4e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ ## 开发者注意 **开发者 QQ 群:670821194** + **当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!** **2.0 版本如果有问题请第一时间加群反馈!**