From f91d24aaaadda3b77641b43050f4d1debbcdda1c Mon Sep 17 00:00:00 2001 From: jerry Date: Tue, 29 Sep 2020 15:07:43 +0800 Subject: [PATCH] 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();