diff --git a/.gitignore b/.gitignore index 57d64bf1..6bc9a117 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,5 @@ package-lock.json ### ASDF ### /.tool-version + +.DS_Store diff --git a/composer.json b/composer.json index e9372fd6..f07cfc41 100644 --- a/composer.json +++ b/composer.json @@ -15,22 +15,20 @@ "require": { "php": "^7.2 || ^7.3 || ^7.4 || ^8.0 || ^8.1", "ext-json": "*", - "ext-posix": "*", "doctrine/dbal": "^2.13.1", "dragonmantank/cron-expression": "^3.3", "jelix/version": "^2.0", "koriym/attributes": "^1.0", "psr/container": "^2.0", "psy/psysh": "^0.11.2", - "symfony/console": "~6.0 || ~5.0 || ~4.0 || ~3.0", + "symfony/console": "~6.0 || ~5.0 || ~4.0", "symfony/polyfill-ctype": "^1.19", "symfony/polyfill-mbstring": "^1.19", "symfony/polyfill-php80": "^1.16", - "symfony/routing": "~6.0 || ~5.0 || ~4.0 || ~3.0", - "zhamao/connection-manager": "^1.0", - "zhamao/console": "^1.0", + "symfony/routing": "~6.0 || ~5.0 || ~4.0", "zhamao/logger": "dev-master", - "zhamao/request": "^1.1" + "zhamao/request": "^1.1", + "onebot/libonebot": "*@dev" }, "require-dev": { "brainmaestro/composer-git-hooks": "^2.8", @@ -58,7 +56,8 @@ "ZM\\": "src/ZM" }, "files": [ - "src/ZM/global_functions.php" + "src/Globals/global_functions.php", + "src/Globals/global_defines_app.php" ] }, "autoload-dev": { @@ -106,5 +105,11 @@ "analyse": "phpstan analyse --memory-limit 300M", "cs-fix": "php-cs-fixer fix", "test": "bin/phpunit-swoole --no-coverage" - } + }, + "repositories": [ + { + "type": "path", + "url": "/Users/jerry/project/onebot/php-libonebot" + } + ] } diff --git a/config/global.php b/config/global.php index 831a6ef0..ce72ac23 100644 --- a/config/global.php +++ b/config/global.php @@ -1,152 +1,60 @@ set参数 */ -$config['swoole'] = [ - 'log_file' => $config['crash_dir'] . 'swoole_error.log', - // 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量 - 'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode - 'max_coroutine' => 300000, - 'max_wait_time' => 5, - // 'task_worker_num' => 4, - // 'task_enable_coroutine' => true +/* 要启动的服务器监听端口及协议 */ +$config['servers'] = [ + [ + 'host' => '0.0.0.0', + 'port' => 20001, + 'type' => 'websocket', + ], + [ + 'host' => '0.0.0.0', + 'port' => 20002, + 'type' => 'http', + 'flag' => 20002, + ], + [ + 'host' => '0.0.0.0', + 'port' => 20003, + 'type' => 'http', + 'flag' => 20003, + ], ]; -/* 一些框架与框架运行时设置的调整 */ +/* Workerman 驱动相关配置 */ +$config['workerman_options'] = [ + 'worker_num' => 1, // 如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量 +]; + +/* Swoole 驱动相关配置 */ +$config['swoole_options'] = [ + 'coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL), // 协程 Hook 内容 + 'swoole_set' => [ + 'worker_num' => 1, // 如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量 + 'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode + 'max_coroutine' => 300000, // 允许最大的协程数 + 'max_wait_time' => 5, // 安全退出模式下允许等待 Worker 的最长秒数 + // 'task_worker_num' => 4, // 启动 TaskWorker 进程的数量(默认不启动) + // 'task_enable_coroutine' => true // TaskWorker 是否开启协程 + ], + 'swoole_server_mode' => SWOOLE_PROCESS, // Swoole Server 启动模式,默认为 SWOOLE_PROCESS +]; + +/* 框架本体运行时的一些可调配置 */ $config['runtime'] = [ - 'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL), - 'swoole_server_mode' => SWOOLE_PROCESS, - 'middleware_error_policy' => 1, 'reload_delay_time' => 800, - 'global_middleware_binding' => [], - 'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件 'annotation_reader_ignore' => [ // 设置注解解析器忽略的注解名或命名空间,防止解析到不该解析的 'name' => [ 'mixin', ], 'namespace' => [], ], -]; - -/* 轻量字符串缓存,默认开启 */ -$config['light_cache'] = [ - 'size' => 512, // 最多允许储存的条数(需要2的倍数) - 'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数) - 'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多) - 'persistence_path' => $config['zm_data'] . '_cache.json', - 'auto_save_interval' => 900, -]; - -/* 大容量跨进程变量存储(2.2.0可用) */ -$config['worker_cache'] = [ - 'worker' => 0, - 'transaction_timeout' => 30000, -]; - -/* MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ -$config['mysql_config'] = [ - 'host' => '', - 'port' => 3306, - 'unix_socket' => null, - 'username' => 'root', - 'password' => '123456', - 'dbname' => '', - 'charset' => 'utf8mb4', - 'pool_size' => 64, - 'options' => [ - PDO::ATTR_STRINGIFY_FETCHES => false, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - ], -]; - -/* Redis连接信息,host留空则启动时不创建Redis连接池 */ -$config['redis_config'] = [ - 'host' => '', - 'port' => 6379, - 'timeout' => 1, - 'db_index' => 0, - 'auth' => '', -]; - -/* onebot连接约定的token */ -$config['access_token'] = ''; - -/* HTTP服务器固定请求头的返回 */ -$config['http_header'] = [ - 'Server' => 'zhamao-framework', - 'Content-Type' => 'text/html; charset=utf-8', -]; - -/* HTTP服务器在指定状态码下回复的页面(默认) */ -$config['http_default_code_page'] = [ - '404' => '404.html', -]; - -/* zhamao-framework在框架启动时初始化的atomic们 */ -$config['init_atomics'] = [ - // 'custom_atomic_name' => 0, //自定义添加的Atomic -]; - -/* 终端日志显示等级(0-4) */ -$config['info_level'] = 2; - -/* 上下文接口类 implemented from ContextInterface */ -$config['context_class'] = \ZM\Context\Context::class; - -/* 静态文件访问 */ -$config['static_file_server'] = [ - 'status' => false, - 'document_root' => realpath(__DIR__ . '/../') . '/resources/html', - 'document_index' => [ - 'index.html', - ], -]; - -/* 机器人解析模块,关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */ -$config['onebot'] = [ - 'status' => true, - 'single_bot_mode' => false, - 'message_level' => 99, - 'message_convert_string' => true, - 'message_command_policy' => 'interrupt', -]; - -/* 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */ -$config['remote_terminal'] = [ - 'status' => false, - 'host' => '127.0.0.1', - 'port' => 20002, - 'token' => '', -]; - -/* 模块(插件)加载器的相关设置 */ -$config['module_loader'] = [ - 'enable_hotload' => false, - 'load_path' => $config['zm_data'] . 'modules', + 'timezone' => 'Asia/Shanghai', ]; return $config; diff --git a/config/global_old.php b/config/global_old.php new file mode 100644 index 00000000..831a6ef0 --- /dev/null +++ b/config/global_old.php @@ -0,0 +1,152 @@ +set参数 */ +$config['swoole'] = [ + 'log_file' => $config['crash_dir'] . 'swoole_error.log', + // 'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量 + 'dispatch_mode' => 2, // 包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode + 'max_coroutine' => 300000, + 'max_wait_time' => 5, + // 'task_worker_num' => 4, + // 'task_enable_coroutine' => true +]; + +/* 一些框架与框架运行时设置的调整 */ +$config['runtime'] = [ + 'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL), + 'swoole_server_mode' => SWOOLE_PROCESS, + 'middleware_error_policy' => 1, + 'reload_delay_time' => 800, + 'global_middleware_binding' => [], + 'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件 + 'annotation_reader_ignore' => [ // 设置注解解析器忽略的注解名或命名空间,防止解析到不该解析的 + 'name' => [ + 'mixin', + ], + 'namespace' => [], + ], +]; + +/* 轻量字符串缓存,默认开启 */ +$config['light_cache'] = [ + 'size' => 512, // 最多允许储存的条数(需要2的倍数) + 'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数) + 'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多) + 'persistence_path' => $config['zm_data'] . '_cache.json', + 'auto_save_interval' => 900, +]; + +/* 大容量跨进程变量存储(2.2.0可用) */ +$config['worker_cache'] = [ + 'worker' => 0, + 'transaction_timeout' => 30000, +]; + +/* MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ +$config['mysql_config'] = [ + 'host' => '', + 'port' => 3306, + 'unix_socket' => null, + 'username' => 'root', + 'password' => '123456', + 'dbname' => '', + 'charset' => 'utf8mb4', + 'pool_size' => 64, + 'options' => [ + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ], +]; + +/* Redis连接信息,host留空则启动时不创建Redis连接池 */ +$config['redis_config'] = [ + 'host' => '', + 'port' => 6379, + 'timeout' => 1, + 'db_index' => 0, + 'auth' => '', +]; + +/* onebot连接约定的token */ +$config['access_token'] = ''; + +/* HTTP服务器固定请求头的返回 */ +$config['http_header'] = [ + 'Server' => 'zhamao-framework', + 'Content-Type' => 'text/html; charset=utf-8', +]; + +/* HTTP服务器在指定状态码下回复的页面(默认) */ +$config['http_default_code_page'] = [ + '404' => '404.html', +]; + +/* zhamao-framework在框架启动时初始化的atomic们 */ +$config['init_atomics'] = [ + // 'custom_atomic_name' => 0, //自定义添加的Atomic +]; + +/* 终端日志显示等级(0-4) */ +$config['info_level'] = 2; + +/* 上下文接口类 implemented from ContextInterface */ +$config['context_class'] = \ZM\Context\Context::class; + +/* 静态文件访问 */ +$config['static_file_server'] = [ + 'status' => false, + 'document_root' => realpath(__DIR__ . '/../') . '/resources/html', + 'document_index' => [ + 'index.html', + ], +]; + +/* 机器人解析模块,关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */ +$config['onebot'] = [ + 'status' => true, + 'single_bot_mode' => false, + 'message_level' => 99, + 'message_convert_string' => true, + 'message_command_policy' => 'interrupt', +]; + +/* 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */ +$config['remote_terminal'] = [ + 'status' => false, + 'host' => '127.0.0.1', + 'port' => 20002, + 'token' => '', +]; + +/* 模块(插件)加载器的相关设置 */ +$config['module_loader'] = [ + 'enable_hotload' => false, + 'load_path' => $config['zm_data'] . 'modules', +]; + +return $config; diff --git a/phpstan.neon b/phpstan.neon index b6c908a9..dcff31ac 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -12,3 +12,4 @@ parameters: - SWOOLE_VERSION - ZM_TEST_LOG_DEBUG - _PHAR_STUB_ID + - LOAD_MODE diff --git a/src/Custom/Annotation/Example.php b/src/Custom/Annotation/Example.php deleted file mode 100644 index 0de28bac..00000000 --- a/src/Custom/Annotation/Example.php +++ /dev/null @@ -1,29 +0,0 @@ -str = $str; - } -} diff --git a/src/Custom/global_function.php b/src/Custom/global_function.php deleted file mode 100644 index 0142ee18..00000000 --- a/src/Custom/global_function.php +++ /dev/null @@ -1,10 +0,0 @@ - $v) { + if (is_dir($base_path . '/' . $v) && !in_array($v, $exclude_annotations)) { + $scan_paths[trim($k, '\\')] = $base_path . '/' . $v; + } + } + foreach (($composer['autoload-dev']['psr-4'] ?? []) as $k => $v) { + if (is_dir($base_path . '/' . $v) && !in_array($v, $exclude_annotations)) { + $scan_paths[trim($k, '\\')] = $base_path . '/' . $v; + } + } + $all_event_class = []; + foreach ($scan_paths as $namespace => $autoload_path) { + $all_event_class = array_merge($all_event_class, FileSystem::getClassesPsr4($autoload_path, $namespace)); + } + + $reader = new DualReader(new AnnotationReader(), new AttributeReader()); + $event_list = []; + $setup_list = []; + foreach ($all_event_class as $v) { + $reflection_class = new ReflectionClass($v); + $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); + foreach ($methods as $vs) { + $method_annotations = $reader->getMethodAnnotations($vs); + if ($method_annotations != []) { + $annotation = $method_annotations[0]; + if ($annotation instanceof OnSetup) { + $setup_list[] = [ + 'class' => $v, + 'method' => $vs->getName(), + ]; + } + } + } + } + return json_encode(['setup' => $setup_list, 'event' => $event_list]); + } catch (Throwable $e) { + $stderr = fopen('php://stderr', 'w'); + fwrite($stderr, zm_internal_errcode('E00031') . $e->getMessage() . ' in ' . $e->getFile() . ' at line ' . $e->getLine() . PHP_EOL); + fclose($stderr); + exit(1); + } +} + +// 在*nix等支持多进程环境的情况,可直接运行此文件,那么就执行 +if (debug_backtrace() === []) { + require((!is_dir(__DIR__ . '/../../vendor')) ? getcwd() : (__DIR__ . '/../..')) . '/vendor/autoload.php'; + echo _zm_setup_loader(); +} diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php deleted file mode 100644 index d7ee44cc..00000000 --- a/src/Module/Example/Hello.php +++ /dev/null @@ -1,264 +0,0 @@ -reply('重启中...'); - ZMUtil::reload(); - } - - /** - * @CQCommand("我是谁") - */ - public function whoami() - { - $bot = ctx()->getRobot()->getLoginInfo(); - $bot_id = $bot['data']['user_id']; - $r = OneBotV11::get($bot_id); - $QQid = ctx()->getUserId(); - $nick = $r->getStrangerInfo($QQid)['data']['nickname']; - return '你是' . $nick . ',QQ号是' . $QQid; - } - - /** - * 向机器人发送"你好啊",也可回复这句话 - * @CQCommand(match="你好",alias={"你好啊","你是谁"}) - */ - public function hello() - { - return '你好啊,我是由炸毛框架构建的机器人!'; - } - - /** - * 一个最基本的第三方 API 接口使用示例 - * @CQCommand("一言") - */ - public function hitokoto() - { - $api_result = ZMRequest::get('https://v1.hitokoto.cn/'); - if ($api_result === false) { - return '接口请求出错,请稍后再试!'; - } - $obj = json_decode($api_result, true); - if ($obj === null) { - return '接口解析出错!可能返回了非法数据!'; - } - return $obj['hitokoto'] . "\n----「" . $obj['from'] . '」'; - } - - /** - * 图灵机器人的内置实现,在tuling123.com申请一个apikey填入下方变量即可。 - * @CQCommand(start_with="机器人",end_with="机器人",message_type="group") - * @CQMessage(message_type="private",level=1) - */ - public function turingAPI() - { - $user_id = ctx()->getUserId(); - $api = ZMConfig::get('global', 'custom.turing_apikey') ?? ''; // 请在这里填入你的图灵机器人的apikey - if ($api === '') { - return false; - } // 如果没有填入apikey则此功能关闭 - if (property_exists($this, '_running_annotation') && ($this->_running_annotation instanceof CQCommand)) { - $msg = ctx()->getFullArg('我在!有什么事吗?'); - } else { - $msg = ctx()->getMessage(); - } - ctx()->setMessage($msg); - if (MessageUtil::matchCommand($msg, ctx()->getData())->status === false) { - return TuringAPI::getTuringMsg($msg, $user_id, $api); - } - QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache('level') + 1); - // 执行嵌套消息,递归层级+1 - return true; - } - - /** - * 响应at机器人的消息 - * @CQBefore("message") - */ - public function changeAt() - { - if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) { - $msg = str_replace(CQ::at(ctx()->getRobotId()), '', ctx()->getMessage()); - ctx()->setMessage('机器人' . $msg); - logger()->info(ctx()->getMessage()); - } - return true; - } - - /** - * 一个简单随机数的功能demo - * 问法1:随机数 1 20 - * 问法2:从1到20的随机数 - * @CQCommand("随机数") - * @CQCommand(pattern="*从*到*的随机数") - * @CommandArgument(name="num1",type="int",required=true) - * @CommandArgument(name="num2",type="int",required=true) - * @param mixed $num1 - * @param mixed $num2 - * @return string - */ - public function randNum($num1, $num2) - { - $a = min($num1, $num2); - $b = max($num1, $num2); - // 回复用户结果 - return '从' . $a . '到' . $b . '的随机数是:' . mt_rand($a, $b); - } - - /** - * 中间件测试的一个示例函数 - * @RequestMapping("/httpTimer") - * @Middleware("timer") - */ - public function timer() - { - return 'This page is used as testing TimerMiddleware! Do not use it in production.'; - } - - /** - * 默认示例页面 - * @RequestMapping("/index") - * @RequestMapping("/") - */ - public function index() - { - return 'Hello Zhamao!'; - } - - /** - * 使用自定义参数的路由参数 - * @RequestMapping("/whoami/{name}") - * - * @param array $param 参数 - * @return string 返回的 HTML Body - */ - public function paramGet(array $param = []): string - { - return 'Hello, ' . $param['name']; - } - - /** - * 在机器人连接后向终端输出信息 - * @OnOpenEvent("qq") - * - * @param ConnectionObject $conn WebSocket 连接对象 - */ - public function onConnect(ConnectionObject $conn) - { - logger()->info('机器人 ' . $conn->getOption('connect_id') . ' 已连接!'); - } - - /** - * 在机器人断开连接后向终端输出信息 - * @OnCloseEvent("qq") - */ - public function onDisconnect(ConnectionObject $conn) - { - logger()->info('机器人 ' . $conn->getOption('connect_id') . ' 已断开连接!'); - } - - /** - * 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰 - * @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200) - * - * @throws InterruptException - */ - public function onRequest() - { - EventDispatcher::interrupt(); - } - - /** - * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 - * @OnOpenEvent("default") - */ - public function closeUnknownConn() - { - logger()->info('发现了未知的 Websocket 连接,正在断开'); - server()->disconnect(ctx()->getConnection()->getFd()); - } - - /** - * 输出帮助信息 - * - * @CQCommand("帮助") - */ - #[CQCommand('帮助')] - public function help(): string - { - $util = resolve(CommandInfoUtil::class); - $commands = $util->get(); - $helps = array_map(static function ($command) use ($util) { - return $util->getHelp($command['id']); - }, $commands); - array_unshift($helps, '帮助:'); - return implode("\n", $helps); - } - - /** - * @CQCommand("proxy") - */ - #[CQCommand('proxy')] - public function proxy() - { - bot()->all()->allGroups()->sendGroupMsg(0, ctx()->getMessage()); - } - - /** - * 欢迎来到容器时代 - * - * @param Context $context 通过依赖注入实现的 - * - * @CQCommand("容器你好") - */ - public function welcomeToContainerAge(Context $context) - { - $context->reply('欢迎来到容器时代'); - } -} diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php deleted file mode 100644 index 65d3c02a..00000000 --- a/src/Module/Middleware/TimerMiddleware.php +++ /dev/null @@ -1,49 +0,0 @@ -starttime = microtime(true); - return true; - } - - /** - * @HandleAfter() - */ - public function onAfter() - { - logger()->info('Using ' . round((microtime(true) - $this->starttime) * 1000, 3) . ' ms.'); - } - - /** - * @HandleException(\Exception::class) - * @throws Exception - */ - public function onException(Exception $e) - { - logger()->error('Using ' . round((microtime(true) - $this->starttime) * 1000, 3) . ' ms but an Exception occurred.'); - throw $e; - } -} diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php deleted file mode 100644 index 5e4b5cf3..00000000 --- a/src/ZM/API/CQ.php +++ /dev/null @@ -1,433 +0,0 @@ - $qq]); - } - - /** - * 发送QQ原生表情 - * @param int|string $id 表情ID - * @return string CQ码 - */ - public static function face($id): string - { - return self::buildCQ('face', ['id' => $id]); - } - - /** - * 发送图片 - * @param string $file 文件的路径、URL或者base64编码的图片数据 - * @param bool $cache 是否缓存(默认为true) - * @param bool $flash 是否闪照(默认为false) - * @param bool $proxy 是否使用代理(默认为true) - * @param int $timeout 超时时间(默认不超时) - * @return string CQ码 - */ - public static function image(string $file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1): string - { - $optional_values = [ - 'cache' => !$cache ? 'cache=0' : '', - 'flash' => $flash ? 'type=flash' : '', - 'proxy' => !$proxy ? 'proxy=false' : '', - 'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '', - ]; - return self::buildCQ('image', ['file' => $file], $optional_values); - } - - /** - * 发送语音 - * @param string $file 文件的路径、URL或者base64编码的语音数据 - * @param bool $magic 是否加特技(默认为false) - * @param bool $cache 是否缓存(默认为true) - * @param bool $proxy 是否使用代理(默认为true) - * @param int $timeout 超时时间(默认不超时) - * @return string CQ码 - */ - public static function record(string $file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1): string - { - $optional_values = [ - 'magic' => $magic ? 'magic=true' : '', - 'cache' => !$cache ? 'cache=0' : '', - 'proxy' => !$proxy ? 'proxy=false' : '', - 'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '', - ]; - return self::buildCQ('record', ['file' => $file], $optional_values); - } - - /** - * 发送短视频 - * @param string $file 文件的路径、URL或者base64编码的短视频数据 - * @param bool $cache 是否缓存(默认为true) - * @param bool $proxy 是否使用代理(默认为true) - * @param int $timeout 超时时间(默认不超时) - * @return string CQ码 - */ - public static function video(string $file, bool $cache = true, bool $proxy = true, int $timeout = -1): string - { - $optional_values = [ - 'cache' => !$cache ? 'cache=0' : '', - 'proxy' => !$proxy ? 'proxy=false' : '', - 'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '', - ]; - return self::buildCQ('video', ['file' => $file], $optional_values); - } - - /** - * 发送投掷骰子(只能在单条回复中单独使用) - * @return string CQ码 - */ - public static function rps(): string - { - return '[CQ:rps]'; - } - - /** - * 发送掷骰子表情(只能在单条回复中单独使用) - * @return string CQ码 - */ - public static function dice(): string - { - return '[CQ:dice]'; - } - - /** - * 戳一戳(原窗口抖动,仅支持好友消息使用) - * @return string CQ码 - */ - public static function shake(): string - { - return '[CQ:shake]'; - } - - /** - * 发送新的戳一戳 - * @param int|string $type 焯一戳类型 - * @param int|string $id 戳一戳ID号 - * @param string $name 戳一戳名称(可选) - * @return string CQ码 - */ - public static function poke($type, $id, string $name = ''): string - { - $optional_values = [ - 'name' => $name ? 'name=' . $name : '', - ]; - return self::buildCQ('poke', ['type' => $type, 'id' => $id], $optional_values); - } - - /** - * 发送匿名消息 - * @param int $ignore 是否忽略错误(默认为1,0表示不忽略错误) - * @return string CQ码 - */ - public static function anonymous(int $ignore = 1): string - { - return self::buildCQ('anonymous', [], ['ignore' => $ignore != 1 ? 'ignore=0' : '']); - } - - /** - * 发送链接分享(只能在单条回复中单独使用) - * @param string $url 分享地址 - * @param string $title 标题 - * @param null|string $content 卡片内容(可选) - * @param null|string $image 卡片图片(可选) - * @return string CQ码 - */ - public static function share(string $url, string $title, ?string $content = null, ?string $image = null): string - { - $optional_values = [ - 'content' => $content ? 'content=' . self::encode($content, true) : '', - 'image' => $image ? 'image=' . self::encode($image, true) : '', - ]; - return self::buildCQ('share', ['url' => $url, 'title' => $title], $optional_values); - } - - /** - * 发送好友或群推荐名片 - * @param int|string $type 名片类型 - * @param int|string $id 好友或群ID - * @return string CQ码 - */ - public static function contact($type, $id): string - { - return self::buildCQ('contact', ['type' => $type, 'id' => $id]); - } - - /** - * 发送位置 - * @param float|string $lat 纬度 - * @param float|string $lon 经度 - * @param string $title 标题(可选) - * @param string $content 卡片内容(可选) - * @return string CQ码 - */ - public static function location($lat, $lon, string $title = '', string $content = ''): string - { - $optional_values = [ - 'title' => $title ? 'title=' . self::encode($title, true) : '', - 'content' => $content ? 'content=' . self::encode($content, true) : '', - ]; - return self::buildCQ('location', ['lat' => $lat, 'lon' => $lon], $optional_values); - } - - /** - * 发送音乐分享(只能在单条回复中单独使用) - * - * qq、163、xiami为内置分享,需要先通过搜索功能获取id后使用 - * - * @param string $type 分享类型(仅限 `qq`、`163`、`xiami` 或 `custom`) - * @param int|string $id_or_url 当分享类型不是 `custom` 时,表示的是分享音乐的ID(需要先通过搜索功能获取id后使用),反之表示的是音乐卡片点入的链接 - * @param null|string $audio 当分享类型是 `custom` 时,表示为音乐(如mp3文件)的HTTP链接地址(不可为空) - * @param null|string $title 当分享类型是 `custom` 时,表示为音乐卡片的标题,建议12字以内(不可为空) - * @param null|string $content 当分享类型是 `custom` 时,表示为音乐卡片的简介(可忽略) - * @param null|string $image 当分享类型是 `custom` 时,表示为音乐卡片的图片链接地址(可忽略) - * @return string CQ码 - */ - public static function music(string $type, $id_or_url, ?string $audio = null, ?string $title = null, ?string $content = null, ?string $image = null): string - { - switch ($type) { - case 'qq': - case '163': - case 'xiami': - return self::buildCQ('music', ['type' => $type, 'id' => $id_or_url]); - case 'custom': - if ($title === null || $audio === null) { - logger()->warning(zm_internal_errcode('E00035') . '传入CQ码实例的标题和音频链接不能为空!'); - return ' '; - } - $optional_values = [ - 'content' => $content ? 'content=' . self::encode($content, true) : '', - 'image' => $image ? 'image=' . self::encode($image, true) : '', - ]; - return self::buildCQ('music', ['type' => 'custom', 'url' => $id_or_url, 'audio' => $audio, 'title' => $title], $optional_values); - default: - logger()->warning(zm_internal_errcode('E00035') . "传入的music type({$type})错误!"); - return ' '; - } - } - - /** - * 合并转发消息 - * @param int|string $id 合并转发ID, 需要通过 `/get_forward_msg` API获取转发的具体内容 - * @return string CQ码 - */ - public static function forward($id): string - { - return self::buildCQ('forward', ['id' => $id]); - } - - /** - * 合并转发消息节点 - * 特殊说明: 需要使用单独的API /send_group_forward_msg 发送, 并且由于消息段较为复杂, 仅支持Array形式入参。 - * 如果引用消息和自定义消息同时出现, 实际查看顺序将取消息段顺序。 - * 另外按 CQHTTP 文档说明, data 应全为字符串, 但由于需要接收message 类型的消息, 所以 仅限此Type的content字段 支持Array套娃 - * @param int|string $user_id 转发消息id - * @param string $nickname 发送者显示名字 - * @param string $content 具体消息 - * @return string CQ码 - * @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送 - */ - public static function node($user_id, string $nickname, string $content): string - { - return self::buildCQ('node', ['user_id' => $user_id, 'nickname' => $nickname, 'content' => $content]); - } - - /** - * XML消息 - * @param string $data xml内容, xml中的value部分 - * @return string CQ码 - */ - public static function xml(string $data): string - { - return self::buildCQ('xml', ['data' => $data]); - } - - /** - * JSON消息 - * @param string $data json内容 - * @param int $resid 0为走小程序通道,其他值为富文本通道(默认为0) - * @return string CQ码 - */ - public static function json(string $data, int $resid = 0): string - { - return self::buildCQ('json', ['data' => $data, 'resid' => $resid]); - } - - /** - * 返回一个自定义扩展的CQ码(支持自定义类型和参数) - * @param string $type_name CQ码类型名称 - * @param array $params 参数 - * @return string CQ码 - */ - public static function _custom(string $type_name, array $params): string - { - return self::buildCQ($type_name, $params); - } - - /** - * 反转义字符串中的CQ码敏感符号 - * @param int|string|Stringable $msg 字符串 - * @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true - * @return string 转义后的CQ码 - */ - public static function decode($msg, bool $is_content = false): string - { - $msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg); - if ($is_content) { - $msg = str_replace(',', ',', $msg); - } - return $msg; - } - - /** - * 简单反转义替换CQ码的方括号 - * @param int|string|Stringable $str 字符串 - * @return string 字符串 - */ - public static function replace($str): string - { - $str = str_replace('{{', '[', (string) $str); - return str_replace('}}', ']', $str); - } - - /** - * 转义CQ码的特殊字符,同encode - * @param int|string|Stringable $msg 字符串 - * @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true - * @return string 转义后的CQ码 - */ - public static function escape($msg, bool $is_content = false): string - { - $msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg); - if ($is_content) { - $msg = str_replace(',', ',', $msg); - } - return $msg; - } - - /** - * 转义CQ码的特殊字符 - * @param int|string|Stringable $msg 字符串 - * @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true - * @return string 转义后的CQ码 - */ - public static function encode($msg, bool $is_content = false): string - { - $msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg); - if ($is_content) { - $msg = str_replace(',', ',', $msg); - } - return $msg; - } - - /** - * 移除消息中所有的CQ码并返回移除CQ码后的消息 - * @param string $msg 消息 - * @return string 消息内容 - */ - public static function removeCQ(string $msg): string - { - $final = ''; - $last_end = 0; - foreach (self::getAllCQ($msg) as $v) { - $final .= mb_substr($msg, $last_end, $v['start'] - $last_end); - $last_end = $v['end'] + 1; - } - $final .= mb_substr($msg, $last_end); - return $final; - } - - /** - * 获取消息中第一个CQ码 - * @param string $msg 消息内容 - * @param bool $is_object 是否以对象形式返回,如果为False的话,返回数组形式(默认为false) - * @return null|array|CQObject 返回的CQ码(数组或对象) - */ - public static function getCQ(string $msg, bool $is_object = false) - { - if (($head = mb_strpos($msg, '[CQ:')) !== false) { - $key_offset = mb_substr($msg, $head); - $close = mb_strpos($key_offset, ']'); - if ($close === false) { - return null; - } - $content = mb_substr($msg, $head + 4, $close + $head - mb_strlen($msg)); - $exp = explode(',', $content); - $cq['type'] = array_shift($exp); - foreach ($exp as $v) { - $ss = explode('=', $v); - $sk = array_shift($ss); - $cq['params'][$sk] = self::decode(implode('=', $ss), true); - } - $cq['start'] = $head; - $cq['end'] = $close + $head; - return !$is_object ? $cq : CQObject::fromArray($cq); - } - return null; - } - - /** - * 获取消息中所有的CQ码 - * @param string $msg 消息内容 - * @param bool $is_object 是否以对象形式返回,如果为False的话,返回数组形式(默认为false) - * @return array|CQObject[] 返回的CQ码们(数组或对象) - */ - public static function getAllCQ(string $msg, bool $is_object = false): array - { - $cqs = []; - $offset = 0; - while (($head = mb_strpos(($submsg = mb_substr($msg, $offset)), '[CQ:')) !== false) { - $key_offset = mb_substr($submsg, $head); - $tmpmsg = mb_strpos($key_offset, ']'); - if ($tmpmsg === false) { - break; - } // 没闭合,不算CQ码 - $content = mb_substr($submsg, $head + 4, $tmpmsg + $head - mb_strlen($submsg)); - $exp = explode(',', $content); - $cq = []; - $cq['type'] = array_shift($exp); - foreach ($exp as $v) { - $ss = explode('=', $v); - $sk = array_shift($ss); - $cq['params'][$sk] = self::decode(implode('=', $ss), true); - } - $cq['start'] = $offset + $head; - $cq['end'] = $offset + $tmpmsg + $head; - $offset += $head + $tmpmsg + 1; - $cqs[] = (!$is_object ? $cq : CQObject::fromArray($cq)); - } - return $cqs; - } - - private static function buildCQ(string $cq, array $array, array $optional_values = []): string - { - $str = '[CQ:' . $cq; - foreach ($array as $k => $v) { - if ($v === null) { - logger()->warning('param ' . $k . ' cannot be set with null, empty CQ will returned!'); - return ' '; - } - $str .= ',' . $k . '=' . self::encode($v); - } - foreach ($optional_values as $v) { - if ($v !== '') { - $str .= ',' . $v; - } - } - return $str . ']'; - } -} diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php deleted file mode 100644 index ff4405a7..00000000 --- a/src/ZM/API/CQAPI.php +++ /dev/null @@ -1,91 +0,0 @@ -getOption('type') === CONN_WEBSOCKET) { - return $this->processWebsocketAPI($connection, $reply, $function); - } - - return $this->processHttpAPI($connection, $reply, $function); - } - - private function processWebsocketAPI($connection, $reply, $function = false) - { - $api_id = ZMAtomic::get('wait_msg_id')->add(); - $reply['echo'] = $api_id; - if (server()->push($connection->getFd(), json_encode($reply))) { - if ($function === true) { - $obj = [ - 'data' => $reply, - 'time' => microtime(true), - 'self_id' => $connection->getOption('connect_id'), - 'echo' => $api_id, - ]; - return CoMessage::yieldByWS($obj, ['echo'], 30); - } - return true; - } - logger()->warning(zm_internal_errcode('E00036') . 'CQAPI send failed, websocket push error.'); - $response = [ - 'status' => 'failed', - 'retcode' => -1000, - 'data' => null, - 'self_id' => $connection->getOption('connect_id'), - ]; - SpinLock::lock('wait_api'); - $r = LightCacheInside::get('wait_api', 'wait_api'); - unset($r[$reply['echo']]); - LightCacheInside::set('wait_api', 'wait_api', $r); - SpinLock::unlock('wait_api'); - if ($function === true) { - return $response; - } - return false; - } - - private function processHttpAPI($connection, $reply, $function = null): bool - { - unset($connection, $reply, $function); - // TODO: HTTP方式处理API的代码还没写 - return false; - } -} diff --git a/src/ZM/API/GoCqhttpAPIV11.php b/src/ZM/API/GoCqhttpAPIV11.php deleted file mode 100644 index fc1743af..00000000 --- a/src/ZM/API/GoCqhttpAPIV11.php +++ /dev/null @@ -1,120 +0,0 @@ -connection = $connection; - $this->callback = $callback; - $this->prefix = $prefix; - } - - /** - * 获取频道系统内BOT的资料 - * 响应字段:nickname, tiny_id, avatar_url - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E7%B3%BB%E7%BB%9F%E5%86%85bot%E7%9A%84%E8%B5%84%E6%96%99 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGuildServiceProfile() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取频道列表 - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGuildList() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 通过访客获取频道元数据 - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E9%80%9A%E8%BF%87%E8%AE%BF%E5%AE%A2%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%85%83%E6%95%B0%E6%8D%AE - * @param int|string $guild_id 频道ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGuildMetaByGuest($guild_id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'guild_id' => $guild_id, - ], - ], $this->callback); - } - - /** - * 获取子频道列表 - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E5%AD%90%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8 - * @param int|string $guild_id 频道ID - * @param false $no_cache 禁用缓存(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGuildChannelList($guild_id, bool $no_cache = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'guild_id' => $guild_id, - 'no_cache' => $no_cache, - ], - ], $this->callback); - } - - /** - * 获取频道成员列表 - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8 - * @param int|string $guild_id 频道ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGuildMembers($guild_id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'guild_id' => $guild_id, - ], - ], $this->callback); - } - - /** - * 发送信息到子频道 - * @see https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E5%88%B0%E5%AD%90%E9%A2%91%E9%81%93 - * @param int|string $guild_id 频道ID - * @param int|string $channel_id 子频道ID - * @param string $message 信息内容 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function sendGuildChannelMsg($guild_id, $channel_id, string $message) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'guild_id' => $guild_id, - 'channel_id' => $channel_id, - 'message' => $message, - ], - ], $this->callback); - } -} diff --git a/src/ZM/API/OneBotV11.php b/src/ZM/API/OneBotV11.php deleted file mode 100644 index 4d16385f..00000000 --- a/src/ZM/API/OneBotV11.php +++ /dev/null @@ -1,765 +0,0 @@ -connection = $connection; - } - - /** - * 获取机器人Action/API实例 - * @param int|string $robot_id 机器人ID - * @throws RobotNotFoundException - * @return ZMRobot 机器人实例 - */ - public static function get($robot_id): ZMRobot - { - $r = ManagerGM::getAllByName('qq'); - foreach ($r as $v) { - if ($v->getOption('connect_id') == $robot_id) { - return new ZMRobot($v); - } - } - throw new RobotNotFoundException('机器人 ' . $robot_id . ' 未连接到框架!'); - } - - /** - * 随机获取一个连接到框架的机器人实例 - * @throws RobotNotFoundException - * @return ZMRobot 机器人实例 - */ - public static function getRandom(): ZMRobot - { - $r = ManagerGM::getAllByName('qq'); - if ($r == []) { - throw new RobotNotFoundException('没有任何机器人连接到框架!'); - } - return new ZMRobot($r[array_rand($r)]); - } - - /** - * 获取所有机器人实例 - * @return ZMRobot[] 机器人实例们 - */ - public static function getAllRobot(): array - { - $r = ManagerGM::getAllByName('qq'); - $obj = []; - foreach ($r as $v) { - $obj[] = new ZMRobot($v); - } - return $obj; - } - - /** - * 设置回调或启用协程等待API回包 - * @param bool|Closure $callback 是否开启协程或设置异步回调函数,如果为true,则协程等待结果,如果为false,则异步执行并不等待结果,如果为回调函数,则异步执行且调用回调 - * @return OneBotV11 返回本身 - */ - public function setCallback($callback = true): OneBotV11 - { - $this->callback = $callback; - return $this; - } - - /** - * 设置API调用类型后缀 - * @param int $prefix 设置后缀类型,API_NORMAL为不加后缀,API_ASYNC为异步调用,API_RATE_LIMITED为加后缀并且限制调用频率 - * @return OneBotV11 返回本身 - */ - public function setPrefix(int $prefix = self::API_NORMAL): OneBotV11 - { - $this->prefix = $prefix; - return $this; - } - - public function getSelfId() - { - return $this->connection->getOption('connect_id'); - } - - /* 下面是 OneBot 标准的 V11 公开 API */ - - /** - * 发送私聊消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $user_id 用户ID - * @param string $message 消息内容 - * @param bool $auto_escape 是否自动转义(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function sendPrivateMsg($user_id, string $message, bool $auto_escape = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'message' => $message, - 'auto_escape' => $auto_escape, - ], - ], $this->callback); - } - - /** - * 发送群消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF - * @param int|string $group_id 群组ID - * @param string $message 消息内容 - * @param bool $auto_escape 是否自动转义(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function sendGroupMsg($group_id, string $message, bool $auto_escape = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'message' => $message, - 'auto_escape' => $auto_escape, - ], - ], $this->callback); - } - - /** - * 发送消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF - * @param string $message_type 消息类型 - * @param int|string $target_id 目标ID - * @param string $message 消息内容 - * @param bool $auto_escape 是否自动转义(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function sendMsg(string $message_type, $target_id, string $message, bool $auto_escape = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'message_type' => $message_type, - ($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id, - 'message' => $message, - 'auto_escape' => $auto_escape, - ], - ], $this->callback); - } - - /** - * 撤回消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF - * @param int|string $message_id 消息ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function deleteMsg($message_id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'message_id' => $message_id, - ], - ], $this->callback); - } - - /** - * 获取消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF - * @param int|string $message_id 消息ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getMsg($message_id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'message_id' => $message_id, - ], - ], $this->callback); - } - - /** - * 获取合并转发消息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $id ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getForwardMsg($id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'id' => $id, - ], - ], $this->callback); - } - - /** - * 发送好友赞 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E - * @param int|string $user_id 用户ID - * @param int $times 时间 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function sendLike($user_id, int $times = 1) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'times' => $times, - ], - ], $this->callback); - } - - /** - * 群组踢人 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA - * @param int|string $group_id 群ID - * @param int|string $user_id 用户ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupKick($group_id, $user_id, bool $reject_add_request = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'reject_add_request' => $reject_add_request, - ], - ], $this->callback); - } - - /** - * 群组单人禁言 - * @see https://github.com/botuniverse/onebot-11/blob/master/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|string $group_id 群ID - * @param int|string $user_id 用户ID - * @param int $duration 禁言时长 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupBan($group_id, $user_id, int $duration = 1800) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'duration' => $duration, - ], - ], $this->callback); - } - - /** - * 群组匿名用户禁言 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param array|int|string $anonymous_or_flag 匿名禁言Flag或匿名用户对象 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupAnonymousBan($group_id, $anonymous_or_flag, int $duration = 1800) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - (is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag, - 'duration' => $duration, - ], - ], $this->callback); - } - - /** - * 群组全员禁言 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupWholeBan($group_id, bool $enable = true) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'enable' => $enable, - ], - ], $this->callback); - } - - /** - * 群组设置管理员 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param int|string $user_id 用户ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupAdmin($group_id, $user_id, bool $enable = true) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'enable' => $enable, - ], - ], $this->callback); - } - - /** - * 群组匿名 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D - * @param int|string $group_id 群ID - * @param bool $enable 是否启用(默认为true) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupAnonymous($group_id, bool $enable = true) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'enable' => $enable, - ], - ], $this->callback); - } - - /** - * 设置群名片(群备注) - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param int|string $user_id 用户ID - * @param string $card 名片内容(默认为空) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupCard($group_id, $user_id, string $card = '') - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'card' => $card, - ], - ], $this->callback); - } - - /** - * 设置群名 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D - * @param int|string $group_id 群ID - * @param string $group_name 群名 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupName($group_id, string $group_name) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'group_name' => $group_name, - ], - ], $this->callback); - } - - /** - * 退出群组 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84 - * @param int|string $group_id 群ID - * @param bool $is_dismiss 是否解散(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupLeave($group_id, bool $is_dismiss = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'is_dismiss' => $is_dismiss, - ], - ], $this->callback); - } - - /** - * 设置群组专属头衔 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param int|string $user_id 用户ID - * @param string $special_title 专属头衔内容 - * @param int $duration 持续时间(默认为-1,永久) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupSpecialTitle($group_id, $user_id, string $special_title = '', int $duration = -1) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'special_title' => $special_title, - 'duration' => $duration, - ], - ], $this->callback); - } - - /** - * 处理加好友请求 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 array|int|string $flag 处理加好友请求的flag - * @param bool $approve 是否同意(默认为true) - * @param string $remark 设置昵称(默认不设置) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setFriendAddRequest($flag, bool $approve = true, string $remark = '') - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'flag' => $flag, - 'approve' => $approve, - 'remark' => $remark, - ], - ], $this->callback); - } - - /** - * 处理加群请求/邀请 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 array|int|string $flag 处理加群请求的flag - * @param string $sub_type 处理请求类型(包含add和invite) - * @param bool $approve 是否同意(默认为true) - * @param string $reason 拒绝理由(仅在拒绝时有效,默认为空) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setGroupAddRequest($flag, string $sub_type, bool $approve = true, string $reason = '') - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'flag' => $flag, - 'sub_type' => $sub_type, - 'approve' => $approve, - 'reason' => $reason, - ], - ], $this->callback); - } - - /** - * 获取登录号信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getLoginInfo() - { - return $this->processAPI($this->connection, ['action' => $this->getActionName($this->prefix, __FUNCTION__)], $this->callback); - } - - /** - * 获取陌生人信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $user_id 用户ID - * @param bool $no_cache 是否不使用缓存(默认为false) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getStrangerInfo($user_id, bool $no_cache = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'no_cache' => $no_cache, - ], - ], $this->callback); - } - - /** - * 获取好友列表 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getFriendList() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取群信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF - * @param int|string $group_id 群ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGroupInfo($group_id, bool $no_cache = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'no_cache' => $no_cache, - ], - ], $this->callback); - } - - /** - * 获取群列表 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGroupList() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取群成员信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param int|string $user_id 用户ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGroupMemberInfo($group_id, $user_id, bool $no_cache = false) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'no_cache' => $no_cache, - ], - ], $this->callback); - } - - /** - * 获取群成员列表 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGroupMemberList($group_id) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - ], - ], $this->callback); - } - - /** - * 获取群荣誉信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 int|string $group_id 群ID - * @param string $type 荣誉类型 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getGroupHonorInfo($group_id, string $type) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'type' => $type, - ], - ], $this->callback); - } - - /** - * 获取 CSRF Token - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getCsrfToken() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取 QQ 相关接口凭证 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getCredentials(string $domain = '') - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'domain' => $domain, - ], - ], $this->callback); - } - - /** - * 获取语音 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3 - * @param string $file 文件 - * @param string $out_format 输出格式 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getRecord(string $file, string $out_format) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'file' => $file, - 'out_format' => $out_format, - ], - ], $this->callback); - } - - /** - * 获取图片 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87 - * @param string $file 文件 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getImage(string $file) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'file' => $file, - ], - ], $this->callback); - } - - /** - * 检查是否可以发送图片 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function canSendImage() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 检查是否可以发送语音 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function canSendRecord() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取运行状态 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getStatus() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取版本信息 - * @see https://github.com/botuniverse/onebot-11/blob/master/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 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function getVersionInfo() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 重启 OneBot 实现 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0 - * @param int $delay 延迟时间(毫秒,默认为0) - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function setRestart(int $delay = 0) - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - 'params' => [ - 'delay' => $delay, - ], - ], $this->callback); - } - - /** - * 清理缓存 - * @see https://github.com/botuniverse/onebot-11/blob/master/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function cleanCache() - { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName($this->prefix, __FUNCTION__), - ], $this->callback); - } - - /** - * 获取内置支持的扩展API对象 - * 现支持 go-cqhttp 的扩展API - * @params string $package_name 包名 - * @throws ZMKnownException - * @return mixed 返回包的操作对象 - */ - public function getExtendedAPI(string $package_name = 'go-cqhttp') - { - $table = [ - 'go-cqhttp' => GoCqhttpAPIV11::class, - ]; - if (isset($table[$package_name])) { - return new $table[$package_name]($this->connection, $this->callback, $this->prefix); - } - throw new ZMKnownException(zm_internal_errcode('E00071'), '无法找到对应的调用扩展类'); - } - - /** - * @param string $action 动作(API)名称 - * @param array $params 参数 - * @return array|bool 返回API调用结果(数组)或异步API调用状态(bool) - */ - public function callExtendedAPI(string $action, array $params = []) - { - return $this->processAPI($this->connection, [ - 'action' => $action, - 'params' => $params, - ], $this->callback); - } -} diff --git a/src/ZM/API/Proxies/Bot/AbstractBotProxy.php b/src/ZM/API/Proxies/Bot/AbstractBotProxy.php deleted file mode 100644 index 1581aa07..00000000 --- a/src/ZM/API/Proxies/Bot/AbstractBotProxy.php +++ /dev/null @@ -1,74 +0,0 @@ -bot = $bot; - } - - /** - * 在传入的机器人实例上调用方法 - * - * @param string $name 方法名 - * @param array $arguments 参数 - * @return mixed - */ - public function __call(string $name, array $arguments) - { - return $this->bot->{$name}(...$arguments); - } - - /** - * 获取传入的机器人实例的属性 - * - * @param string $name 属性名 - * @return mixed - */ - public function __get(string $name) - { - return $this->bot->{$name}; - } - - /** - * 设置传入的机器人实例的属性 - * - * @param string $name 属性名 - * @param mixed $value 属性值 - */ - public function __set(string $name, $value) - { - $this->bot->{$name} = $value; - } - - /** - * 判断传入的机器人实例的属性是否存在 - * - * @param string $name 属性名 - */ - public function __isset(string $name): bool - { - return isset($this->bot->{$name}); - } -} diff --git a/src/ZM/API/Proxies/Bot/AllBotsProxy.php b/src/ZM/API/Proxies/Bot/AllBotsProxy.php deleted file mode 100644 index 59540a32..00000000 --- a/src/ZM/API/Proxies/Bot/AllBotsProxy.php +++ /dev/null @@ -1,42 +0,0 @@ - 返回一个包含所有执行结果的数组,键名为机器人ID - */ - public function __call(string $name, array $arguments) - { - // 如果调用的方法为代理方法,则传入当前代理作为参数 - // 一般此情况代表用户进行嵌套代理,即 `->all()->allGroups()` 等情况 - $reflection = new ReflectionMethod(ZMRobot::class, $name); - if (($return = $reflection->getReturnType()) && $return instanceof ReflectionNamedType && str_contains($return->getName(), 'Proxy')) { - logger()->debug("Trying to construct proxy {$name} inside proxy, returning nested proxy."); - // 插入当前代理作为第一个参数 - array_unshift($arguments, $this); - return $this->bot->{$name}(...$arguments); - } - - $result = []; - // 遍历所有机器人实例 - foreach ($this->bot::getAllRobot() as $bot) { - logger()->debug("Calling {$name} on bot {$bot->getSelfId()}."); - $result[$bot->getSelfId()] = $bot->{$name}(...$arguments); - } - return $result; - } -} diff --git a/src/ZM/API/Proxies/Bot/AllGroupsProxy.php b/src/ZM/API/Proxies/Bot/AllGroupsProxy.php deleted file mode 100644 index 9336df16..00000000 --- a/src/ZM/API/Proxies/Bot/AllGroupsProxy.php +++ /dev/null @@ -1,42 +0,0 @@ - 返回一个包含所有执行结果的数组,键名为群号 - */ - public function __call(string $name, array $arguments) - { - // 如果调用的方法并非群组方法,则直接返回输出 - // 因为目前所有群组方法都是以 `group_id` 作为第一个参数,故以此来判断 - $reflection = new \ReflectionMethod(ZMRobot::class, $name); - if (!$reflection->getNumberOfParameters() || $reflection->getParameters()[0]->getName() !== 'group_id') { - logger()->warning("Trying to call non-group method {$name} on AllGroupsProxy, skipped."); - return $this->bot->{$name}(...$arguments); - } - - $result = []; - // 获取并遍历所有群组 - $groups = $this->bot->getGroupList()['data']; - foreach ($groups as $group) { - $arguments[0] = $group['group_id']; - $bot_id = implode_when_necessary($this->bot->getSelfId()); - logger()->debug("Calling {$name} on group {$group['group_id']} on bot {$bot_id}."); - // 在群组上调用方法 - $result[$group['group_id']] = $this->bot->{$name}(...$arguments); - } - return $result; - } -} diff --git a/src/ZM/API/TuringAPI.php b/src/ZM/API/TuringAPI.php deleted file mode 100644 index 92b968b1..00000000 --- a/src/ZM/API/TuringAPI.php +++ /dev/null @@ -1,132 +0,0 @@ - 0, - 'userInfo' => [ - 'apiKey' => $api, - 'userId' => $user_id, - ], - ]; - if ($msg != '') { - $content['perception']['inputText']['text'] = $msg; - } - $msg = trim($msg); - if (mb_strlen($msg) < 1 && !isset($url)) { - return '请说出你想说的话'; - } - if (isset($url)) { - $content['perception']['inputImage']['url'] = $url; - $content['reqType'] = 1; - } - if (!isset($content['perception'])) { - return '请说出你想说的话'; - } - $client = new Client('openapi.tuling123.com', 80); - $client->setHeaders(['Content-type' => 'application/json']); - $client->post('/openapi/api/v2', json_encode($content, JSON_UNESCAPED_UNICODE)); - $api_return = json_decode($client->body, true); - if (!isset($api_return['intent']['code'])) { - return 'XD 哎呀,我脑子突然短路了,请稍后再问我吧!'; - } - $status = self::getResultStatus($api_return); - if ($status !== true) { - if ($status == 'err:输入文本内容超长(上限150)') { - return '你的话太多了!!!'; - } - if ($api_return['intent']['code'] == 4003) { - return '哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗'; - } - Console::error(zm_internal_errcode('E00038') . "图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status); - // echo json_encode($r, 128|256); - return '哎呀,我刚才有点走神了,要不一会儿换一种问题试试?'; - } - $result = $api_return['results']; - // logger()->info(Console::setColor(json_encode($result, 128 | 256), "green")); - $final = ''; - foreach ($result as $v) { - switch ($v['resultType']) { - case 'url': - $final .= "\n" . $v['values']['url']; - break; - case 'text': - $final .= "\n" . $v['values']['text']; - break; - case 'image': - $final .= "\n" . CQ::image($v['values']['image']); - break; - } - } - return trim($final); - } - - /** - * @param array $r 数据API回包 - * @return bool|string 错误消息或成功鸥鸟 - */ - public static function getResultStatus(array $r) - { - switch ($r['intent']['code']) { - case 5000: - return 'err:无解析结果'; - case 4000: - case 6000: - return 'err:暂不支持该功能'; - case 4001: - return 'err:加密方式错误'; - case 4005: - case 4002: - return 'err:无功能权限'; - case 4003: - return 'err:该apikey没有可用请求次数'; - case 4007: - return 'err:apikey不合法'; - case 4100: - return 'err:userid获取失败'; - case 4200: - return 'err:上传格式错误'; - case 4300: - return 'err:批量操作超过限制'; - case 4400: - return 'err:没有上传合法userid'; - case 4500: - return 'err:userid申请个数超过限制'; - case 4600: - return 'err:输入内容为空'; - case 4602: - return 'err:输入文本内容超长(上限150)'; - case 7002: - return 'err:上传信息失败'; - case 8008: - return 'err:服务器错误'; - default: - return true; - } - } -} diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php deleted file mode 100644 index 014c1706..00000000 --- a/src/ZM/API/ZMRobot.php +++ /dev/null @@ -1,37 +0,0 @@ -getSelfId()); - logger()->debug("Constructing AllBotsProxy for ZMRobot({$bot_id})"); - return new Proxies\AllBotsProxy($bot); - } - - /** - * 获取一个会在所有群上执行的代理 - */ - public function allGroups(Proxies\AbstractBotProxy $proxy = null): Proxies\AllGroupsProxy - { - $bot = $proxy ?: $this; - $bot_id = implode_when_necessary($bot->getSelfId()); - logger()->debug("Constructing AllGroupsProxy for ZMRobot({$bot_id})"); - return new Proxies\AllGroupsProxy($bot); - } -} diff --git a/src/ZM/Adapters/AdapterInterface.php b/src/ZM/Adapters/AdapterInterface.php deleted file mode 100644 index d6abd0a5..00000000 --- a/src/ZM/Adapters/AdapterInterface.php +++ /dev/null @@ -1,27 +0,0 @@ -getFrame()->data, true); - - // 将数据存入协程参数中 - set_coroutine_params(compact('data')); - - try { - logger()->debug('start handle incoming request'); - - // 非元事件调用 pre-before 事件 - if (!$this->isMetaEvent($data)) { - logger()->debug('pre-before event'); - $pre_before_result = $this->handleBeforeEvent($data, 'pre'); - if ($pre_before_result->store === 'block') { - EventDispatcher::interrupt(); - } - } - - // 回调或事件处理 resume - if (CoMessage::resumeByWS()) { - EventDispatcher::interrupt(); - } - - // 非元事件调用 after-before 事件 - if (!$this->isMetaEvent($data)) { - logger()->debug('post-before event'); - $post_before_result = $this->handleBeforeEvent($data, 'post'); - if ($post_before_result->store === 'block') { - EventDispatcher::interrupt(); - } - } - - // 进入回调、事件分发流程 - if ($this->isEvent($data)) { - // 事件分发 - switch ($data['post_type']) { - case 'message': - logger()->debug('message event {data}', compact('data')); - $this->handleMessageEvent($data, $context); - break; - case 'meta_event': - logger()->debug('meta event {data}', compact('data')); - $this->handleMetaEvent($data, $context); - break; - case 'notice': - logger()->debug('notice event {data}', compact('data')); - $this->handleNoticeEvent($data, $context); - break; - case 'request': - logger()->debug('request event {data}', compact('data')); - $this->handleRequestEvent($data, $context); - break; - } - } elseif ($this->isAPIResponse($data)) { - logger()->debug('api response {data}', compact('data')); - $this->handleAPIResponse($data, $context); - } - logger()->debug('event end {data}', compact('data')); - // 回调、事件处理完成 - } catch (WaitTimeoutException $e) { - $e->module->finalReply($e->getMessage()); - } finally { - // 非元事件调用 after 事件 - if (!$this->isMetaEvent($data)) { - logger()->debug('after event'); - $after_result = $this->handleAfterEvent($data); - if ($after_result->store === 'block') { - EventDispatcher::interrupt(); - } - } - } - } - - /** - * 处理 API 响应 - * - * @param array $data 数据 - * @param ContextInterface $context 上下文 - */ - private function handleAPIResponse(array $data, ContextInterface $context): void - { - set_coroutine_params(['cq_response' => $data]); - $dispatcher = new EventDispatcher(CQAPIResponse::class); - $dispatcher->setRuleFunction(function (CQAPIResponse $event) use ($context) { - return $event->retcode === $context->getCQResponse()['retcode']; - }); - $dispatcher->dispatchEvents($data); - } - - /** - * 处理消息事件 - * - * @param array $data 消息数据 - * @param ContextInterface $context 上下文 - */ - private function handleMessageEvent(array $data, ContextInterface $context): void - { - // 分发 CQCommand 事件 - $dispatcher = new EventDispatcher(CQCommand::class); - // 设定返回值处理函数 - $dispatcher->setReturnFunction(function ($result) use ($context) { - if (is_string($result)) { - $context->reply($result); - } - if ($context->getCache('has_reply') === true) { - EventDispatcher::interrupt(); - } - }); - - $message = $data['message']; - - // 将消息段数组转换为消息字符串 - if (is_array($message)) { - $message = MessageUtil::arrayToStr($message); - } - - // 匹配命令 - $match_result = MessageUtil::matchCommand($message, $context->getData()); - - if ($match_result->status) { - $matches = $match_result->match; - - $input_arguments = MessageUtil::checkArguments($match_result->object->class, $match_result->object->method, $matches); - if (!empty($matches)) { - $context->setCache('match', $matches); - } - - $dispatcher->dispatchEvent($match_result->object, null, ...$input_arguments); - - // 处理命令返回结果 - if (is_string($dispatcher->store)) { - $context->reply($dispatcher->store); - } - - if ($context->getCache('has_reply') === true) { - $policy = ZMConfig::get('global', 'onebot')['message_command_policy'] ?? 'interrupt'; - switch ($policy) { - case 'interrupt': - EventDispatcher::interrupt(); - // no break - case 'continue': - break; - default: - throw new \Exception('未知的消息命令策略:' . $policy); - } - } - } - - // 分发 CQMessage 事件 - $dispatcher = new EventDispatcher(CQMessage::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQMessage $event) use ($context) { - return ($event->message === '' || ($event->message === $context->getMessage())) - && ($event->user_id === 0 || ($event->user_id === $context->getUserId())) - && ($event->group_id === 0 || ($event->group_id === ($context->getGroupId() ?? 0))) - && ($event->message_type === '' || ($event->message_type === $context->getMessageType())) - && ($event->raw_message === '' || ($event->raw_message === $context->getData()['raw_message'])); - }); - // 设定返回值处理函数 - $dispatcher->setReturnFunction(function ($result) use ($context) { - if (is_string($result)) { - $context->reply($result); - } - }); - - $dispatcher->dispatchEvents($context->getMessage()); - } - - /** - * 处理元事件 - * - * @param array $data 消息数据 - * @param ContextInterface $context 上下文 - */ - private function handleMetaEvent(array $data, ContextInterface $context): void - { - $dispatcher = new EventDispatcher(CQMetaEvent::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQMetaEvent $event) use ($context) { - return compare_object_and_array_by_keys($event, $context->getData(), ['meta_event_type']); - }); - - $dispatcher->dispatchEvents($context->getData()); - } - - /** - * 处理通知事件 - * - * @param array $data 消息数据 - * @param ContextInterface $context 上下文 - */ - private function handleNoticeEvent(array $data, ContextInterface $context): void - { - $dispatcher = new EventDispatcher(CQNotice::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQNotice $event) use ($context) { - return compare_object_and_array_by_keys($event, $context->getData(), ['notice_type', 'sub_type', 'group_id', 'operator_id']); - }); - - $dispatcher->dispatchEvents($context->getData()); - } - - /** - * 处理请求事件 - * - * @param array $data 消息数据 - * @param ContextInterface $context 上下文 - */ - private function handleRequestEvent(array $data, ContextInterface $context): void - { - $dispatcher = new EventDispatcher(CQRequest::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQRequest $event) use ($context) { - return compare_object_and_array_by_keys($event, $context->getData(), ['request_type', 'sub_type', 'user_id', 'comment']); - }); - - $dispatcher->dispatchEvents($context->getData()); - } - - /** - * 处理前置事件 - * - * @param array $data 消息数据 - * @param string $time 执行时机 - */ - private function handleBeforeEvent(array $data, string $time): EventDispatcher - { - $dispatcher = new EventDispatcher(CQBefore::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQBefore $event) use ($data, $time) { - if ($time === 'pre') { - $level = $event->level >= 200; - } else { - $level = $event->level < 200; - } - return $level && ($event->cq_event === ($data['post_type'] ?? '')); - }); - // 设定返回值处理函数 - $dispatcher->setReturnFunction(function ($result) { - if (!$result) { - EventDispatcher::interrupt('block'); - } - }); - - $dispatcher->dispatchEvents($data); - return $dispatcher; - } - - /** - * 处理后置事件 - * - * @param array $data 消息数据 - */ - private function handleAfterEvent(array $data): EventDispatcher - { - $dispatcher = new EventDispatcher(CQAfter::class); - // 设定匹配规则函数 - $dispatcher->setRuleFunction(function (CQAfter $event) use ($data) { - return $event->cq_event === $data['post_type']; - }); - - $dispatcher->dispatchEvents($data); - return $dispatcher; - } - - /** - * 判断是否为 API 回调 - */ - private function isAPIResponse(array $data): bool - { - // API 响应应带有 echo 字段 - return !isset($data['post_type']) && isset($data['echo']); - } - - /** - * 判断是否为事件 - */ - private function isEvent(array $data): bool - { - // 所有事件都应带有 post_type 字段 - return isset($data['post_type']); - } - - /** - * 判断是否为元事件 - */ - private function isMetaEvent(array $data): bool - { - return $this->isEvent($data) && $data['post_type'] === 'meta_event'; - } -} diff --git a/src/ZM/Annotation/AnnotationHandler.php b/src/ZM/Annotation/AnnotationHandler.php new file mode 100644 index 00000000..bb5fa594 --- /dev/null +++ b/src/ZM/Annotation/AnnotationHandler.php @@ -0,0 +1,183 @@ +annotation_class = $annotation_class; + logger()->debug('开始分发注解 {annotation}', ['annotation' => $annotation_class]); + } + + public static function interrupt($return_var = null) + { + throw new InterruptException($return_var); + } + + public function setRuleCallback(callable $rule): AnnotationHandler + { + logger()->debug('注解调用器设置事件ruleFunc: {annotation}', ['annotation' => $this->annotation_class]); + $this->rule_callback = $rule; + return $this; + } + + public function setReturnCallback(callable $return): AnnotationHandler + { + logger()->debug('注解调用器设置事件returnFunc: {annotation}', ['annotation' => $this->annotation_class]); + $this->return_callback = $return; + return $this; + } + + /** + * @param mixed ...$params + * @throws Throwable + */ + public function handleAll(...$params) + { + try { + foreach ((AnnotationMap::$_list[$this->annotation_class] ?? []) as $v) { + $this->handle($v, $this->rule_callback, ...$params); + if ($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) { + $this->status = self::STATUS_NORMAL; + continue; + } + if (is_callable($this->return_callback) && $this->status === self::STATUS_NORMAL) { + ($this->return_callback)($this->return_val); + } + } + } catch (InterruptException $e) { + $this->return_val = $e->return_var; + $this->status = self::STATUS_INTERRUPTED; + } catch (Throwable $e) { + $this->status = self::STATUS_EXCEPTION; + throw $e; + } + } + + public function handle(AnnotationBase $v, ?callable $rule_callback = null, ...$params): bool + { + $target_class = resolve($v->class); + $target_method = $v->method; + // 先执行规则 + if ($rule_callback !== null && !$rule_callback($this, $params)) { + $this->status = self::STATUS_RULE_FAILED; + return false; + } + + // 检查中间件 + $mid_obj = []; + $before_result = true; + foreach ($this->getRegisteredMiddlewares($target_class, $target_method) as $v) { + $mid_obj[] = $v[0]; // 投喂中间件 + if ($v[1] !== '') { // 顺带执行before + if (function_exists('container')) { + $before_result = container()->call([$v[0], $v[1]], $params); + } else { + $before_result = call_user_func([$v[0], $v[1]], $params); + } + if ($before_result === false) { + break; + } + } + } + $mid_obj_cnt1 = count($mid_obj) - 1; + if ($before_result) { // before全部通过了 + try { + // 执行注解绑定的方法 + // TODO: 记得完善好容器后把这里的这个if else去掉 + if (function_exists('container')) { + $this->return_val = container()->call([$target_class, $target_method], $params); + } else { + $this->return_val = call_user_func([$target_class, $target_method], $params); + } + } catch (Throwable $e) { + if ($e instanceof InterruptException) { + throw $e; + } + for ($i = $mid_obj_cnt1; $i >= 0; --$i) { + $obj = $mid_obj[$i]; + foreach ($obj[3] as $name => $method) { + if ($e instanceof $name) { + $obj[0]->{$method}($e); + return false; + } + } + } + throw $e; + } + } else { + $this->status = self::STATUS_BEFORE_FAILED; + } + for ($i = $mid_obj_cnt1; $i >= 0; --$i) { + if ($mid_obj[$i][2] !== '') { + $mid_obj[$i][0]->{$mid_obj[$i][2]}($this->return_val); + } + } + return true; + } + + /** + * 获取注册过的中间件 + * + * @param object|string $class 类对象 + * @param string $method 方法名称 + */ + private function getRegisteredMiddlewares($class, string $method): Generator + { + foreach (AnnotationMap::$_map[get_class($class)][$method] ?? [] as $annotation) { + if ($annotation instanceof Middleware) { + $name = $annotation->name; + $reg_mid = AnnotationMap::$_middleware_map[$name]['class'] ?? null; + if ($reg_mid === null) { + logger()->error('Not a valid middleware name: {name}', ['name' => $name]); + continue; + } + + $obj = new $reg_mid($annotation->params); + yield [ + $obj, + AnnotationMap::$_middleware_map[$name]['before'] ?? '', + AnnotationMap::$_middleware_map[$name]['after'] ?? '', + AnnotationMap::$_middleware_map[$name]['exceptions'] ?? [], + ]; + } + } + return []; + } +} diff --git a/src/ZM/Annotation/AnnotationMap.php b/src/ZM/Annotation/AnnotationMap.php new file mode 100644 index 00000000..a10df802 --- /dev/null +++ b/src/ZM/Annotation/AnnotationMap.php @@ -0,0 +1,31 @@ +> + * @internal + */ + public static $_list = []; + + /** + * @var array>> + * @internal + */ + public static $_map = []; + + /** + * @var array + * @internal + */ + public static $_middleware_map = []; +} diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index ce0a592f..79e5939c 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -10,21 +10,20 @@ use Koriym\Attributes\DualReader; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use ZM\Annotation\Http\HandleAfter; -use ZM\Annotation\Http\HandleBefore; -use ZM\Annotation\Http\HandleException; -use ZM\Annotation\Http\Middleware; -use ZM\Annotation\Http\MiddlewareClass; -use ZM\Annotation\Http\RequestMapping; +use Symfony\Component\Routing\RouteCollection; +use ZM\Annotation\Http\Controller; +use ZM\Annotation\Http\Route; use ZM\Annotation\Interfaces\ErgodicAnnotation; use ZM\Annotation\Interfaces\Level; -use ZM\Annotation\Module\Closed; +use ZM\Annotation\Middleware\HandleAfter; +use ZM\Annotation\Middleware\HandleBefore; +use ZM\Annotation\Middleware\HandleException; +use ZM\Annotation\Middleware\Middleware; +use ZM\Annotation\Middleware\MiddlewareClass; use ZM\Config\ZMConfig; -use ZM\Event\EventManager; -use ZM\Exception\AnnotationException; -use ZM\Utils\Manager\RouteManager; -use ZM\Utils\ZMUtil; -use function server; +use ZM\Exception\ConfigException; +use ZM\Store\FileSystem; +use ZM\Store\InternalGlobals; class AnnotationParser { @@ -60,14 +59,15 @@ class AnnotationParser /** * 注册各个模块类的注解和模块level的排序 * @throws ReflectionException + * @throws ConfigException */ - public function registerMods() + public function parseAll() { foreach ($this->path_list as $path) { logger()->debug('parsing annotation in ' . $path[0] . ':' . $path[1]); - $all_class = ZMUtil::getClassesPsr4($path[0], $path[1]); + $all_class = FileSystem::getClassesPsr4($path[0], $path[1]); - $conf = ZMConfig::get('global', 'runtime')['annotation_reader_ignore'] ?? []; + $conf = ZMConfig::get('global.runtime.annotation_reader_ignore'); if (isset($conf['name']) && is_array($conf['name'])) { foreach ($conf['name'] as $v) { AnnotationReader::addGlobalIgnoredName($v); @@ -145,7 +145,7 @@ class AnnotationParser /* @var AnnotationBase $method_anno */ $method_anno->class = $v; $method_anno->method = $method_name; - if (!($method_anno instanceof Middleware) && ($middlewares = ZMConfig::get('global', 'runtime')['global_middleware_binding'][get_class($method_anno)] ?? []) !== []) { + if (!($method_anno instanceof Middleware) && ($middlewares = ZMConfig::get('global.global_middleware_binding')[get_class($method_anno)] ?? []) !== []) { if (!isset($inserted[$v][$method_name])) { // 在这里在其他中间件前插入插入全局的中间件 foreach ($middlewares as $middleware) { @@ -156,12 +156,12 @@ class AnnotationParser } $inserted[$v][$method_name] = true; } - } elseif ($method_anno instanceof RequestMapping) { - RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations); + } elseif ($method_anno instanceof Route) { + $this->addRouteAnnotation($method_anno, $method_name, $v, $methods_annotations); } elseif ($method_anno instanceof Middleware) { $this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno; } else { - EventManager::$event_map[$method_anno->class][$method_anno->method][] = $method_anno; + AnnotationMap::$_map[$method_anno->class][$method_anno->method][] = $method_anno; } } } @@ -214,9 +214,7 @@ class AnnotationParser */ public function addRegisterPath(string $path, string $indoor_name) { - if (server()->worker_id === 0) { - logger()->debug('Add register path: ' . $path . ' => ' . $indoor_name); - } + logger()->debug('Add register path: ' . $path . ' => ' . $indoor_name); $this->path_list[] = [$path, $indoor_name]; } @@ -238,26 +236,7 @@ class AnnotationParser } } - /** - * @throws AnnotationException - */ - public function verifyMiddlewares() - { - if ((ZMConfig::get('global', 'runtime')['middleware_error_policy'] ?? 1) === 2) { - // 我承认套三层foreach很不优雅,但是这个会很快的。 - foreach ($this->middleware_map as $v) { - foreach ($v as $vs) { - foreach ($vs as $mid) { - if (!isset($this->middlewares[$mid->middleware])) { - throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$mid->middleware}\"!"); - } - } - } - } - } - } - - public function getRunTime() + public function getUsedTime() { return microtime(true) - $this->start_time; } @@ -287,4 +266,27 @@ class AnnotationParser } return $result; } + + private function addRouteAnnotation(Route $vss, $method, $class, $methods_annotations) + { + if (InternalGlobals::$routes === null) { + InternalGlobals::$routes = new RouteCollection(); + } + + // 拿到所属方法的类上面有没有控制器的注解 + $prefix = ''; + foreach ($methods_annotations as $annotation) { + if ($annotation instanceof Controller) { + $prefix = $annotation->prefix; + break; + } + } + $tail = trim($vss->route, '/'); + $route_name = $prefix . ($tail === '' ? '' : '/') . $tail; + logger()->debug('添加路由:' . $route_name); + $route = new \Symfony\Component\Routing\Route($route_name, ['_class' => $class, '_method' => $method]); + $route->setMethods($vss->request_method); + + InternalGlobals::$routes->add(md5($route_name), $route); + } } diff --git a/src/ZM/Annotation/CQ/CQAPIResponse.php b/src/ZM/Annotation/CQ/CQAPIResponse.php deleted file mode 100644 index 2afe201f..00000000 --- a/src/ZM/Annotation/CQ/CQAPIResponse.php +++ /dev/null @@ -1,32 +0,0 @@ -retcode = $retcode; - } -} diff --git a/src/ZM/Annotation/CQ/CQAfter.php b/src/ZM/Annotation/CQ/CQAfter.php deleted file mode 100644 index 0822564d..00000000 --- a/src/ZM/Annotation/CQ/CQAfter.php +++ /dev/null @@ -1,51 +0,0 @@ -cq_event = $cq_event; - $this->level = $level; - } - - /** - * @return mixed - */ - public function getLevel() - { - return $this->level; - } - - /** - * @param mixed $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQBefore.php b/src/ZM/Annotation/CQ/CQBefore.php deleted file mode 100644 index 7db292e6..00000000 --- a/src/ZM/Annotation/CQ/CQBefore.php +++ /dev/null @@ -1,52 +0,0 @@ -cq_event = $cq_event; - $this->level = $level; - } - - /** - * @return int 返回等级 - */ - public function getLevel(): int - { - return $this->level; - } - - /** - * @param mixed $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php deleted file mode 100644 index bb8f62bb..00000000 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ /dev/null @@ -1,86 +0,0 @@ -match = $match; - $this->pattern = $pattern; - $this->regex = $regex; - $this->start_with = $start_with; - $this->end_with = $end_with; - $this->keyword = $keyword; - $this->alias = $alias; - $this->message_type = $message_type; - $this->user_id = $user_id; - $this->group_id = $group_id; - $this->discuss_id = $discuss_id; - $this->level = $level; - } - - public function getLevel(): int - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQMessage.php b/src/ZM/Annotation/CQ/CQMessage.php deleted file mode 100644 index 9f071ad7..00000000 --- a/src/ZM/Annotation/CQ/CQMessage.php +++ /dev/null @@ -1,65 +0,0 @@ -message_type = $message_type; - $this->user_id = $user_id; - $this->group_id = $group_id; - $this->discuss_id = $discuss_id; - $this->message = $message; - $this->raw_message = $raw_message; - $this->level = $level; - } - - public function getLevel(): int - { - return $this->level; - } - - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQMetaEvent.php b/src/ZM/Annotation/CQ/CQMetaEvent.php deleted file mode 100644 index 90934f58..00000000 --- a/src/ZM/Annotation/CQ/CQMetaEvent.php +++ /dev/null @@ -1,53 +0,0 @@ -meta_event_type = $meta_event_type; - $this->level = $level; - } - - /** - * @return int 返回等级 - */ - public function getLevel(): int - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQNotice.php b/src/ZM/Annotation/CQ/CQNotice.php deleted file mode 100644 index 511e4723..00000000 --- a/src/ZM/Annotation/CQ/CQNotice.php +++ /dev/null @@ -1,58 +0,0 @@ -notice_type = $notice_type; - $this->sub_type = $sub_type; - $this->group_id = $group_id; - $this->operator_id = $operator_id; - $this->level = $level; - } - - public function getLevel(): int - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CQRequest.php b/src/ZM/Annotation/CQ/CQRequest.php deleted file mode 100644 index a09069d0..00000000 --- a/src/ZM/Annotation/CQ/CQRequest.php +++ /dev/null @@ -1,58 +0,0 @@ -request_type = $request_type; - $this->sub_type = $sub_type; - $this->user_id = $user_id; - $this->comment = $comment; - $this->level = $level; - } - - public function getLevel(): int - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/CQ/CommandArgument.php b/src/ZM/Annotation/CQ/CommandArgument.php deleted file mode 100644 index 38030822..00000000 --- a/src/ZM/Annotation/CQ/CommandArgument.php +++ /dev/null @@ -1,146 +0,0 @@ -name = $name; - $this->description = $description; - $this->type = $this->fixTypeName($type); - $this->required = $required; - $this->prompt = $prompt; - $this->default = $default; - $this->timeout = $timeout; - $this->error_prompt_policy = $error_prompt_policy; - if ($this->type === 'bool') { - if ($this->default === '') { - $this->default = 'yes'; - } - if (!in_array($this->default, array_merge(TRUE_LIST, FALSE_LIST))) { - throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为布尔型,检测到非法的默认值 ' . $this->default); - } - } elseif ($this->type === 'number') { - if ($this->default === '') { - $this->default = '0'; - } - if (!is_numeric($this->default)) { - throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为数字型,检测到非法的默认值 ' . $this->default); - } - } - } - - public function getTypeErrorPrompt(): string - { - return '参数类型错误,请重新输入!'; - } - - public function getErrorQuitPrompt(): string - { - return '参数类型错误,停止输入!'; - } - - /** - * @throws ZMKnownException - */ - protected function fixTypeName(string $type): string - { - $table = [ - 'str' => 'string', - 'string' => 'string', - 'strings' => 'string', - 'byte' => 'string', - 'num' => 'number', - 'number' => 'number', - 'int' => 'number', - 'float' => 'number', - 'double' => 'number', - 'boolean' => 'bool', - 'bool' => 'bool', - 'true' => 'bool', - 'any' => 'any', - 'all' => 'any', - '*' => 'any', - ]; - if (array_key_exists($type, $table)) { - return $table[$type]; - } - throw new ZMKnownException(zm_internal_errcode('E00077') . 'Invalid argument type: ' . $type . ', only support any, string, number and bool !'); - } -} diff --git a/src/ZM/Annotation/Module/Closed.php b/src/ZM/Annotation/Closed.php similarity index 84% rename from src/ZM/Annotation/Module/Closed.php rename to src/ZM/Annotation/Closed.php index 04efffdb..82b0f10a 100644 --- a/src/ZM/Annotation/Module/Closed.php +++ b/src/ZM/Annotation/Closed.php @@ -2,12 +2,11 @@ declare(strict_types=1); -namespace ZM\Annotation\Module; +namespace ZM\Annotation; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; use Doctrine\Common\Annotations\Annotation\Target; -use ZM\Annotation\AnnotationBase; /** * Class Closed diff --git a/src/ZM/Annotation/Command/TerminalCommand.php b/src/ZM/Annotation/Command/TerminalCommand.php deleted file mode 100644 index be099e1d..00000000 --- a/src/ZM/Annotation/Command/TerminalCommand.php +++ /dev/null @@ -1,41 +0,0 @@ -command = $command; - $this->alias = $alias; - $this->description = $description; - } -} diff --git a/src/ZM/Annotation/Cron/Cron.php b/src/ZM/Annotation/Cron/Cron.php deleted file mode 100644 index 4db8294a..00000000 --- a/src/ZM/Annotation/Cron/Cron.php +++ /dev/null @@ -1,85 +0,0 @@ -expression = $expression; - $this->worker_id = $worker_id; - $this->check_delay_time = $check_delay_time; - $this->max_iteration_count = $max_iteration_count; - } - - public function getStatus(): int - { - return $this->status; - } - - /** - * @internal - */ - public function setStatus(int $status): void - { - $this->status = $status; - } - - /** - * @internal - */ - public function getRecordNextTime(): int - { - return $this->record_next_time; - } - - /** - * @internal - */ - public function setRecordNextTime(int $record_next_time): void - { - $this->record_next_time = $record_next_time; - } -} diff --git a/src/ZM/Annotation/Swoole/OnSetup.php b/src/ZM/Annotation/Framework/OnSetup.php similarity index 92% rename from src/ZM/Annotation/Swoole/OnSetup.php rename to src/ZM/Annotation/Framework/OnSetup.php index ee22f561..daef9a59 100644 --- a/src/ZM/Annotation/Swoole/OnSetup.php +++ b/src/ZM/Annotation/Framework/OnSetup.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Swoole; +namespace ZM\Annotation\Framework; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; diff --git a/src/ZM/Annotation/Http/RequestMethod.php b/src/ZM/Annotation/Http/RequestMethod.php deleted file mode 100644 index 68014c9d..00000000 --- a/src/ZM/Annotation/Http/RequestMethod.php +++ /dev/null @@ -1,46 +0,0 @@ -method = $method; - } -} diff --git a/src/ZM/Annotation/Http/RequestMapping.php b/src/ZM/Annotation/Http/Route.php similarity index 84% rename from src/ZM/Annotation/Http/RequestMapping.php rename to src/ZM/Annotation/Http/Route.php index 4b9c49a2..9c51efa7 100644 --- a/src/ZM/Annotation/Http/RequestMapping.php +++ b/src/ZM/Annotation/Http/Route.php @@ -17,7 +17,7 @@ use ZM\Annotation\AnnotationBase; * @Target("METHOD") */ #[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)] -class RequestMapping extends AnnotationBase +class Route extends AnnotationBase { /** * @var string @@ -33,7 +33,7 @@ class RequestMapping extends AnnotationBase /** * @var array */ - public $request_method = [RequestMethod::GET, RequestMethod::POST]; + public $request_method = ['GET', 'POST']; /** * Routing path params binding. eg. {"id"="\d+"} @@ -41,7 +41,7 @@ class RequestMapping extends AnnotationBase */ public $params = []; - public function __construct($route, $name = '', $request_method = [RequestMethod::GET, RequestMethod::POST], $params = []) + public function __construct($route, $name = '', $request_method = ['GET', 'POST'], $params = []) { $this->route = $route; $this->name = $name; diff --git a/src/ZM/Annotation/Http/HandleAfter.php b/src/ZM/Annotation/Middleware/HandleAfter.php similarity index 91% rename from src/ZM/Annotation/Http/HandleAfter.php rename to src/ZM/Annotation/Middleware/HandleAfter.php index a7c97e4a..1cb9ddc4 100644 --- a/src/ZM/Annotation/Http/HandleAfter.php +++ b/src/ZM/Annotation/Middleware/HandleAfter.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Http; +namespace ZM\Annotation\Middleware; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; diff --git a/src/ZM/Annotation/Http/HandleBefore.php b/src/ZM/Annotation/Middleware/HandleBefore.php similarity index 91% rename from src/ZM/Annotation/Http/HandleBefore.php rename to src/ZM/Annotation/Middleware/HandleBefore.php index f46c30d7..6038eb7b 100644 --- a/src/ZM/Annotation/Http/HandleBefore.php +++ b/src/ZM/Annotation/Middleware/HandleBefore.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Http; +namespace ZM\Annotation\Middleware; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; diff --git a/src/ZM/Annotation/Http/HandleException.php b/src/ZM/Annotation/Middleware/HandleException.php similarity index 94% rename from src/ZM/Annotation/Http/HandleException.php rename to src/ZM/Annotation/Middleware/HandleException.php index 46f45d83..39ffda54 100644 --- a/src/ZM/Annotation/Http/HandleException.php +++ b/src/ZM/Annotation/Middleware/HandleException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Http; +namespace ZM\Annotation\Middleware; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; diff --git a/src/ZM/Annotation/Http/Middleware.php b/src/ZM/Annotation/Middleware/Middleware.php similarity index 81% rename from src/ZM/Annotation/Http/Middleware.php rename to src/ZM/Annotation/Middleware/Middleware.php index f16d914a..797703f3 100644 --- a/src/ZM/Annotation/Http/Middleware.php +++ b/src/ZM/Annotation/Middleware/Middleware.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Http; +namespace ZM\Annotation\Middleware; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; @@ -24,16 +24,16 @@ class Middleware extends AnnotationBase implements ErgodicAnnotation * @var string * @Required() */ - public $middleware; + public $name; /** * @var string[] */ public $params = []; - public function __construct($middleware, $params = []) + public function __construct($name, $params = []) { - $this->middleware = $middleware; + $this->name = $name; $this->params = $params; } } diff --git a/src/ZM/Annotation/Http/MiddlewareClass.php b/src/ZM/Annotation/Middleware/MiddlewareClass.php similarity index 94% rename from src/ZM/Annotation/Http/MiddlewareClass.php rename to src/ZM/Annotation/Middleware/MiddlewareClass.php index 2815c70c..6d926d01 100644 --- a/src/ZM/Annotation/Http/MiddlewareClass.php +++ b/src/ZM/Annotation/Middleware/MiddlewareClass.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace ZM\Annotation\Http; +namespace ZM\Annotation\Middleware; use Attribute; use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor; diff --git a/src/ZM/Annotation/OneBot/OnOneBotEvent.php b/src/ZM/Annotation/OneBot/OnOneBotEvent.php new file mode 100644 index 00000000..472e8063 --- /dev/null +++ b/src/ZM/Annotation/OneBot/OnOneBotEvent.php @@ -0,0 +1,53 @@ +type = $type; + $this->detail_type = $detail_type; + $this->impl = $impl; + $this->platform = $platform; + $this->self_id = $self_id; + $this->sub_type = $sub_type; + } +} diff --git a/src/ZM/Annotation/Swoole/OnCloseEvent.php b/src/ZM/Annotation/Swoole/OnCloseEvent.php deleted file mode 100644 index 37886ff4..00000000 --- a/src/ZM/Annotation/Swoole/OnCloseEvent.php +++ /dev/null @@ -1,31 +0,0 @@ -connect_type = $connect_type; - $this->rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnManagerStartEvent.php b/src/ZM/Annotation/Swoole/OnManagerStartEvent.php deleted file mode 100644 index 9a291397..00000000 --- a/src/ZM/Annotation/Swoole/OnManagerStartEvent.php +++ /dev/null @@ -1,25 +0,0 @@ -rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnMessageEvent.php b/src/ZM/Annotation/Swoole/OnMessageEvent.php deleted file mode 100644 index 1d0daf51..00000000 --- a/src/ZM/Annotation/Swoole/OnMessageEvent.php +++ /dev/null @@ -1,31 +0,0 @@ -connect_type = $connect_type; - $this->rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnOpenEvent.php b/src/ZM/Annotation/Swoole/OnOpenEvent.php deleted file mode 100644 index 9f414ac6..00000000 --- a/src/ZM/Annotation/Swoole/OnOpenEvent.php +++ /dev/null @@ -1,31 +0,0 @@ -connect_type = $connect_type; - $this->rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnPipeMessageEvent.php b/src/ZM/Annotation/Swoole/OnPipeMessageEvent.php deleted file mode 100644 index 32a206b7..00000000 --- a/src/ZM/Annotation/Swoole/OnPipeMessageEvent.php +++ /dev/null @@ -1,32 +0,0 @@ -action = $action; - } -} diff --git a/src/ZM/Annotation/Swoole/OnRequestEvent.php b/src/ZM/Annotation/Swoole/OnRequestEvent.php deleted file mode 100644 index f5425e93..00000000 --- a/src/ZM/Annotation/Swoole/OnRequestEvent.php +++ /dev/null @@ -1,25 +0,0 @@ -rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnSave.php b/src/ZM/Annotation/Swoole/OnSave.php deleted file mode 100644 index 43cccd48..00000000 --- a/src/ZM/Annotation/Swoole/OnSave.php +++ /dev/null @@ -1,21 +0,0 @@ -worker_id = $worker_id; - } -} diff --git a/src/ZM/Annotation/Swoole/OnSwooleEvent.php b/src/ZM/Annotation/Swoole/OnSwooleEvent.php deleted file mode 100644 index 5193cb1b..00000000 --- a/src/ZM/Annotation/Swoole/OnSwooleEvent.php +++ /dev/null @@ -1,43 +0,0 @@ -type = $type; - $this->rule = $rule; - $this->level = $level; - } - - public function getType(): string - { - return $this->type; - } - - public function setType(string $type) - { - $this->type = $type; - } -} diff --git a/src/ZM/Annotation/Swoole/OnSwooleEventBase.php b/src/ZM/Annotation/Swoole/OnSwooleEventBase.php deleted file mode 100644 index a877dfd2..00000000 --- a/src/ZM/Annotation/Swoole/OnSwooleEventBase.php +++ /dev/null @@ -1,45 +0,0 @@ -rule !== '' ? $this->rule : true; - } - - public function setRule(string $rule) - { - $this->rule = $rule; - } - - public function getLevel(): int - { - return $this->level; - } - - /** - * @param int $level - */ - public function setLevel($level) - { - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnTask.php b/src/ZM/Annotation/Swoole/OnTask.php deleted file mode 100644 index 10ed4ec7..00000000 --- a/src/ZM/Annotation/Swoole/OnTask.php +++ /dev/null @@ -1,47 +0,0 @@ -task_name = $task_name; - $this->rule = $rule; - } - - /** - * @return string 返回规则语句 - */ - public function getRule(): string - { - return $this->rule; - } -} diff --git a/src/ZM/Annotation/Swoole/OnTaskEvent.php b/src/ZM/Annotation/Swoole/OnTaskEvent.php deleted file mode 100644 index 512f4422..00000000 --- a/src/ZM/Annotation/Swoole/OnTaskEvent.php +++ /dev/null @@ -1,25 +0,0 @@ -rule = $rule; - $this->level = $level; - } -} diff --git a/src/ZM/Annotation/Swoole/OnTick.php b/src/ZM/Annotation/Swoole/OnTick.php deleted file mode 100644 index dc56bd61..00000000 --- a/src/ZM/Annotation/Swoole/OnTick.php +++ /dev/null @@ -1,39 +0,0 @@ -tick_ms = $tick_ms; - $this->worker_id = $worker_id; - } -} diff --git a/src/ZM/Annotation/Swoole/SwooleHandler.php b/src/ZM/Annotation/Swoole/SwooleHandler.php deleted file mode 100644 index 5a130094..00000000 --- a/src/ZM/Annotation/Swoole/SwooleHandler.php +++ /dev/null @@ -1,36 +0,0 @@ -event = $event; - $this->params = $params; - } -} diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index f2f1e386..35886738 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -4,15 +4,10 @@ declare(strict_types=1); namespace ZM\Command; -use ArrayIterator; -use League\CLImate\CLImate; -use Phar; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Console\TermColor; -use ZM\Utils\DataProvider; class BuildCommand extends Command { @@ -20,10 +15,8 @@ class BuildCommand extends Command protected static $defaultName = 'build'; /** - * @var OutputInterface + * 配置 */ - private $output; - protected function configure() { $this->setDescription('Build an ".phar" file | 将项目构建一个phar包'); @@ -34,8 +27,9 @@ class BuildCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { + /* TODO $this->output = $output; - $target_dir = $input->getOption('target') ?? (WORKING_DIR); + $target_dir = $input->getOption('target') ?? WORKING_DIR; if (mb_strpos($target_dir, '../')) { $target_dir = realpath($target_dir); } @@ -58,47 +52,49 @@ class BuildCommand extends Command } $filename = 'server.phar'; $this->build($target_dir, $filename); - - return 0; + */ + $output->writeln('Not implemented.'); + return 1; } + /* + private function build($target_dir, $filename) + { + @unlink($target_dir . $filename); + $phar = new Phar($target_dir . $filename); + $phar->startBuffering(); - private function build($target_dir, $filename) - { - @unlink($target_dir . $filename); - $phar = new Phar($target_dir . $filename); - $phar->startBuffering(); + $all = DataProvider::scanDirFiles(DataProvider::getSourceRootDir(), true, true); - $all = DataProvider::scanDirFiles(DataProvider::getSourceRootDir(), true, true); + $all = array_filter($all, function ($x) { + $dirs = preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $x); + return !($dirs !== 1); + }); - $all = array_filter($all, function ($x) { - $dirs = preg_match('/(^(bin|config|resources|src|vendor)\\/|^(composer\\.json|README\\.md)$)/', $x); - return !($dirs !== 1); - }); + sort($all); - sort($all); + $archive_dir = DataProvider::getSourceRootDir(); + $map = []; - $archive_dir = DataProvider::getSourceRootDir(); - $map = []; - - if (class_exists('\\League\\CLImate\\CLImate')) { - $climate = new CLImate(); - $progress = $climate->progress()->total(count($all)); - } - foreach ($all as $k => $v) { - $map[$v] = $archive_dir . '/' . $v; - if (isset($progress)) { - $progress->current($k + 1, 'Adding ' . $v); + if (class_exists('\\League\\CLImate\\CLImate')) { + $climate = new CLImate(); + $progress = $climate->progress()->total(count($all)); } + foreach ($all as $k => $v) { + $map[$v] = $archive_dir . '/' . $v; + if (isset($progress)) { + $progress->current($k + 1, 'Adding ' . $v); + } + } + $this->output->write('Building...'); + $phar->buildFromIterator(new ArrayIterator($map)); + $phar->setStub( + "#!/usr/bin/env php\n" . + $phar->createDefaultStub(LOAD_MODE == 0 ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php') + ); + $phar->stopBuffering(); + $this->output->writeln(''); + $this->output->writeln('Successfully built. Location: ' . $target_dir . "{$filename}"); + $this->output->writeln('You may use `chmod +x server.phar` to let phar executable with `./` command'); } - $this->output->write('Building...'); - $phar->buildFromIterator(new ArrayIterator($map)); - $phar->setStub( - "#!/usr/bin/env php\n" . - $phar->createDefaultStub(LOAD_MODE == 0 ? 'src/entry.php' : 'vendor/zhamao/framework/src/entry.php') - ); - $phar->stopBuffering(); - $this->output->writeln(''); - $this->output->writeln('Successfully built. Location: ' . $target_dir . "{$filename}"); - $this->output->writeln('You may use `chmod +x server.phar` to let phar executable with `./` command'); - } + */ } diff --git a/src/ZM/Command/CheckConfigCommand.php b/src/ZM/Command/CheckConfigCommand.php index ace9f562..ef49127b 100644 --- a/src/ZM/Command/CheckConfigCommand.php +++ b/src/ZM/Command/CheckConfigCommand.php @@ -27,7 +27,7 @@ class CheckConfigCommand extends Command return 1; } $current_cfg = getcwd() . '/config/'; - $remote_cfg = include_once FRAMEWORK_ROOT_DIR . '/config/global.php'; + $remote_cfg = include_once FRAMEWORK_ROOT_DIR . '/config/global_old.php'; if (file_exists($current_cfg . 'global.php')) { $this->check($remote_cfg, 'global.php', $output); } diff --git a/src/ZM/Command/Generate/SystemdGenerateCommand.php b/src/ZM/Command/Generate/SystemdGenerateCommand.php index 14519631..6de2711a 100644 --- a/src/ZM/Command/Generate/SystemdGenerateCommand.php +++ b/src/ZM/Command/Generate/SystemdGenerateCommand.php @@ -8,7 +8,6 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use ZM\Config\ZMConfig; -use ZM\Utils\DataProvider; class SystemdGenerateCommand extends Command { @@ -22,7 +21,7 @@ class SystemdGenerateCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { - ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); + ZMConfig::setDirectory(SOURCE_ROOT_DIR . '/config'); $path = $this->generate(); $output->writeln('成功生成 systemd 文件,位置:' . $path . ''); $output->writeln('有关如何使用 systemd 配置文件,请访问 `https://github.com/zhamao-robot/zhamao-framework/issues/36`'); @@ -38,8 +37,7 @@ class SystemdGenerateCommand extends Command global $argv; $s .= "\nExecStart=" . PHP_BINARY . " {$argv[0]} server"; $s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"; - @mkdir(getcwd() . '/resources/'); - file_put_contents(ZMConfig::get('global', 'zm_data') . 'zhamao.service', $s); - return ZMConfig::get('global', 'zm_data') . 'zhamao.service'; + file_put_contents(WORKING_DIR . '/zhamao.service', $s); + return WORKING_DIR . '/zhamao.service'; } } diff --git a/src/ZM/Command/InitCommand.php b/src/ZM/Command/InitCommand.php index 26ffe578..8a7f411f 100644 --- a/src/ZM/Command/InitCommand.php +++ b/src/ZM/Command/InitCommand.php @@ -48,7 +48,7 @@ class InitCommand extends Command $info = pathinfo($file); @mkdir($base_path . $info['dirname'], 0777, true); echo 'Copying ' . $file . PHP_EOL; - $package_name = (json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name']); + $package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name']; copy($base_path . '/vendor/' . $package_name . $file, $base_path . $file); } else { echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL; diff --git a/src/ZM/Command/Module/ModuleCommand.php b/src/ZM/Command/Module/ModuleCommand.php deleted file mode 100644 index f8134b5b..00000000 --- a/src/ZM/Command/Module/ModuleCommand.php +++ /dev/null @@ -1,46 +0,0 @@ -addOption('env', null, InputOption::VALUE_REQUIRED, '设置环境类型 (production, development, staging)', ''); - $this->addOption('log-theme', null, InputOption::VALUE_REQUIRED, '改变终端的主题配色', 'default'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); - ZMConfig::setEnv($input->getOption('env')); - if (ZMConfig::get('global') === false) { - exit(zm_internal_errcode('E00007') . 'Global config load failed: ' . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n"); - } - - // 定义常量 - /** @noinspection PhpIncludeInspection */ - include_once DataProvider::getFrameworkRootDir() . '/src/ZM/global_defines.php'; - - Console::init( - ZMConfig::get('global', 'info_level') ?? 2, - null, - $input->getOption('log-theme'), - ($o = ZMConfig::get('console_color')) === false ? [] : $o - ); - - $timezone = ZMConfig::get('global', 'timezone') ?? 'Asia/Shanghai'; - date_default_timezone_set($timezone); - return 0; - } -} diff --git a/src/ZM/Command/Module/ModuleListCommand.php b/src/ZM/Command/Module/ModuleListCommand.php deleted file mode 100644 index 0a9074f8..00000000 --- a/src/ZM/Command/Module/ModuleListCommand.php +++ /dev/null @@ -1,94 +0,0 @@ -setDescription('查看所有模块信息'); - $this->setHelp('此功能将会把炸毛框架的模块列举出来。'); - } - - /** - * @throws ZMException - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - - $list = ModuleManager::getConfiguredModules(); - - foreach ($list as $v) { - echo '[' . Console::setColor($v['name'], 'green') . ']' . PHP_EOL; - $out_list = ['类型' => '源码(source)']; - if (isset($v['version'])) { - $out_list['版本'] = $v['version']; - } - if (isset($v['description'])) { - $out_list['描述'] = $v['description']; - } - $out_list['目录'] = str_replace(DataProvider::getSourceRootDir() . '/', '', $v['module-path']); - $this->printList($out_list); - } - if ($list === []) { - echo Console::setColor('没有发现已编写打包配置文件(zm.json)的模块!', 'yellow') . PHP_EOL; - } - $list = ModuleManager::getPackedModules(); - foreach ($list as $v) { - echo '[' . Console::setColor($v['name'], 'gold') . ']' . PHP_EOL; - $out_list = ['类型' => '模块包(phar)']; - if (isset($v['module-config']['version'])) { - $out_list['版本'] = $v['module-config']['version']; - } - if (isset($v['module-config']['description'])) { - $out_list['描述'] = $v['module-config']['description']; - } - $out_list['位置'] = str_replace(DataProvider::getSourceRootDir() . '/', '', $v['phar-path']); - $this->printList($out_list); - } - if ($list === []) { - echo Console::setColor('没有发现已打包且装载的模块!', 'yellow') . PHP_EOL; - } - - $list = ModuleManager::getComposerModules(); - foreach ($list as $v) { - echo '[' . Console::setColor($v['name'], 'blue') . ']' . PHP_EOL; - $out_list = ['类型' => 'Composer库(composer)']; - $out_list['包名'] = $v['composer-name']; - $out_list['目录'] = str_replace(DataProvider::getSourceRootDir() . '/', '', $v['module-path']); - if (isset($v['version'])) { - $out_list['版本'] = $v['version']; - } - if (isset($v['description'])) { - $out_list['描述'] = $v['description']; - } - $out_list['命名空间'] = $v['namespace']; - $this->printList($out_list); - } - if ($list === []) { - echo Console::setColor('没有发现Composer模块!', 'yellow') . PHP_EOL; - } - return 0; - } - - private function printList($list) - { - foreach ($list as $k => $v) { - echo "\t" . $k . ': ' . Console::setColor($v, 'yellow') . PHP_EOL; - } - } -} diff --git a/src/ZM/Command/Module/ModulePackCommand.php b/src/ZM/Command/Module/ModulePackCommand.php deleted file mode 100644 index 71ca0fa7..00000000 --- a/src/ZM/Command/Module/ModulePackCommand.php +++ /dev/null @@ -1,49 +0,0 @@ -addArgument('module-name', InputArgument::REQUIRED); - $this->setDescription('将配置好的模块构建一个phar包'); - $this->setHelp('此功能将会把炸毛框架的模块打包为".phar",供发布和执行。'); - $this->addOption('target', 'D', InputOption::VALUE_REQUIRED, 'Output Directory | 指定输出目录'); - } - - /** - * @throws ZMException - */ - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - $list = ModuleManager::getConfiguredModules(); - if (!isset($list[$input->getArgument('module-name')])) { - $output->writeln('不存在模块 ' . $input->getArgument('module-name') . ' !'); - return 1; - } - $result = ModuleManager::packModule($list[$input->getArgument('module-name')], $input->getOption('target') ?? (DataProvider::getDataFolder() . '/output')); - if ($result) { - Console::success('打包完成!'); - } else { - Console::error('打包失败!'); - } - return 0; - } -} diff --git a/src/ZM/Command/Module/ModuleUnpackCommand.php b/src/ZM/Command/Module/ModuleUnpackCommand.php deleted file mode 100644 index de4f12d2..00000000 --- a/src/ZM/Command/Module/ModuleUnpackCommand.php +++ /dev/null @@ -1,47 +0,0 @@ -addOption('overwrite-light-cache', null, null, '覆盖现有的LightCache项目'); - $this->addOption('overwrite-zm-data', null, null, '覆盖现有的zm_data文件'); - $this->addOption('overwrite-source', null, null, '覆盖现有的源码文件'); - $this->addOption('ignore-depends', null, null, '解包时忽略检查依赖'); - $this->addArgument('module-name', InputArgument::REQUIRED, '模块名称'); - $this->setDescription('解包一个phar模块到src目录'); - $this->setHelp('此功能将phar格式的模块包解包到src目录下。'); - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - parent::execute($input, $output); - - $list = ModuleManager::getPackedModules(); - if (!isset($list[$input->getArgument('module-name')])) { - $output->writeln('不存在打包的模块 ' . $input->getArgument('module-name') . ' !'); - return 1; - } - $result = ModuleManager::unpackModule($list[$input->getArgument('module-name')], $input->getOptions()); - if ($result) { - Console::success('解压完成!'); - } else { - Console::error('解压失败!'); - } - return 0; - } -} diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php deleted file mode 100644 index 70addf71..00000000 --- a/src/ZM/Command/PureHttpCommand.php +++ /dev/null @@ -1,102 +0,0 @@ -setDescription('Run a simple http server | 启动一个简单的文件 HTTP 服务器'); - $this->setHelp('直接运行可以启动'); - $this->addArgument('dir', InputArgument::REQUIRED, 'Your directory'); - $this->addOption('host', 'H', InputOption::VALUE_REQUIRED, '启动监听地址'); - $this->addOption('port', 'P', InputOption::VALUE_REQUIRED, '启动监听地址的端口'); - // ... - } - - protected function execute(InputInterface $input, OutputInterface $output): int - { - $tty_width = explode(' ', trim(exec('stty size')))[1]; - if (realpath($input->getArgument('dir') ?? '.') === false) { - $output->writeln('Directory error(' . ($input->getArgument('dir') ?? '.') . '): no such file or directory.'); - return self::FAILURE; - } - ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); - $global = ZMConfig::get('global'); - $host = $input->getOption('host') ?? $global['host']; - $port = $input->getOption('port') ?? $global['port']; - - $index = ['index.html', 'index.htm']; - $out = [ - 'listen' => $host . ':' . $port, - 'version' => ZM_VERSION, - 'web_root' => realpath($input->getArgument('dir') ?? '.'), - 'index' => implode(',', $index), - ]; - $printer = new TablePrinter($out); - $printer->printAll(); - $server = new Server($host, $port); - $server->set(ZMConfig::get('global', 'swoole')); - Console::init(2, $server); - ZMAtomic::$atomics['request'] = []; - for ($i = 0; $i < 32; ++$i) { - ZMAtomic::$atomics['request'][$i] = new Atomic(0); - } - $server->on('request', function (Request $request, Response $response) use ($input, $index, $server) { - ZMAtomic::$atomics['request'][$server->worker_id]->add(1); - HttpUtil::handleStaticPage( - $request->server['request_uri'], - $response, - [ - 'document_root' => realpath($input->getArgument('dir') ?? '.'), - 'document_index' => $index, - ] - ); - // echo "\r" . Coroutine::stats()["coroutine_peak_num"]; - }); - $server->on('start', function ($server) { - Process::signal(SIGINT, function () use ($server) { - echo "\r"; - logger()->notice('服务器收到中断信号 SIGINT,正在停止'); - for ($i = 0; $i < 32; ++$i) { - $num = ZMAtomic::$atomics['request'][$i]->get(); - if ($num != 0) { - echo "[{$i}]: " . $num . "\n"; - } - } - $server->shutdown(); - $server->stop(); - }); - logger()->notice('服务器已启动,请使用 Ctrl+C 以停止。'); - }); - $server->start(); - // return this if there was no problem running the command - // (it's equivalent to returning int(0)) - return 0; - // or return this if some error happened during the execution - // (it's equivalent to returning int(1)) - // return 1; - } -} diff --git a/src/ZM/Command/Server/ServerCommand.php b/src/ZM/Command/Server/ServerCommand.php index 173c7efc..c99e03a8 100644 --- a/src/ZM/Command/Server/ServerCommand.php +++ b/src/ZM/Command/Server/ServerCommand.php @@ -8,7 +8,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use ZM\Exception\ZMKnownException; -use ZM\Utils\Manager\ProcessManager; +use ZM\Process\ProcessStateManager; abstract class ServerCommand extends Command { @@ -19,11 +19,12 @@ abstract class ServerCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output): int { - $file = ProcessManager::getProcessState(ZM_PROCESS_MASTER); + $file = ProcessStateManager::getProcessState(ZM_PROCESS_MASTER); + /* @noinspection PhpComposerExtensionStubsInspection */ if ($file === false || posix_getsid(intval($file['pid'])) === false) { $output->writeln('未检测到正在运行的守护进程或框架进程!'); - if (ProcessManager::isStateEmpty()) { - ProcessManager::removeProcessState(ZM_PROCESS_MASTER); + if (ProcessStateManager::isStateEmpty()) { + ProcessStateManager::removeProcessState(ZM_PROCESS_MASTER); } else { $output->writeln('检测到可能残留的守护进程或框架进程,请使用命令关闭:server:stop --force'); } diff --git a/src/ZM/Command/Server/ServerStartCommand.php b/src/ZM/Command/Server/ServerStartCommand.php index 8fbf706d..eadcc1f9 100644 --- a/src/ZM/Command/Server/ServerStartCommand.php +++ b/src/ZM/Command/Server/ServerStartCommand.php @@ -4,24 +4,24 @@ declare(strict_types=1); namespace ZM\Command\Server; -use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use ZM\Exception\ConfigException; +use ZM\Exception\InitException; use ZM\Exception\ZMKnownException; use ZM\Framework; -use ZM\Utils\Manager\ProcessManager; +use ZM\Process\ProcessStateManager; class ServerStartCommand extends ServerCommand { protected static $defaultName = 'server'; - public static function exportDefinition(): InputDefinition + public static function exportOptionArray(): array { $cmd = new self(); $cmd->configure(); - return $cmd->getDefinition(); + return array_map(function ($x) { return $x->getDefault(); }, $cmd->getDefinition()->getOptions()); } protected function configure() @@ -29,7 +29,10 @@ class ServerStartCommand extends ServerCommand $this->setAliases(['server:start']); $this->setDefinition([ new InputOption('debug-mode', 'D', null, '开启调试模式 (这将关闭协程化)'), + new InputOption('config-dir', null, InputOption::VALUE_REQUIRED, '指定其他配置文件目录'), + new InputOption('driver', null, InputOption::VALUE_REQUIRED, '指定驱动类型'), new InputOption('log-debug', null, null, '调整消息等级到debug (log-level=4)'), + new InputOption('log-level', null, InputOption::VALUE_REQUIRED, '调整消息等级到debug (log-level=4)'), new InputOption('log-verbose', null, null, '调整消息等级到verbose (log-level=3)'), new InputOption('log-info', null, null, '调整消息等级到info (log-level=2)'), new InputOption('log-warning', null, null, '调整消息等级到warning (log-level=1)'), @@ -59,22 +62,26 @@ class ServerStartCommand extends ServerCommand /** * @throws ZMKnownException - * @throws ConfigException + * @throws ConfigException|InitException + * @noinspection PhpComposerExtensionStubsInspection */ protected function execute(InputInterface $input, OutputInterface $output): int { - if (($opt = $input->getOption('env')) !== null) { + // 这段用于config的环境解析,但显然不是很好的方式,应该改成一个独立的方法,不应该在这里检查,但暂时搁置,TODO + /* if (($opt = $input->getOption('env')) !== null) { if (!in_array($opt, ['production', 'staging', 'development', ''])) { $output->writeln(' "--env" option only accept production, development, staging and [empty] ! '); return 1; } - } - $state = ProcessManager::getProcessState(ZM_PROCESS_MASTER); - if (!$input->getOption('no-state-check')) { - if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) { - $output->writeln("检测到已经在 pid: {$state['pid']} 进程启动了框架!"); - $output->writeln('不可以同时启动两个框架!'); - return 1; + }*/ + if (\OneBot\Driver\Process\ProcessManager::isSupportedMultiProcess()) { + $state = ProcessStateManager::getProcessState(ZM_PROCESS_MASTER); + if (!$input->getOption('no-state-check')) { + if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) { + $output->writeln("检测到已经在 pid: {$state['pid']} 进程启动了框架!"); + $output->writeln('不可以同时启动两个框架!'); + return 1; + } } } (new Framework($input->getOptions()))->start(); diff --git a/src/ZM/Command/Server/ServerStopCommand.php b/src/ZM/Command/Server/ServerStopCommand.php index d2ef5006..47158d65 100644 --- a/src/ZM/Command/Server/ServerStopCommand.php +++ b/src/ZM/Command/Server/ServerStopCommand.php @@ -8,8 +8,8 @@ use Swoole\Process; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use ZM\Utils\DataProvider; -use ZM\Utils\Manager\ProcessManager; +use ZM\Process\ProcessStateManager; +use ZM\Store\FileSystem; class ServerStopCommand extends ServerCommand { @@ -26,8 +26,8 @@ class ServerStopCommand extends ServerCommand protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getOption('force') !== false) { - $file_path = _zm_pid_dir(); - $list = DataProvider::scanDirFiles($file_path, false, true); + $file_path = ZM_PID_DIR; + $list = FileSystem::scanDirFiles($file_path, false, true); foreach ($list as $file) { $name = explode('.', $file); if (end($name) == 'pid') { @@ -46,7 +46,7 @@ class ServerStopCommand extends ServerCommand Process::kill(intval($this->daemon_file['pid']), SIGTERM); } $i = 10; - while (ProcessManager::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) { + while (ProcessStateManager::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) { sleep(1); --$i; } diff --git a/src/ZM/Config/ZMConfig.php b/src/ZM/Config/ZMConfig.php index 742f9714..cec7110d 100644 --- a/src/ZM/Config/ZMConfig.php +++ b/src/ZM/Config/ZMConfig.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace ZM\Config; use ZM\Exception\ConfigException; -use ZM\Utils\DataProvider; +use ZM\Store\FileSystem; class ZMConfig { @@ -22,7 +22,7 @@ class ZMConfig public static $config = []; /** @var string 配置文件 */ - private static $path = '.'; + private static $path = 'config'; /** @var string 上次的路径 */ private static $last_path = '.'; @@ -187,7 +187,7 @@ class ZMConfig private static function parseList(string $name): void { $list = []; - $files = DataProvider::scanDirFiles(self::$path, true, true); + $files = FileSystem::scanDirFiles(self::$path, true, true); foreach ($files as $file) { logger()->debug('正在从目录' . self::$path . '读取配置文件 ' . $file); $info = pathinfo($file); @@ -227,15 +227,15 @@ class ZMConfig logger()->warning('文件名 ' . $info['filename'] . ' 不合法(含有"."),请检查文件名是否合法。'); continue; } - $obj->path = realpath(self::$path . '/' . $info['dirname'] . '/' . $info['basename']); + $obj->path = zm_dir(self::$path . '/' . $info['dirname'] . '/' . $info['basename']); $obj->extension = $ext; - $obj->data = self::readConfigFromFile(realpath(self::$path . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); + $obj->data = self::readConfigFromFile(zm_dir(self::$path . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); $list[] = $obj; } } // 如果是源码模式,config目录和default目录相同,所以不需要继续采摘default目录下的文件 if (realpath(self::$path) !== realpath(self::DEFAULT_PATH)) { - $files = DataProvider::scanDirFiles(self::DEFAULT_PATH, true, true); + $files = FileSystem::scanDirFiles(self::DEFAULT_PATH, true, true); foreach ($files as $file) { $info = pathinfo($file); $info['extension'] = $info['extension'] ?? ''; @@ -249,7 +249,7 @@ class ZMConfig $obj->is_env = false; $obj->path = realpath(self::DEFAULT_PATH . '/' . $info['dirname'] . '/' . $info['basename']); $obj->extension = $info['extension']; - $obj->data = self::readConfigFromFile(realpath(self::DEFAULT_PATH . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); + $obj->data = self::readConfigFromFile(zm_dir(self::DEFAULT_PATH . '/' . $info['dirname'] . '/' . $info['basename']), $info['extension']); $list[] = $obj; } } diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index ad8ef2bf..810337b4 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -13,97 +13,61 @@ use ZM\Command\BuildCommand; use ZM\Command\CheckConfigCommand; use ZM\Command\Generate\SystemdGenerateCommand; use ZM\Command\InitCommand; -use ZM\Command\Module\ModuleListCommand; -use ZM\Command\Module\ModulePackCommand; -use ZM\Command\Module\ModuleUnpackCommand; -use ZM\Command\PureHttpCommand; use ZM\Command\Server\ServerReloadCommand; use ZM\Command\Server\ServerStartCommand; use ZM\Command\Server\ServerStatusCommand; use ZM\Command\Server\ServerStopCommand; use ZM\Exception\InitException; +/** + * 命令行启动的入口文件,用于初始化环境变量,并启动命令行应用 + * + * 这里启动的不是框架,而是框架相关的命令行环境 + */ class ConsoleApplication extends Application { - public const VERSION_ID = 473; - - public const VERSION = '2.8.0'; - private static $obj; /** * @throws InitException */ - public function __construct(string $name = 'UNKNOWN') + public function __construct(string $name = 'zhamao-framework') { if (self::$obj !== null) { throw new InitException(zm_internal_errcode('E00069') . 'Initializing another Application is not allowed!'); } - define('ZM_VERSION_ID', self::VERSION_ID); - define('ZM_VERSION', self::VERSION); - self::$obj = $this; + // 如果已经有定义了全局的 WORKING_DIR,那么就报错 + // if (defined('WORKING_DIR')) { + // throw new InitException(); + // } + + // 启动前检查炸毛运行情况 + // _zm_env_check(); + + // 初始化命令 + $this->add(new ServerStatusCommand()); // server运行状态 + $this->add(new ServerReloadCommand()); // server重载 + $this->add(new ServerStopCommand()); // server停止 + $this->add(new ServerStartCommand()); // 运行主服务的指令控制器 + $this->add(new SystemdGenerateCommand()); // 生成systemd文件 + if (LOAD_MODE === 1) { // 如果是 Composer 模式加载的,那么可以输入 check:config 命令,检查配置文件是否需要更新 + $this->add(new CheckConfigCommand()); + } + if (Phar::running() === '') { // 不是 Phar 模式的话,可以执行打包解包初始化命令 + $this->add(new BuildCommand()); // 用于将整个应用打包为一个可执行的 phar + $this->add(new InitCommand()); // 用于在 Composer 模式启动下,初始化脚手架文件 + // $this->add(new PluginPackCommand()); // 用于打包一个子模块为 phar 并进行分发 + // $this->add(new PluginListCommand()); // 用于列出已配置的子模块列表(存在 zm.json 文件的目录) + // $this->add(new PluginUnpackCommand()); // 用于将打包好的 phar 模块解包到 src 目录中 + } + + self::$obj = $this; // 用于标记已经初始化完成 parent::__construct($name, ZM_VERSION); } /** - * @throws InitException + * {@inheritdoc} */ - public function initEnv(string $with_default_cmd = ''): ConsoleApplication - { - if (defined('WORKING_DIR')) { - throw new InitException(); - } - - _zm_env_check(); - - // 定义多进程的全局变量 - define('ZM_PROCESS_MASTER', 1); - define('ZM_PROCESS_MANAGER', 2); - define('ZM_PROCESS_WORKER', 4); - define('ZM_PROCESS_USER', 8); - define('ZM_PROCESS_TASKWORKER', 16); - - define('WORKING_DIR', getcwd()); - - if (!is_dir(_zm_pid_dir())) { - @mkdir(_zm_pid_dir()); - } - - if (Phar::running() !== '') { - echo "* Running in phar mode.\n"; - define('SOURCE_ROOT_DIR', Phar::running()); - define('LOAD_MODE', is_dir(SOURCE_ROOT_DIR . '/src/ZM') ? 0 : 1); - define('FRAMEWORK_ROOT_DIR', LOAD_MODE == 1 ? (SOURCE_ROOT_DIR . '/vendor/zhamao/framework') : SOURCE_ROOT_DIR); - } else { - define('SOURCE_ROOT_DIR', WORKING_DIR); - define('LOAD_MODE', is_dir(SOURCE_ROOT_DIR . '/src/ZM') ? 0 : 1); - define('FRAMEWORK_ROOT_DIR', realpath(__DIR__ . '/../../')); - } - - $this->addCommands([ - new ServerStatusCommand(), - new ServerReloadCommand(), - new ServerStopCommand(), - new ServerStartCommand(), // 运行主服务的指令控制器 - new PureHttpCommand(), // 纯HTTP服务器指令 - new SystemdGenerateCommand(), - ]); - if (LOAD_MODE === 1) { - $this->add(new CheckConfigCommand()); - } - if (Phar::running() === '') { - $this->add(new BuildCommand()); - $this->add(new InitCommand()); - $this->add(new ModulePackCommand()); - $this->add(new ModuleListCommand()); - $this->add(new ModuleUnpackCommand()); - } - if (!empty($with_default_cmd)) { - $this->setDefaultCommand($with_default_cmd); - } - return $this; - } - public function run(InputInterface $input = null, OutputInterface $output = null): int { try { diff --git a/src/ZM/Container/BoundMethod.php b/src/ZM/Container/BoundMethod.php deleted file mode 100644 index b2a89329..00000000 --- a/src/ZM/Container/BoundMethod.php +++ /dev/null @@ -1,104 +0,0 @@ -make($callback[0]); - } - - if (!is_callable($callback)) { - throw new InvalidArgumentException('Callback is not callable.'); - } - - return call_user_func_array($callback, self::getMethodDependencies($container, $callback, $parameters)); - } - - /** - * Get all dependencies for a given method. - * - * @param callable|string $callback - * @throws ReflectionException - */ - protected static function getMethodDependencies(ContainerInterface $container, $callback, array $parameters = []): array - { - $dependencies = []; - - foreach (ReflectionUtil::getCallReflector($callback)->getParameters() as $i => $parameter) { - if (isset($parameters[$i]) && $parameter->hasType() && ($type = $parameter->getType())) { - if ($type instanceof \ReflectionNamedType && gettype($parameters[$i]) === $type->getName()) { - $dependencies[] = $parameters[$i]; - continue; - } - } - static::addDependencyForCallParameter($container, $parameter, $parameters, $dependencies); - } - - return array_merge($dependencies, array_values($parameters)); - } - - /** - * Get the dependency for the given call parameter. - * - * @throws EntryResolutionException - */ - protected static function addDependencyForCallParameter( - ContainerInterface $container, - ReflectionParameter $parameter, - array &$parameters, - array &$dependencies - ): void { - if (array_key_exists($param_name = $parameter->getName(), $parameters)) { - $dependencies[] = $parameters[$param_name]; - - unset($parameters[$param_name]); - } elseif (!is_null($class_name = ReflectionUtil::getParameterClassName($parameter))) { - if (array_key_exists($class_name, $parameters)) { - $dependencies[] = $parameters[$class_name]; - - unset($parameters[$class_name]); - } elseif ($parameter->isVariadic()) { - $variadic_dependencies = $container->make($class_name); - - $dependencies = array_merge($dependencies, is_array($variadic_dependencies) - ? $variadic_dependencies - : [$variadic_dependencies]); - } else { - $dependencies[] = $container->make($class_name); - } - } elseif ($parameter->isDefaultValueAvailable()) { - $dependencies[] = $parameter->getDefaultValue(); - } elseif (!array_key_exists($param_name, $parameters) && !$parameter->isOptional()) { - $message = "无法解析类 {$parameter->getDeclaringClass()->getName()} 的依赖 {$parameter}"; - - throw new EntryResolutionException($message); - } - } -} diff --git a/src/ZM/Container/Container.php b/src/ZM/Container/Container.php deleted file mode 100644 index 3bbf4922..00000000 --- a/src/ZM/Container/Container.php +++ /dev/null @@ -1,61 +0,0 @@ -bound($id) || $this->getParent()->has($id); - } - - /** - * 获取一个绑定的实例 - * - * @template T - * @param class-string $abstract 类或接口名 - * @param array $parameters 参数 - * @throws EntryResolutionException - * @return Closure|mixed|T 实例 - */ - public function make(string $abstract, array $parameters = []) - { - if (isset($this->shared[$abstract])) { - return $this->shared[$abstract]; - } - - // 此类没有,父类有,则从父类中获取 - if (!$this->bound($abstract) && $this->getParent()->bound($abstract)) { - $this->log("{$abstract} is not bound, but in parent container, using parent container"); - return $this->getParent()->make($abstract, $parameters); - } - - return $this->traitMake($abstract, $parameters); - } -} diff --git a/src/ZM/Container/ContainerInterface.php b/src/ZM/Container/ContainerInterface.php deleted file mode 100644 index 26616c66..00000000 --- a/src/ZM/Container/ContainerInterface.php +++ /dev/null @@ -1,110 +0,0 @@ - $abstract 类或接口名 - * @param array $parameters 参数 - * @return Closure|mixed|T 实例 - */ - public function make(string $abstract, array $parameters = []); - - /** - * 调用对应的方法,并自动注入依赖 - * - * @param callable $callback 对应的方法 - * @param array $parameters 参数 - * @param null|string $default_method 默认方法 - * @return mixed - */ - public function call(callable $callback, array $parameters = [], string $default_method = null); -} diff --git a/src/ZM/Container/ContainerServicesProvider.php b/src/ZM/Container/ContainerServicesProvider.php deleted file mode 100644 index 4fca8092..00000000 --- a/src/ZM/Container/ContainerServicesProvider.php +++ /dev/null @@ -1,116 +0,0 @@ -registerGlobalServices(WorkerContainer::getInstance()); - break; - case 'request': - $this->registerRequestServices(Container::getInstance()); - break; - case 'message': - $this->registerConnectionServices(Container::getInstance()); - $this->registerMessageServices(Container::getInstance()); - break; - case 'connection': - $this->registerConnectionServices(Container::getInstance()); - break; - default: - break; - } - } - - /** - * 清理服务 - */ - public function cleanup(): void - { - container()->flush(); - } - - /** - * 注册全局服务 - */ - private function registerGlobalServices(ContainerInterface $container): void - { - $container->instance('path.working', DataProvider::getWorkingDir()); - $container->instance('path.source', DataProvider::getSourceRootDir()); - $container->alias('path.source', 'path.base'); - $container->instance('path.config', DataProvider::getSourceRootDir() . '/config'); - $container->instance('path.module_config', ZMConfig::get('global', 'config_dir')); - $container->instance('path.data', DataProvider::getDataFolder()); - $container->instance('path.framework', DataProvider::getFrameworkRootDir()); - - $container->instance('server', Framework::$server); - $container->instance('worker_id', Framework::$server->worker_id); - - $container->singleton(AdapterInterface::class, OneBot11Adapter::class); - $container->instance(LoggerInterface::class, ZMConfig::get('logging.logger')()); - } - - /** - * 注册请求服务(HTTP请求) - */ - private function registerRequestServices(ContainerInterface $container): void - { - $context = Context::$context[zm_cid()]; - $container->instance(Request::class, $context['request']); - $container->instance(Response::class, $context['response']); - $container->bind(ContextInterface::class, Closure::fromCallable('ctx')); - $container->alias(ContextInterface::class, Context::class); - } - - /** - * 注册消息服务(WS消息) - */ - private function registerMessageServices(ContainerInterface $container): void - { - $context = Context::$context[zm_cid()]; - $container->instance(Frame::class, $context['frame']); // WS 消息帧 - $container->bind(ContextInterface::class, Closure::fromCallable('ctx')); - $container->alias(ContextInterface::class, Context::class); - } - - /** - * 注册链接服务 - */ - private function registerConnectionServices(ContainerInterface $container): void - { - $context = Context::$context[zm_cid()]; - $container->instance(ConnectionObject::class, $context['connection']); - } -} diff --git a/src/ZM/Container/ContainerTrait.php b/src/ZM/Container/ContainerTrait.php deleted file mode 100644 index 4f68f335..00000000 --- a/src/ZM/Container/ContainerTrait.php +++ /dev/null @@ -1,736 +0,0 @@ -shouldLog()) { - $this->log('Container created'); - } - } - - /** - * 判断对应的类或接口是否已经注册 - * - * @param string $abstract 类或接口名 - */ - public function bound(string $abstract): bool - { - return array_key_exists($abstract, self::$bindings) - || array_key_exists($abstract, self::$instances) - || array_key_exists($abstract, $this->shared) - || $this->isAlias($abstract); - } - - /** - * 获取类别名(如存在) - * - * @param string $abstract 类或接口名 - * @return string 别名,不存在时返回传入的类或接口名 - */ - public function getAlias(string $abstract): string - { - if (!isset(self::$aliases[$abstract])) { - return $abstract; - } - - return $this->getAlias(self::$aliases[$abstract]); - } - - /** - * 注册一个类别名 - * - * @param string $abstract 类或接口名 - * @param string $alias 别名 - */ - public function alias(string $abstract, string $alias): void - { - if ($alias === $abstract) { - throw new InvalidArgumentException("[{$abstract}] is same as [{$alias}]"); - } - - self::$aliases[$alias] = $abstract; - - if ($this->shouldLog()) { - $this->log("[{$abstract}] is aliased as [{$alias}]"); - } - } - - /** - * 注册绑定 - * - * @param string $abstract 类或接口名 - * @param null|Closure|string $concrete 返回类实例的闭包,或是类名 - * @param bool $shared 是否共享 - */ - public function bind(string $abstract, $concrete = null, bool $shared = false): void - { - $this->dropStaleInstances($abstract); - - // 如果没有提供闭包,则默认为自动解析类名 - if (is_null($concrete)) { - $concrete = $abstract; - } - - $concrete_name = ''; - if ($this->shouldLog()) { - $concrete_name = ReflectionUtil::variableToString($concrete); - } - - // 如果不是闭包,则认为是类名,此时将其包装在一个闭包中,以方便后续处理 - if (!$concrete instanceof Closure) { - $concrete = $this->getClosure($abstract, $concrete); - } - - self::$bindings[$abstract] = compact('concrete', 'shared'); - - if ($this->shouldLog()) { - $this->log("[{$abstract}] is bound to [{$concrete_name}]" . ($shared ? ' (shared)' : '')); - } - } - - /** - * 注册绑定 - * - * 在已经绑定时不会重复注册 - * - * @param string $abstract 类或接口名 - * @param null|Closure|string $concrete 返回类实例的闭包,或是类名 - * @param bool $shared 是否共享 - */ - public function bindIf(string $abstract, $concrete = null, bool $shared = false): void - { - if (!$this->bound($abstract)) { - $this->bind($abstract, $concrete, $shared); - } - } - - /** - * 注册一个单例绑定 - * - * @param string $abstract 类或接口名 - * @param null|Closure|string $concrete 返回类实例的闭包,或是类名 - */ - public function singleton(string $abstract, $concrete = null): void - { - $this->bind($abstract, $concrete, true); - } - - /** - * 注册一个单例绑定 - * - * 在已经绑定时不会重复注册 - * - * @param string $abstract 类或接口名 - * @param null|Closure|string $concrete 返回类实例的闭包,或是类名 - */ - public function singletonIf(string $abstract, $concrete = null): void - { - if (!$this->bound($abstract)) { - $this->singleton($abstract, $concrete); - } - } - - /** - * 注册一个已有的实例,效果等同于单例绑定 - * - * @param string $abstract 类或接口名 - * @param mixed $instance 实例 - * @return mixed - */ - public function instance(string $abstract, $instance) - { - if (isset(self::$instances[$abstract])) { - return self::$instances[$abstract]; - } - - self::$instances[$abstract] = $instance; - - if ($this->shouldLog()) { - $class_name = ReflectionUtil::variableToString($instance); - $this->log("[{$abstract}] is bound to [{$class_name}] (instance)"); - } - - return $instance; - } - - /** - * 获取一个解析对应类实例的闭包 - * - * @param string $abstract 类或接口名 - */ - public function factory(string $abstract): Closure - { - return function () use ($abstract) { - return $this->make($abstract); - }; - } - - /** - * 清除所有绑定和实例 - */ - public function flush(): void - { - self::$aliases = []; - self::$bindings = []; - self::$instances = []; - - $this->shared = []; - $this->build_stack = []; - $this->with = []; - - if ($this->shouldLog()) { - $this->log('Container flushed'); - } - } - - /** - * 获取一个绑定的实例 - * - * @template T - * @param class-string $abstract 类或接口名 - * @param array $parameters 参数 - * @throws EntryResolutionException - * @return Closure|mixed|T 实例 - */ - public function make(string $abstract, array $parameters = []) - { - $abstract = $this->getAlias($abstract); - - $needs_contextual_build = !empty($parameters); - - if (isset($this->shared[$abstract])) { - if ($this->shouldLog()) { - $this->log(sprintf( - '[%s] resolved (shared)%s', - $abstract, - ($needs_contextual_build ? ' with ' . implode(', ', $parameters) : '') - )); - } - return $this->shared[$abstract]; - } - - // 如果已经存在在实例池中(通常意味着单例绑定),则直接返回该实例 - if (isset(self::$instances[$abstract]) && !$needs_contextual_build) { - if ($this->shouldLog()) { - $this->log("[{$abstract}] resolved (instance)"); - } - return self::$instances[$abstract]; - } - - $this->with[] = $parameters; - - $concrete = $this->getConcrete($abstract); - - // 构造该类的实例,并递归解析所有依赖 - if ($this->isBuildable($concrete, $abstract)) { - $object = $this->build($concrete); - } else { - $object = $this->make($concrete); - } - - // 如果该类存在扩展器(装饰器),则逐个应用到实例 - foreach ($this->getExtenders($abstract) as $extender) { - $object = $extender($object, $this); - } - - // 如果该类被注册为单例,则需要将其存放在实例池中,方便后续取用同一实例 - if (!$needs_contextual_build && $this->isShared($abstract)) { - $this->shared[$abstract] = $object; - if ($this->shouldLog()) { - $this->log("[{$abstract}] added to shared pool"); - } - } - - // 弹出本次构造的覆盖参数 - array_pop($this->with); - - if ($this->shouldLog()) { - $this->log(sprintf( - '[%s] resolved%s', - $abstract, - ($needs_contextual_build ? ' with ' . implode(', ', $parameters) : '') - )); - } - - return $object; - } - - /** - * 实例化具体的类实例 - * - * @param Closure|string $concrete 类名或对应的闭包 - * @throws EntryResolutionException - * @return mixed - */ - public function build($concrete) - { - // 如果传入的是闭包,则直接执行并返回 - if ($concrete instanceof Closure) { - return $concrete($this, $this->getLastParameterOverride()); - } - - try { - $reflection = new ReflectionClass($concrete); - } catch (ReflectionException $e) { - throw new EntryResolutionException("指定的类 {$concrete} 不存在", 0, $e); - } - - if (!$reflection->isInstantiable()) { - $this->notInstantiable($concrete); - } - - $this->build_stack[] = $concrete; - - $constructor = $reflection->getConstructor(); - - // 如果不存在构造函数,则代表不需要进一步解析,直接实例化即可 - if (is_null($constructor)) { - array_pop($this->build_stack); - return new $concrete(); - } - - $dependencies = $constructor->getParameters(); - - // 获取所有依赖的实例 - try { - $instances = $this->resolveDependencies($dependencies); - } catch (EntryResolutionException $e) { - array_pop($this->build_stack); - throw $e; - } - - array_pop($this->build_stack); - - return $reflection->newInstanceArgs($instances); - } - - /** - * 调用对应的方法,并自动注入依赖 - * - * @param callable|string $callback 对应的方法 - * @param array $parameters 参数 - * @param null|string $default_method 默认方法 - * @return mixed - */ - public function call($callback, array $parameters = [], string $default_method = null) - { - if ($this->shouldLog()) { - if (count($parameters)) { - $str_parameters = array_map([ReflectionUtil::class, 'variableToString'], $parameters); - $str_parameters = implode(', ', $str_parameters); - } else { - $str_parameters = ''; - } - $this->log(sprintf( - 'Called %s%s(%s)', - ReflectionUtil::variableToString($callback), - ($default_method ? '@' . $default_method : ''), - $str_parameters - )); - } - return BoundMethod::call($this, $callback, $parameters, $default_method); - } - - /** - * Finds an entry of the container by its identifier and returns it. - * - * @param string $id identifier of the entry to look for - * - * @throws NotFoundExceptionInterface no entry was found for **this** identifier - * @throws ContainerExceptionInterface error while retrieving the entry - * - * @return mixed entry - */ - public function get(string $id) - { - try { - return $this->make($id); - } catch (Exception $e) { - if ($this->has($id)) { - throw new EntryResolutionException('', 0, $e); - } - - throw new EntryNotFoundException('', 0, $e); - } - } - - /** - * Returns true if the container can return an entry for the given identifier. - * Returns false otherwise. - * - * `has($id)` returning true does not mean that `get($id)` will not throw an exception. - * It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`. - * - * @param string $id identifier of the entry to look for - */ - public function has(string $id): bool - { - return $this->bound($id); - } - - /** - * 扩展一个类或接口 - * - * @param string $abstract 类或接口名 - * @param Closure $closure 扩展闭包 - */ - public function extend(string $abstract, Closure $closure): void - { - $abstract = $this->getAlias($abstract); - - // 如果该类已经被解析过,则直接将扩展器应用到该类的实例上 - // 否则,将扩展器存入扩展器池,等待解析 - if (isset(self::$instances[$abstract])) { - self::$instances[$abstract] = $closure(self::$instances[$abstract], $this); - } else { - self::$extenders[$abstract][] = $closure; - } - - if ($this->shouldLog()) { - $this->log("[{$abstract}] extended"); - } - } - - /** - * 获取日志前缀 - */ - public function getLogPrefix(): string - { - return ($this->log_prefix ?: '[WorkerContainer(U)]') . ' '; - } - - /** - * 设置日志前缀 - */ - public function setLogPrefix(string $prefix): void - { - $this->log_prefix = $prefix; - } - - /** - * 获取对应类型的所有扩展器 - * - * @param string $abstract 类或接口名 - * @return Closure[] - */ - protected function getExtenders(string $abstract): array - { - $abstract = $this->getAlias($abstract); - - return self::$extenders[$abstract] ?? []; - } - - /** - * 判断传入的是否为别名 - */ - protected function isAlias(string $name): bool - { - return array_key_exists($name, self::$aliases); - } - - /** - * 抛弃所有过时的实例和别名 - * - * @param string $abstract 类或接口名 - */ - protected function dropStaleInstances(string $abstract): void - { - unset( - self::$instances[$abstract], - self::$aliases[$abstract], - $this->shared[$abstract] - ); - } - - /** - * 获取一个解析对应类的闭包 - * - * @param string $abstract 类或接口名 - * @param string $concrete 实际类名 - */ - protected function getClosure(string $abstract, string $concrete): Closure - { - return static function ($container, $parameters = []) use ($abstract, $concrete) { - $method = $abstract === $concrete ? 'build' : 'make'; - - return $container->{$method}($concrete, $parameters); - }; - } - - /** - * 获取最后一次的覆盖参数 - */ - protected function getLastParameterOverride(): array - { - return $this->with[count($this->with) - 1] ?? []; - } - - /** - * 抛出实例化异常 - * - * @throws EntryResolutionException - */ - protected function notInstantiable(string $concrete, string $reason = ''): void - { - if (!empty($this->build_stack)) { - $previous = implode(', ', $this->build_stack); - $message = "类 {$concrete} 无法实例化,其被 {$previous} 依赖"; - } else { - $message = "类 {$concrete} 无法实例化"; - } - - throw new EntryResolutionException("{$message}:{$reason}"); - } - - /** - * 解析依赖 - * - * @param ReflectionParameter[] $dependencies - * @throws EntryResolutionException - */ - protected function resolveDependencies(array $dependencies): array - { - $results = []; - - foreach ($dependencies as $dependency) { - // 如果此依赖存在覆盖参数,则使用覆盖参数 - // 否则,将尝试解析参数 - if ($this->hasParameterOverride($dependency)) { - $results[] = $this->getParameterOverride($dependency); - continue; - } - - // 如果存在临时注入的依赖,则使用临时注入的依赖 - if ($this->hasParameterTypeOverride($dependency)) { - $results[] = $this->getParameterTypeOverride($dependency); - continue; - } - - // 如果类名为空,则代表此依赖是基本类型,且无法对其进行依赖解析 - $class_name = ReflectionUtil::getParameterClassName($dependency); - $results[] = is_null($class_name) - ? $this->resolvePrimitive($dependency) - : $this->resolveClass($dependency); - - if ($this->shouldLog()) { - if (is_null($class_name)) { - if ($dependency->hasType()) { - $class_name = $dependency->getType(); - } else { - $class_name = 'Primitive'; - } - } - $this->log("Dependency [{$class_name} {$dependency->name}] resolved"); - } - } - - return $results; - } - - /** - * 判断传入的参数是否存在覆盖参数 - */ - protected function hasParameterOverride(ReflectionParameter $parameter): bool - { - return array_key_exists($parameter->name, $this->getLastParameterOverride()); - } - - /** - * 获取覆盖参数 - * - * @return mixed - */ - protected function getParameterOverride(ReflectionParameter $parameter) - { - return $this->getLastParameterOverride()[$parameter->name]; - } - - /** - * 判断传入的参数是否存在临时注入的参数 - */ - protected function hasParameterTypeOverride(ReflectionParameter $parameter): bool - { - if (!$parameter->hasType()) { - return false; - } - - $type = $parameter->getType(); - - if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) { - return false; - } - - return array_key_exists($type->getName(), $this->getLastParameterOverride()); - } - - /** - * 获取临时注入的参数 - * - * @return mixed - */ - protected function getParameterTypeOverride(ReflectionParameter $parameter) - { - $type = $parameter->getType(); - - if (!$type instanceof ReflectionNamedType) { - return []; - } - - return $this->getLastParameterOverride()[$type->getName()]; - } - - /** - * 解析基本类型 - * - * @throws EntryResolutionException 如参数不存在默认值,则抛出异常 - * @return mixed 对应类型的默认值 - */ - protected function resolvePrimitive(ReflectionParameter $parameter) - { - if ($parameter->isDefaultValueAvailable()) { - return $parameter->getDefaultValue(); - } - - throw new EntryResolutionException("无法解析类 {$parameter->getDeclaringClass()->getName()} 的参数 {$parameter}"); - } - - /** - * 解析类 - * - * @throws EntryResolutionException 如果无法解析类,则抛出异常 - * @return mixed - */ - protected function resolveClass(ReflectionParameter $parameter) - { - try { - // 尝试解析 - return $this->make(ReflectionUtil::getParameterClassName($parameter)); - } catch (EntryResolutionException $e) { - // 如果参数是可选的,则返回默认值 - if ($parameter->isDefaultValueAvailable()) { - array_pop($this->with); - return $parameter->getDefaultValue(); - } - - if ($parameter->isVariadic()) { - array_pop($this->with); - return []; - } - - throw $e; - } - } - - /** - * 获取类名的实际类型 - * - * @param string $abstract 类或接口名 - * @return Closure|string - */ - protected function getConcrete(string $abstract) - { - if (isset(self::$bindings[$abstract])) { - return self::$bindings[$abstract]['concrete']; - } - - return $abstract; - } - - /** - * 判断传入的实际类型是否可以构造 - * - * @param mixed $concrete 实际类型 - * @param string $abstract 类或接口名 - */ - protected function isBuildable($concrete, string $abstract): bool - { - return $concrete === $abstract || $concrete instanceof Closure; - } - - /** - * 判断传入的类型是否为共享实例 - * - * @param string $abstract 类或接口名 - */ - protected function isShared(string $abstract): bool - { - return isset($this->instances[$abstract]) - || (isset($this->bindings[$abstract]['shared']) - && $this->bindings[$abstract]['shared'] === true); - } - - /** - * 判断是否输出日志 - */ - protected function shouldLog(): bool - { - return Console::getLevel() >= 4; - } - - /** - * 记录日志(自动附加容器日志前缀) - */ - protected function log(string $message): void - { - logger()->debug($this->getLogPrefix() . $message); - } -} diff --git a/src/ZM/Container/EntryNotFoundException.php b/src/ZM/Container/EntryNotFoundException.php deleted file mode 100644 index 03332d17..00000000 --- a/src/ZM/Container/EntryNotFoundException.php +++ /dev/null @@ -1,12 +0,0 @@ -cid = $cid; - } - - /** - * @return Server - */ - public function getServer(): ?Server - { - return self::$context[$this->cid]['server'] ?? server(); - } - - public function getFrame(): ?Frame - { - return self::$context[$this->cid]['frame'] ?? null; - } - - public function getFd(): ?int - { - return self::$context[$this->cid]['fd'] ?? $this->getFrame()->fd ?? null; - } - - /** - * @return mixed - */ - public function getData() - { - return self::$context[$this->cid]['data'] ?? null; - } - - public function setData($data) - { - self::$context[$this->cid]['data'] = $data; - } - - public function getRequest(): ?Request - { - return self::$context[$this->cid]['request'] ?? null; - } - - public function getResponse(): ?Response - { - return self::$context[$this->cid]['response'] ?? null; - } - - public function getConnection(): ?ConnectionObject - { - return ManagerGM::get($this->getFd()); - } - - public function getCid(): ?int - { - return $this->cid; - } - - public function getRobot(): ?ZMRobot - { - $conn = ManagerGM::get($this->getFrame()->fd); - return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null; - } - - public function getMessage() - { - if ((ZMConfig::get('global', 'onebot')['message_convert_string'] ?? true) === true && is_array($msg = $this->getOriginMessage())) { - return MessageUtil::arrayToStr($msg); - } - return self::$context[$this->cid]['data']['message'] ?? null; - } - - public function setMessage($msg) - { - if (is_string($msg) && is_array($this->getOriginMessage())) { - $msg = MessageUtil::strToArray($msg); - } - self::$context[$this->cid]['data']['message'] = $msg; - } - - public function getUserId() - { - return $this->getData()['user_id'] ?? null; - } - - 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) - { - self::$context[$this->cid]['data']['group_id'] = $id; - } - - public function getDiscussId() - { - return $this->getData()['discuss_id'] ?? null; - } - - public function setDiscussId($id) - { - self::$context[$this->cid]['data']['discuss_id'] = $id; - } - - public function getMessageType(): ?string - { - return $this->getData()['message_type'] ?? null; - } - - 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 self::$context[$this->cid]['cache'][$key] ?? null; - } - - public function setCache($key, $value) - { - self::$context[$this->cid]['cache'][$key] = $value; - } - - public function getCQResponse() - { - return self::$context[$this->cid]['cq_response'] ?? null; - } - - /** - * only can used by cq->message event function - * @param array|string $msg 要回复的消息 - * @param bool|callable|Closure $yield 是否协程挂起(true),是否绑定异步事件(Closure) - * @return array|bool 返回API调用结果 - */ - public function reply($msg, $yield = false) - { - $data = $this->getData(); - $conn = $this->getConnection(); - if (!is_array($msg)) { - switch ($this->getData()['message_type']) { - case 'group': - case 'private': - case 'discuss': - $this->setCache('has_reply', true); - $operation['reply'] = $msg; - $operation['at_sender'] = false; - return (new ZMRobot($conn))->setCallback($yield)->callExtendedAPI('.handle_quick_operation', [ - 'context' => $data, - 'operation' => $operation, - ]); - } - return false; - } - $operation = $msg; - return (new ZMRobot($conn))->setCallback(false)->callExtendedAPI('.handle_quick_operation', [ - 'context' => $data, - 'operation' => $operation, - ]); - } - - /** - * @param array|string $msg 要回复的消息 - * @param bool $yield 是否协程挂起(true),是否绑定异步事件(Closure) - * @throws InterruptException 阻止消息被后续插件处理 - */ - public function finalReply($msg, $yield = false) - { - self::$context[$this->cid]['cache']['block_continue'] = true; - if ($msg != '') { - $this->reply($msg, $yield); - } - EventDispatcher::interrupt(); - } - - /** - * @param string $prompt - * @param int $timeout - * @param string $timeout_prompt - * @throws WaitTimeoutException - * @throws InvalidArgumentException - * @return string 返回用户输入的内容 - */ - public function waitMessage($prompt = '', $timeout = 600, $timeout_prompt = '') - { - if (!isset($this->getData()['user_id'], $this->getData()['message'], $this->getData()['self_id'])) { - throw new InvalidArgumentException('协程等待参数缺失'); - } - - logger()->debug('==== 开始等待输入 ===='); - if ($prompt != '') { - $this->reply($prompt); - } - - try { - $r = CoMessage::yieldByWS($this->getData(), ['user_id', 'self_id', 'message_type', get_onebot_target_id_name($this->getMessageType())], $timeout); - } catch (Exception $e) { - $r = false; - } - if ($r === false) { - throw new WaitTimeoutException($this, $timeout_prompt); - } - if (is_array($r['message']) && (ZMConfig::get('global', 'onebot')['message_convert_string'] ?? true) === true) { - return MessageUtil::arrayToStr($r['message']); - } - return $r['message']; - } - - /** - * 根据选定的模式获取消息参数 - * @param int|string $mode 获取的模式 - * @param string|Stringable $prompt_msg 提示语回复 - * @throws InvalidArgumentException - * @throws ZMKnownException - * @throws WaitTimeoutException - * @return float|int|string - */ - public function getArgs($mode, $prompt_msg) - { - $arg = ctx()->getCache('match') ?? []; - switch ($mode) { - case ZM_MATCH_ALL: - $p = $arg; - return trim(implode(' ', $p)) == '' ? $this->waitMessage($prompt_msg) : trim(implode(' ', $p)); - case ZM_MATCH_NUMBER: - foreach ($arg as $k => $v) { - if (is_numeric($v)) { - array_splice($arg, $k, 1); - ctx()->setCache('match', $arg); - return $v; - } - } - return $this->waitMessage($prompt_msg); - case ZM_MATCH_FIRST: - if (isset($arg[0])) { - $a = $arg[0]; - array_splice($arg, 0, 1); - ctx()->setCache('match', $arg); - return $a; - } - return $this->waitMessage($prompt_msg); - } - throw new InvalidArgumentException(); - } - - /** - * 获取下一个参数 - * @param string $prompt_msg 提示语回复 - * @throws InvalidArgumentException - * @throws ZMKnownException - * @throws WaitTimeoutException - * @return int|mixed|string 返回获取的参数 - */ - public function getNextArg($prompt_msg = '') - { - return $this->getArgs(ZM_MATCH_FIRST, $prompt_msg); - } - - /** - * 获取接下来所有的消息当成一个完整的参数(包含空格) - * @param string $prompt_msg 提示语回复 - * @throws InvalidArgumentException - * @throws ZMKnownException - * @throws WaitTimeoutException - * @return int|mixed|string 返回获取的参数 - */ - public function getFullArg($prompt_msg = '') - { - return $this->getArgs(ZM_MATCH_ALL, $prompt_msg); - } - - /** - * 获取下一个数字类型的参数 - * @param string $prompt_msg 提示语回复 - * @throws InvalidArgumentException - * @throws ZMKnownException - * @throws WaitTimeoutException - * @return int|mixed|string 返回获取的参数 - */ - public function getNumArg($prompt_msg = '') - { - return $this->getArgs(ZM_MATCH_NUMBER, $prompt_msg); - } - - /** - * @throws ZMKnownException - * @return ContextInterface 返回上下文 - */ - public function cloneFromParent(): ContextInterface - { - set_coroutine_params(self::$context[Coroutine::getPcid()] ?? self::$context[$this->cid]); - return context(); - } - - public function copy() - { - return self::$context[$this->cid]; - } - - public function getOption() - { - return self::getCache('match'); - } - - public function getOriginMessage() - { - return self::$context[$this->cid]['data']['message'] ?? null; - } - - public function getArrayMessage(): array - { - $msg = $this->getOriginMessage(); - if (is_array($msg)) { - return $msg; - } - return MessageUtil::strToArray($msg); - } - - public function getStringMessage(): string - { - $msg = $this->getOriginMessage(); - if (is_string($msg)) { - return $msg; - } - return MessageUtil::arrayToStr($msg); - } -} diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php deleted file mode 100644 index 068be3d9..00000000 --- a/src/ZM/Context/ContextInterface.php +++ /dev/null @@ -1,100 +0,0 @@ -fetchAllAssociative('select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=?;', [$db_name]); - foreach ($result as $v) { - self::$table_list[] = $v['TABLE_NAME']; - } - } - - /** - * @param string $table_name 表名 - * @throws DbException - * @return Table 返回表对象 - */ - public static function table(string $table_name): Table - { - if (Table::getTableInstance($table_name) === null) { - if (in_array($table_name, self::$table_list)) { - return new Table($table_name); - } - if (SqlPoolStorage::$sql_pool !== null) { - throw new DbException('Table ' . $table_name . ' not exist in database.'); - } - throw new DbException('Database connection not exist or connect failed. Please check sql configuration'); - } - return Table::getTableInstance($table_name); - } - - /** - * @param string $line SQL语句 - * @throws DbException - */ - public static function statement(string $line) - { - self::rawQuery($line, []); - } - - /** - * @param string $line SQL语句 - * @return bool 返回查询是否成功的结果 - */ - public static function unprepared(string $line): bool - { - $conn = SqlPoolStorage::$sql_pool->getConnection(); - $result = !($conn->query($line) === false); - SqlPoolStorage::$sql_pool->putConnection($conn); - return $result; - } - - /** - * @param string $line SQL语句 - * @param array $params 查询参数 - * @param int $fetch_mode fetch规则 - * @throws DbException - * @return array|false 返回结果集或false - */ - public static function rawQuery(string $line, array $params = [], int $fetch_mode = ZM_DEFAULT_FETCH_MODE) - { - if (!is_array($params)) { - $params = [$params]; - } - logger()->debug('MySQL: ' . $line . ' | ' . implode(', ', $params)); - try { - if (SqlPoolStorage::$sql_pool === null) { - throw new DbException('未连接到任何数据库!'); - } - $conn = SqlPoolStorage::$sql_pool->getConnection(); - $ps = $conn->prepare($line); - if ($ps === false) { - SqlPoolStorage::$sql_pool->putConnection(null); - throw new DbException('SQL语句查询错误,' . $line . ',错误信息:' . $conn->errorInfo()[2]); - } - if ($params == []) { - $result = $ps->execute(); - } elseif (!is_array($params)) { - $result = $ps->execute([$params]); - } else { - $result = $ps->execute($params); - } - if ($result !== true) { - SqlPoolStorage::$sql_pool->putConnection(null); - throw new DBException("语句[{$line}]错误!" . $ps->errorInfo()[2]); - // echo json_encode(debug_backtrace(), 128 | 256); - } - SqlPoolStorage::$sql_pool->putConnection($conn); - return $ps->fetchAll($fetch_mode); - } catch (DbException $e) { - if (mb_strpos($e->getMessage(), 'has gone away') !== false) { - zm_sleep(); - logger()->warning('Gone away of MySQL! retrying!'); - return self::rawQuery($line, $params); - } - logger()->warning($e->getMessage()); - throw $e; - } catch (PDOException $e) { - if (mb_strpos($e->getMessage(), 'has gone away') !== false) { - zm_sleep(); - logger()->warning('Gone away of MySQL! retrying!'); - return self::rawQuery($line, $params); - } - logger()->warning($e->getMessage()); - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - public static function isTableExists($table): bool - { - return in_array($table, self::$table_list); - } -} diff --git a/src/ZM/DB/DeleteBody.php b/src/ZM/DB/DeleteBody.php deleted file mode 100644 index 64a963bc..00000000 --- a/src/ZM/DB/DeleteBody.php +++ /dev/null @@ -1,39 +0,0 @@ -table = $table; - } - - /** - * @throws DbException - * @return mixed - */ - public function save() - { - [$sql, $param] = $this->getWhereSQL(); - return DB::rawQuery('DELETE FROM ' . $this->table->getTableName() . ' WHERE ' . $sql, $param); - } -} diff --git a/src/ZM/DB/InsertBody.php b/src/ZM/DB/InsertBody.php deleted file mode 100644 index 57b9712f..00000000 --- a/src/ZM/DB/InsertBody.php +++ /dev/null @@ -1,40 +0,0 @@ -table = $table; - $this->row = $row; - } - - /** - * @throws DbException - */ - public function save() - { - DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES (' . implode(',', array_fill(0, count($this->row), '?')) . ')', $this->row); - } -} diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php deleted file mode 100644 index 0427b016..00000000 --- a/src/ZM/DB/SelectBody.php +++ /dev/null @@ -1,138 +0,0 @@ -table = $table; - $this->select_thing = $select_thing; - } - - /** - * @throws DbException - */ - public function get() - { - return $this->fetchAll(); - } - - /** - * @throws DbException - */ - public function count(): int - { - $this->select_thing = ['count(*)']; - $str = $this->queryPrepare(); - $this->result = DB::rawQuery($str[0], $str[1]); - return intval($this->result[0]['count(*)']); - } - - /** - * @param mixed $fetch_mode - * @throws DbException - */ - public function fetchAll($fetch_mode = ZM_DEFAULT_FETCH_MODE) - { - $this->execute($fetch_mode); - return $this->getResult(); - } - - /** - * @throws DbException - * @return null|mixed - */ - public function fetchFirst() - { - return $this->fetchAll()[0] ?? null; - } - - /** - * @param null|mixed $key - * @throws DbException - * @return null|mixed - */ - public function value($key = null) - { - $r = $this->fetchFirst(); - if ($r === null) { - return null; - } - if ($key === null) { - return current($r); - } - return $r[$key] ?? null; - } - - /** - * @throws DbException - */ - public function execute(int $fetch_mode = ZM_DEFAULT_FETCH_MODE) - { - $str = $this->queryPrepare(); - $this->result = DB::rawQuery($str[0], $str[1], $fetch_mode); - } - - public function getResult() - { - return $this->result; - } - - public function equals(SelectBody $body): bool - { - if ($this->select_thing != $body->getSelectThing()) { - return false; - } - if ($this->where_thing == $body->getWhereThing()) { - return false; - } - return true; - } - - /** - * @return mixed - */ - public function getSelectThing() - { - return $this->select_thing; - } - - public function getWhereThing(): array - { - return $this->where_thing; - } - - private function queryPrepare(): array - { - $msg = 'SELECT ' . implode(', ', $this->select_thing) . ' FROM ' . $this->table->getTableName(); - $sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '='); - if ($sql[0] != '') { - $msg .= ' WHERE ' . $sql[0]; - $array = $sql[1]; - $sql = $this->table->paintWhereSQL($this->where_thing['!='] ?? [], '!='); - if ($sql[0] != '') { - $msg .= ' AND ' . $sql[0]; - } - $array = array_merge($array, $sql[1]); - } - return [$msg, $array ?? []]; - } -} diff --git a/src/ZM/DB/Table.php b/src/ZM/DB/Table.php deleted file mode 100644 index 519d6b5f..00000000 --- a/src/ZM/DB/Table.php +++ /dev/null @@ -1,98 +0,0 @@ -table_name = $table_name; - self::$table_instance[$table_name] = $this; - } - - public static function getTableInstance($table_name) - { - if (isset(self::$table_instance[$table_name])) { - return self::$table_instance[$table_name]; - } - return null; - } - - public function select($what = []) - { - return new SelectBody($this, $what == [] ? ['*'] : $what); - } - - public function where($column, $operation_or_value, $value = null) - { - return (new SelectBody($this, ['*']))->where($column, $operation_or_value, $value); - } - - public function insert($row) - { - $this->cache = []; - return new InsertBody($this, $row); - } - - public function update(array $set_value) - { - $this->cache = []; - return new UpdateBody($this, $set_value); - } - - public function delete() - { - $this->cache = []; - return new DeleteBody($this); - } - - public function statement() - { - $this->cache = []; - // TODO: 无返回的statement语句 - } - - public function paintWhereSQL($rule, $operator) - { - if ($rule == []) { - return ['', []]; - } - $msg = ''; - $param = []; - foreach ($rule as $k => $v) { - if ($msg == '') { - $msg .= $k . " {$operator} ? "; - } else { - $msg .= ' AND ' . $k . " {$operator} ?"; - } - $param[] = $v; - } - return [$msg, $param]; - } - - /** - * @return mixed - */ - public function getTableName() - { - return $this->table_name; - } -} diff --git a/src/ZM/DB/UpdateBody.php b/src/ZM/DB/UpdateBody.php deleted file mode 100644 index a25c5ef7..00000000 --- a/src/ZM/DB/UpdateBody.php +++ /dev/null @@ -1,58 +0,0 @@ -table = $table; - $this->set_value = $set_value; - } - - /** - * @throws DbException - */ - public function save() - { - $arr = []; - $msg = []; - foreach ($this->set_value as $k => $v) { - $msg[] = $k . ' = ?'; - $arr[] = $v; - } - if ($msg == []) { - throw new DbException('update value sets can not be empty!'); - } - $line = 'UPDATE ' . $this->table->getTableName() . ' SET ' . implode(', ', $msg); - if ($this->where_thing != []) { - [$sql, $param] = $this->getWhereSQL(); - $arr = array_merge($arr, $param); - $line .= ' WHERE ' . $sql; - } - return DB::rawQuery($line, $arr); - } -} diff --git a/src/ZM/DB/WhereBody.php b/src/ZM/DB/WhereBody.php deleted file mode 100644 index 83f2a500..00000000 --- a/src/ZM/DB/WhereBody.php +++ /dev/null @@ -1,48 +0,0 @@ -where_thing[$operation_or_value][$column] = $value; - } elseif (!in_array($operation_or_value, ['=', '!=', '>', '<', '>=', '<=', 'IN', 'in'])) { - $this->where_thing['='][$column] = $operation_or_value; - } else { - $this->where_thing['='][$column] = $operation_or_value; - } - return $this; - } - - protected function getWhereSQL() - { - $param = []; - $msg = ''; - foreach ($this->where_thing as $k => $v) { - foreach ($v as $ks => $vs) { - if ($param != []) { - $msg .= ' AND ' . $ks . " {$k} ?"; - } else { - $msg .= "{$ks} {$k} ?"; - } - $param[] = $vs; - } - } - if ($msg == '') { - $msg = 1; - } - return [$msg, $param]; - } -} diff --git a/src/ZM/Entity/CQObject.php b/src/ZM/Entity/CQObject.php deleted file mode 100644 index 0d0987ff..00000000 --- a/src/ZM/Entity/CQObject.php +++ /dev/null @@ -1,31 +0,0 @@ -type = $type; - $this->params = $params; - $this->start = $start; - $this->end = $end; - } - } - - public static function fromArray($arr): CQObject - { - return new CQObject($arr['type'], $arr['params'] ?? [], $arr['start'], $arr['end']); - } -} diff --git a/src/ZM/Entity/InputArguments.php b/src/ZM/Entity/InputArguments.php deleted file mode 100644 index cef8d148..00000000 --- a/src/ZM/Entity/InputArguments.php +++ /dev/null @@ -1,30 +0,0 @@ -arguments = $arguments; - } - - public function getArguments(): array - { - return $this->arguments; - } - - public function getArgument($name) - { - return $this->arguments[$name] ?? null; - } - - public function get($name) - { - return $this->getArgument($name); - } -} diff --git a/src/ZM/Entity/MatchResult.php b/src/ZM/Entity/MatchResult.php deleted file mode 100644 index ab770374..00000000 --- a/src/ZM/Entity/MatchResult.php +++ /dev/null @@ -1,19 +0,0 @@ -class = $class; - $this->eid = ZMAtomic::get('_event_id')->add(1); - $list = LightCacheInside::get('wait_api', 'event_trace'); - if (isset($list[$class])) { - $this->log = true; - } - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 开始分发事件: " . $class); - } - } - - /** - * @param mixed $return_var - * @throws InterruptException - */ - public static function interrupt($return_var = null) - { - throw new InterruptException($return_var); - } - - public static function enableEventTrace($event_class) - { - SpinLock::lock('_event_trace'); - $list = LightCacheInside::get('wait_api', 'event_trace'); - $list[$event_class] = true; - LightCacheInside::set('wait_api', 'event_trace', $list); - SpinLock::unlock('_event_trace'); - } - - public static function disableEventTrace($event_class) - { - SpinLock::lock('_event_trace'); - $list = LightCacheInside::get('wait_api', 'event_trace'); - unset($list[$event_class]); - LightCacheInside::set('wait_api', 'event_trace', $list); - SpinLock::unlock('_event_trace'); - } - - public function setRuleFunction(callable $rule = null): EventDispatcher - { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 设置事件rule: " . $this->class); - } - $this->rule = $rule; - return $this; - } - - public function setReturnFunction(callable $return_func): EventDispatcher - { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 设置事件returnFunc: " . $this->class); - } - $this->return_func = $return_func; - return $this; - } - - /** - * @param mixed ...$params - * @throws Throwable - */ - public function dispatchEvents(...$params) - { - try { - foreach ((EventManager::$events[$this->class] ?? []) as $v) { -// if ($v->class === QQBot::class && $v->method === 'handleByEvent') { -// zm_dump(EventManager::$events[$this->class]); -// $v->class = OneBot11Adapter::class; -// $v->method = 'handleIncomingRequest'; -// } - $this->dispatchEvent($v, $this->rule, ...$params); - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 单一对象 " . $v->class . '::' . (is_string($v->method) ? $v->method : '{closure}') . ' 分发结束。'); - } - if ($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) { - continue; - } - if (is_callable($this->return_func) && $this->status === self::STATUS_NORMAL) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 单一对象 " . $v->class . '::' . $v->method . ' 正在执行返回值处理函数 ...'); - } - ($this->return_func)($this->store); - } - } - if ($this->status === self::STATUS_RULE_FAILED) { - $this->status = self::STATUS_NORMAL; - } - // TODO:没有过滤before的false,可能会导致一些问题,先观望一下 - } catch (InterruptException $e) { - $this->store = $e->return_var; - $this->status = self::STATUS_INTERRUPTED; - } catch (Throwable $e) { - $this->status = self::STATUS_EXCEPTION; - throw $e; - } - } - - /** - * @param mixed $v - * @param null|mixed $rule_func - * @param mixed ...$params - * @throws InterruptException - * @throws AnnotationException - * @throws Error - * @return bool - * @noinspection PhpMissingReturnTypeInspection - */ - public function dispatchEvent($v, $rule_func = null, ...$params) - { - $q_c = $v->class; - $q_f = $v->method; - if (($q_c ?? '') === '' && ($q_f instanceof Closure)) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 闭包函数的事件触发过程!"); - } - if ($rule_func !== null && !$rule_func($v)) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 闭包函数下的 ruleFunc 判断为 false, 拒绝执行此方法。"); - } - $this->status = self::STATUS_RULE_FAILED; - return false; - } - $this->store = $q_f(...$params); - $this->status = self::STATUS_NORMAL; - return true; - } - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 正在判断 " . $q_c . '::' . $q_f . ' 方法下的 ruleFunc ...'); - } - if ($rule_func !== null && !$rule_func($v)) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] " . $q_c . '::' . $q_f . ' 方法下的 ruleFunc 判断为 false, 拒绝执行此方法。'); - } - $this->status = self::STATUS_RULE_FAILED; - return false; - } - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] " . $q_c . '::' . $q_f . ' 方法下的 ruleFunc 为真,继续执行方法本身 ...'); - } - if (isset(EventManager::$middleware_map[$q_c][$q_f])) { - $middlewares = EventManager::$middleware_map[$q_c][$q_f]; - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] " . $q_c . '::' . $q_f . ' 方法还绑定了 Middleware:' . implode(', ', array_map(function ($x) { - return $x->middleware; - }, $middlewares))); - } - $before_result = true; - $r = []; - foreach ($middlewares as $k => $middleware) { - if (!isset(EventManager::$middlewares[$middleware->middleware])) { - if ((ZMConfig::get('global', 'runtime')['middleware_error_policy'] ?? 1) == 1) { - throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware->middleware}\"!"); - } - continue; - } - $middleware_obj = EventManager::$middlewares[$middleware->middleware]; - $before = $middleware_obj['class']; - // var_dump($middleware_obj); - $r[$k] = new $before(); - $r[$k]->class = $q_c; - $r[$k]->method = $q_f; - $r[$k]->middleware = $middleware; - $r[$k]->current_event = $v; - if (isset($middleware_obj['before'])) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ..."); - } - $rs = $middleware_obj['before']; - $before_result = $r[$k]->{$rs}(...$params); - if ($before_result === false) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] Middleware 前置事件为 false,停止执行原事件,开始执行下一事件。"); - } - break; - } - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] Middleware 前置事件为 true,继续执行原事件。"); - } - } - } - if ($before_result) { - try { - $q_o = ZMUtil::getModInstance($q_c); - $q_o->_running_annotation = $v; - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 正在执行方法 " . $q_c . '::' . $q_f . ' ...'); - } - $this->store = container()->call([$q_o, $q_f], $params); - } catch (Exception $e) { - if ($e instanceof InterruptException) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 检测到事件阻断调用,正在跳出事件分发器 ..."); - } - throw $e; - } - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 方法 " . $q_c . '::' . $q_f . ' 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ...'); - } - for ($i = count($middlewares) - 1; $i >= 0; --$i) { - $middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware]; - if (!isset($middleware_obj['exceptions'])) { - continue; - } - foreach ($middleware_obj['exceptions'] as $name => $method) { - if ($e instanceof $name) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 方法 " . $q_c . '::' . $q_f . ' 的异常 ' . get_class($e) . ' 被 Middleware:' . $middlewares[$i] . ' 下的 ' . get_class($r[$i]) . '::' . $method . ' 捕获。'); - } - $r[$i]->{$method}($e); - self::interrupt(); - } - } - } - throw $e; - } - $cnts = count($middlewares) - 1; - for ($i = $cnts; $i >= 0; --$i) { - $middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware]; - if (isset($middleware_obj['after'], $r[$i])) { - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ..."); - } - $r[$i]->{$middleware_obj['after']}(...$params); - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] Middleware 后置事件执行完毕!"); - } - } - } - $this->status = self::STATUS_NORMAL; - return true; - } - $this->status = self::STATUS_BEFORE_FAILED; - return false; - } - $q_o = ZMUtil::getModInstance($q_c); - $q_o->_running_annotation = $v; - if ($this->log) { - logger()->debug("[事件分发{$this->eid}] 正在执行方法 " . $q_c . '::' . $q_f . ' ...'); - } - $this->store = container()->call([$q_o, $q_f], $params); - $this->status = self::STATUS_NORMAL; - return true; - } - - public function getEid(): int - { - return $this->eid; - } - - public function getClass(): string - { - return $this->class; - } + use Singleton; } diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php deleted file mode 100644 index c5b8a28e..00000000 --- a/src/ZM/Event/EventManager.php +++ /dev/null @@ -1,94 +0,0 @@ -method instanceof Closure) { - logger()->debug("Adding event {$event_name} at @Anonymous"); - } else { - logger()->debug("Adding event {$event_name} at " . ($event_obj->class) . ':' . ($event_obj->method)); - self::$event_map[$event_obj->class][$event_obj->method][] = $event_obj; - } - self::$events[$event_name][] = $event_obj; - (new AnnotationParser())->sortByLevel(self::$events, $event_name); - } - - /** - * @throws AnnotationException - */ - public static function loadEventByParser(AnnotationParser $parser) - { - self::$events = array_merge(self::$events, $parser->generateAnnotationEvents()); - self::$middlewares = $parser->getMiddlewares(); - self::$middleware_map = $parser->getMiddlewareMap(); - self::$req_mapping = $parser->getReqMapping(); - $parser->verifyMiddlewares(); - } - - /** - * 注册所有计时器给每个进程 - * @throws Exception - */ - public static function registerTimerTick() - { - $dispatcher = new EventDispatcher(OnTick::class); - foreach (self::$events[OnTick::class] ?? [] as $vss) { - if (server()->worker_id !== $vss->worker_id && $vss->worker_id != -1) { - return; - } - // echo server()->worker_id.PHP_EOL; - $plain_class = $vss->class; - logger()->debug('Added Middleware-based timer: ' . $plain_class . ' -> ' . $vss->method); - Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) { - set_coroutine_params([]); - if (ZMAtomic::get('stop_signal')->get() != 0) { - Timer::clearAll(); - return; - } - try { - $dispatcher->dispatchEvent($vss, null); - } catch (Exception $e) { - Console::error(zm_internal_errcode('E00034') . 'Uncaught error from TimerTick: ' . $e->getMessage() . ' at ' . $e->getFile() . "({$e->getLine()})"); - } catch (Error $e) { - Console::error(zm_internal_errcode('E00034') . 'Uncaught fatal error from TimerTick: ' . $e->getMessage()); - echo Console::setColor($e->getTraceAsString(), 'gray'); - Console::error('Please check your code!'); - } - }); - } - $conf = ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if (server()->worker_id == $conf['worker']) { - zm_timer_tick(ZMConfig::get('global', 'light_cache')['auto_save_interval'] * 1000, static function () { - LightCache::savePersistence(); - }); - } - } -} diff --git a/src/ZM/Event/EventMapIterator.php b/src/ZM/Event/EventMapIterator.php deleted file mode 100644 index 8fa86893..00000000 --- a/src/ZM/Event/EventMapIterator.php +++ /dev/null @@ -1,78 +0,0 @@ -class = $class; - $this->method = $method; - $this->event_name = $event_name; - } - - #[ReturnTypeWillChange] - public function current() - { - logger()->debug('从 [' . $this->offset . '] 开始获取'); - return EventManager::$event_map[$this->class][$this->method][$this->offset]; - } - - public function next(): void - { - logger()->debug('下一个offset为 [' . ++$this->offset . ']'); - $this->nextToValid(); - } - - #[ReturnTypeWillChange] - public function key() - { - logger()->debug('返回key:' . $this->offset); - return isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? $this->offset : null; - } - - public function valid($s = false): bool - { - logger()->debug( - "[{$this->offset}] " . - ($s ? 'valid' : '') . '存在:' . - (!isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? Console::setColor('false', 'red') : ('true' . - (is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true) ? ',是目标对象' : ',不是目标对象'))) - ); - return - isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) - && is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true); - } - - public function rewind(): void - { - logger()->debug('回到0'); - $this->offset = 0; - $this->nextToValid(); - } - - private function nextToValid() - { - while ( - isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) - && !is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true) - ) { - ++$this->offset; - } - logger()->debug('内部偏移offset为 [' . $this->offset . ']'); - } -} diff --git a/src/ZM/Event/EventProvider.php b/src/ZM/Event/EventProvider.php new file mode 100644 index 00000000..ca62b256 --- /dev/null +++ b/src/ZM/Event/EventProvider.php @@ -0,0 +1,84 @@ +>> 已注册的事件监听器 + */ + private static $_events = []; + + /** @var array @phpstan-ignore-next-line */ + private static $_event_map = []; + + /** + * 添加事件监听器 + * + * @param object|string $event 事件名称 + * @param callable $callback 事件回调 + * @param int $level 事件等级 + */ + public function addEventListener($event, callable $callback, int $level = 20) + { + if (is_object($event)) { // 传入对象时必须带 class 和 method 属性,这时将忽略 callback 参数 + if (property_exists($event, 'class') && property_exists($event, 'method')) { + self::$_events[get_class($event)][] = [$level, [resolve($event->class), $event->method]]; + self::$_event_map[$event->class][$event->method][] = $event; + } elseif (is_array($callback) && is_object($callback[0] ?? '') && is_string($callback[1] ?? null)) { + // 如果没有上面两个属性,则可能是回调函数是一个数组,如果是这样,则可以直接使用回调函数 + self::$_event_map[get_class($callback[0])][$callback[1]][] = $event; + $event->class = get_class($callback[0]); + $event->method = $callback[1]; + } + $this->sortEvents(get_class($event)); + } elseif (is_string($event)) { + self::$_events[$event][] = [$level, $callback]; + $this->sortEvents($event); + } else { + logger()->error('传入了错误的对象'); + } + } + + /** + * 获取事件监听器 + * + * @param string $event_name 事件名称 + * @return array + */ + public function getEventListeners(string $event_name): array + { + return self::$_events[$event_name] ?? []; + } + + /** + * 获取事件监听器 + * + * @param object $event 事件对象 + * @return iterable + */ + public function getListenersForEvent(object $event): iterable + { + return self::getEventListeners(method_exists($event, 'getName') ? $event->getName() : get_class($event)); + } + + /** + * 排序事件 + * + * @param string|Stringable $name + */ + private function sortEvents($name) + { + usort(self::$_events[$name], function ($a, $b) { + return $a[0] <= $b[0] ? 1 : -1; + }); + } +} diff --git a/src/ZM/Event/EventTracer.php b/src/ZM/Event/EventTracer.php deleted file mode 100644 index 7c277c9d..00000000 --- a/src/ZM/Event/EventTracer.php +++ /dev/null @@ -1,53 +0,0 @@ -class)) { - return null; - } - if (empty($current_event->method)) { - return null; - } - return EventManager::$middleware_map[$current_event->class][$current_event->method] ?? []; - } - - public static function getEventTraceList() - { - $result = []; - $list = debug_backtrace(); - foreach ($list as $v) { - if ((($v['object'] ?? null) instanceof EventDispatcher) && $v['function'] == 'dispatchEvent') { - $result[] = $v['args'][0]; - } - } - return $result; - } -} diff --git a/src/ZM/Event/Listener/HttpEventListener.php b/src/ZM/Event/Listener/HttpEventListener.php new file mode 100644 index 00000000..c684547f --- /dev/null +++ b/src/ZM/Event/Listener/HttpEventListener.php @@ -0,0 +1,25 @@ +getSocketFlag(); + $res = HttpFactory::getInstance()->createResponse()->withBody(HttpFactory::getInstance()->createStream($msg)); + $event->withResponse($res); + } +} diff --git a/src/ZM/Event/Listener/ManagerEventListener.php b/src/ZM/Event/Listener/ManagerEventListener.php new file mode 100644 index 00000000..b74957dd --- /dev/null +++ b/src/ZM/Event/Listener/ManagerEventListener.php @@ -0,0 +1,29 @@ +debug('Manager process started'); + + SignalListener::getInstance()->signalManager(); + + /* @noinspection PhpComposerExtensionStubsInspection */ + ProcessStateManager::saveProcessState(ZM_PROCESS_MANAGER, posix_getpid()); + } + + public function onManagerStop() + { + } +} diff --git a/src/ZM/Event/Listener/MasterEventListener.php b/src/ZM/Event/Listener/MasterEventListener.php new file mode 100644 index 00000000..6ecd585a --- /dev/null +++ b/src/ZM/Event/Listener/MasterEventListener.php @@ -0,0 +1,62 @@ +getDriver()->getName() === 'swoole') { + /* @phpstan-ignore-next-line */ + $server = Framework::getInstance()->getDriver()->getSwooleServer(); + $server->on('start', function (Server $server) { + if (!Framework::getInstance()->getArgv()['disable-safe-exit']) { + SignalListener::getInstance()->signalMaster(); + } + ProcessStateManager::saveProcessState(ONEBOT_PROCESS_MASTER, $server->master_pid, [ + 'stdout' => ZMConfig::get('global.swoole_options.swoole_set.log_file'), + 'daemon' => (bool) Framework::getInstance()->getArgv()['daemon'], + ]); + }); + $server->on('shutdown', [MasterEventListener::getInstance(), 'onMasterStop']); + } else { + if (!Framework::getInstance()->getArgv()['disable-safe-exit'] && PHP_OS_FAMILY !== 'Windows') { + SignalListener::getInstance()->signalMaster(); + } + if (PHP_OS_FAMILY !== 'Windows' && extension_loaded('posix')) { + ProcessStateManager::saveProcessState(ONEBOT_PROCESS_MASTER, posix_getpid(), [ + 'stdout' => null, + 'daemon' => (bool) Framework::getInstance()->getArgv()['daemon'], + ]); + Worker::$onMasterStop = [MasterEventListener::getInstance(), 'onMasterStop']; + } + } + } + + /** + * @throws ZMKnownException + */ + public function onMasterStop() + { + if (extension_loaded('posix')) { + logger()->debug('正在关闭 Master 进程,pid=' . posix_getpid()); + ProcessStateManager::removeProcessState(ZM_PROCESS_MASTER); + if (FileSystem::scanDirFiles(ZM_PID_DIR) == []) { + rmdir(ZM_PID_DIR); + } + } + } +} diff --git a/src/ZM/Event/Listener/SignalListener.php b/src/ZM/Event/Listener/SignalListener.php new file mode 100644 index 00000000..a797c798 --- /dev/null +++ b/src/ZM/Event/Listener/SignalListener.php @@ -0,0 +1,140 @@ +getDriver()->getName()) { + case 'swoole': + Process::signal(SIGINT, [$this, 'onWorkerInt']); + break; + case 'workerman': + if (!extension_loaded('pcntl')) { + logger()->error('请安装 PCNTL 扩展以支持 SIGINT 监听'); + break; + } + pcntl_signal(SIGINT, [$this, 'onWorkerInt']); + pcntl_signal(SIGUSR1, SIG_IGN, false); + Worker::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL, '\Workerman\Worker::signalHandler'); + break; + } + } + + public function onWorkerInt() + { + // logger()->notice('Worker received SIGINT'); + } + + public function signalMaster() + { + $driver = Framework::getInstance()->getDriver()->getName(); + if ($driver === 'swoole') { + Process::signal(SIGINT, function () { + echo "\r"; + logger()->notice('Master 进程收到中断信号 SIGINT'); + logger()->notice('正在停止服务器'); + Framework::getInstance()->stop(); + if (extension_loaded('posix')) { + Process::kill(posix_getpid(), SIGTERM); + } else { + /* @phpstan-ignore-next-line */ + Process::kill(Framework::getInstance()->getDriver()->getSwooleServer()->master_pid, SIGTERM); + } + }); + } elseif ($driver === 'workerman') { + if (!extension_loaded('pcntl') || !extension_loaded('posix')) { + logger()->error('请安装 pcntl 和 posix 扩展以支持 SIGINT 监听'); + return; + } + + pcntl_signal(SIGUSR1, function () { + logger()->warning('重启ing'); + Worker::reloadSelf(); + }, false); + pcntl_signal(SIGTERM, function () { + Worker::stopAll(); + }, false); + pcntl_signal(SIGINT, function () { + echo "\r"; + logger()->notice('Master 进程收到中断信号 SIGINT'); + logger()->notice('正在停止服务器'); + Worker::stopAll(); + }, false); + } + } + + public function signalManager() + { + /** @phpstan-ignore-next-line */ + $server = Framework::getInstance()->getDriver()->getSwooleServer(); + $func = function () use ($server) { + if ($server->master_pid == $server->manager_pid) { + echo "\r"; + logger()->notice('Manager 进程收到中断信号 SIGINT'); + swoole_timer_after(2, function () { + /* @noinspection PhpComposerExtensionStubsInspection */ + Process::kill(posix_getpid(), SIGTERM); + }); + } else { + logger()->debug('Manager 已中断'); + } + $this->processKillerPrompt(); + }; + logger()->debug('正在监听 Manager 进程 SIGINT'); + if (version_compare(SWOOLE_VERSION, '4.6.7') >= 0) { + Process::signal(SIGINT, $func); + } elseif (extension_loaded('pcntl')) { + pcntl_signal(SIGINT, $func); + } + } + + /** + * 按5次Ctrl+C后强行杀死框架的处理函数 + */ + private function processKillerPrompt() + { + if (self::$manager_kill_time > 0) { + if (self::$manager_kill_time >= 5) { + $file_path = ZM_PID_DIR; + $flist = FileSystem::scanDirFiles($file_path, false, true); + foreach ($flist as $file) { + $name = explode('.', $file); + if (end($name) == 'pid' && $name[0] !== 'manager') { + $pid = file_get_contents($file_path . '/' . $file); + Process::kill((int) $pid, SIGKILL); + } + unlink($file_path . '/' . $file); + } + } else { + echo "\r"; + logger()->notice('请再按 {count} 次 Ctrl+C 以强制杀死所有 Worker 进程', ['count' => 5 - self::$manager_kill_time]); + } + } + ++self::$manager_kill_time; + } +} diff --git a/src/ZM/Event/Listener/WorkerEventListener.php b/src/ZM/Event/Listener/WorkerEventListener.php new file mode 100644 index 00000000..e9b7a8de --- /dev/null +++ b/src/ZM/Event/Listener/WorkerEventListener.php @@ -0,0 +1,40 @@ +getArgv()['disable-safe-exit'] && PHP_OS_FAMILY !== 'Windows') { + SignalListener::getInstance()->signalWorker(); + } + logger()->debug('Worker #' . ProcessManager::getProcessId() . ' started'); + + if (($name = Framework::getInstance()->getDriver()->getName()) === 'swoole') { + /* @phpstan-ignore-next-line */ + $server = Framework::getInstance()->getDriver()->getSwooleServer(); + ProcessStateManager::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $server->worker_id]); + } elseif ($name === 'workerman' && DIRECTORY_SEPARATOR !== '\\' && extension_loaded('posix')) { + ProcessStateManager::saveProcessState(ZM_PROCESS_WORKER, posix_getpid(), ['worker_id' => ProcessManager::getProcessId()]); + } + } + + public function onWorkerStop() + { + logger()->debug('Worker #' . ProcessManager::getProcessId() . ' stopping'); + ProcessStateManager::removeProcessState(ZM_PROCESS_WORKER, ProcessManager::getProcessId()); + } +} diff --git a/src/ZM/Event/SwooleEvent.php b/src/ZM/Event/SwooleEvent.php deleted file mode 100644 index 8ba12570..00000000 --- a/src/ZM/Event/SwooleEvent.php +++ /dev/null @@ -1,9 +0,0 @@ -info(Console::setColor('Reloading server...', 'gold')); - for ($i = 0; $i < ZM_WORKER_NUM; ++$i) { - Process::kill(zm_atomic('_#worker_' . $i)->get(), SIGUSR1); - } - $conf = ZMConfig::get('global', 'runtime')['reload_delay_time'] ?? 800; - if ($conf !== 0) { - usleep($conf * 1000); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnClose.php b/src/ZM/Event/SwooleEvent/OnClose.php deleted file mode 100644 index ed5a91b2..00000000 --- a/src/ZM/Event/SwooleEvent/OnClose.php +++ /dev/null @@ -1,79 +0,0 @@ -debug('Calling Swoole "close" event from fd=' . $fd); - $conn = ManagerGM::get($fd); - if ($conn === null) { - return; - } - set_coroutine_params(['server' => $server, 'connection' => $conn, 'fd' => $fd]); - - resolve(ContainerServicesProvider::class)->registerServices('connection'); - - $dispatcher1 = new EventDispatcher(OnCloseEvent::class); - $dispatcher1->setRuleFunction(function ($v) { - return $v->connect_type == ctx()->getConnection()->getName() && eval('return ' . $v->getRule() . ';'); - }); - - $dispatcher = new EventDispatcher(OnSwooleEvent::class); - $dispatcher->setRuleFunction(function ($v) { - if ($v->getRule() == '') { - return strtolower($v->type) == 'close'; - } - if (strtolower($v->type) == 'close' && eval('return ' . $v->getRule() . ';')) { - return true; - } - return false; - }); - try { - $obb_onebot = ZMConfig::get('global', 'onebot') ?? - ZMConfig::get('global', 'modules')['onebot'] ?? - ['status' => true, 'single_bot_mode' => false, 'message_level' => 99999]; - if ($conn->getName() === 'qq' && $obb_onebot['status'] === true) { - if ($obb_onebot['single_bot_mode']) { - LightCacheInside::set('connect', 'conn_fd', -1); - } - } - $dispatcher1->dispatchEvents($conn); - $dispatcher->dispatchEvents($conn); - } catch (Exception $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00016') . '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(zm_internal_errcode('E00016') . 'Uncaught ' . get_class($e) . ' when calling "close": ' . $error_msg); - Console::trace(); - } finally { - resolve(ContainerServicesProvider::class)->cleanup(); - ManagerGM::popConnect($fd); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnManagerStart.php b/src/ZM/Event/SwooleEvent/OnManagerStart.php deleted file mode 100644 index 748d2df3..00000000 --- a/src/ZM/Event/SwooleEvent/OnManagerStart.php +++ /dev/null @@ -1,136 +0,0 @@ -debug('Calling onManagerStart event(1)'); - if (!Framework::$argv['disable-safe-exit']) { - SignalListener::signalManager(); - } - ProcessManager::saveProcessState(ZM_PROCESS_MANAGER, $server->manager_pid); - - ProcessManager::createUserProcess('monitor', function () use ($server) { - Process::signal(SIGINT, function () { - Console::success('用户进程检测到了Ctrl+C'); - }); - if (Framework::$argv['watch']) { - if (extension_loaded('inotify')) { - logger()->info('Enabled File watcher, framework will reload automatically.'); - $fd = inotify_init(); - $this->addWatcher(DataProvider::getSourceRootDir() . '/src', $fd); - Event::add($fd, function () use ($fd) { - $r = inotify_read($fd); - logger()->debug('File updated: ' . $r[0]['name']); - ZMUtil::reload(); - }); - Framework::$argv['polling-watch'] = false; // 如果开启了inotify则关闭轮询热更新 - } else { - logger()->warning(zm_internal_errcode('E00024') . '你还没有安装或启用 inotify 扩展,将默认使用轮询检测模式开启热更新!'); - Framework::$argv['polling-watch'] = true; - } - } - if (Framework::$argv['polling-watch']) { - self::$watch_tick_id = swoole_timer_tick(3000, function () use ($server) { - $data = (DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/')); - $hash = md5(''); - foreach ($data as $file) { - $hash = md5($hash . md5_file($file)); - } - if (self::$last_hash == '') { - self::$last_hash = $hash; - } elseif (self::$last_hash !== $hash) { - self::$last_hash = $hash; - $server->reload(); - } - }); - } - if (Framework::$argv['interact']) { - logger()->info('Interact mode'); - ZMBuf::$terminal = $r = STDIN; - Event::add($r, function () use ($r) { - $fget = fgets($r); - if ($fget === false) { - Event::del($r); - return; - } - $var = trim($fget); - if ($var == 'stop') { - Event::del($r); - } - try { - Terminal::executeCommand($var); - } catch (Exception $e) { - Console::error(zm_internal_errcode('E00025') . 'Uncaught exception ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'); - } catch (Error $e) { - Console::error(zm_internal_errcode('E00025') . 'Uncaught error ' . get_class($e) . ': ' . $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'); - } - }); - } - }); - - ProcessManager::getUserProcess('monitor')->set(['enable_coroutine' => true]); - ProcessManager::getUserProcess('monitor')->start(); - - /*$dispatcher = new EventDispatcher(OnManagerStartEvent::class); - $dispatcher->setRuleFunction(function($v) { - return eval("return " . $v->getRule() . ";"); - }); - $dispatcher->dispatchEvents($server); -*/ - logger()->debug('进程 Manager 已启动'); - } - - private function addWatcher($maindir, $fd) - { - $dir = scandir($maindir); - if ($dir[0] == '.') { - unset($dir[0], $dir[1]); - } - foreach ($dir as $subdir) { - if (is_dir($maindir . '/' . $subdir)) { - logger()->debug('添加监听目录:' . $maindir . '/' . $subdir); - inotify_add_watch($fd, $maindir . '/' . $subdir, IN_ATTRIB | IN_ISDIR); - $this->addWatcher($maindir . '/' . $subdir, $fd); - } - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnManagerStop.php b/src/ZM/Event/SwooleEvent/OnManagerStop.php deleted file mode 100644 index 660dbc9a..00000000 --- a/src/ZM/Event/SwooleEvent/OnManagerStop.php +++ /dev/null @@ -1,28 +0,0 @@ -pid) !== false) { - Process::kill($v->pid, SIGTERM); - } - } - logger()->debug('进程 Manager 已停止!'); - ProcessManager::removeProcessState(ZM_PROCESS_MANAGER); - } -} diff --git a/src/ZM/Event/SwooleEvent/OnMessage.php b/src/ZM/Event/SwooleEvent/OnMessage.php deleted file mode 100644 index 49d05129..00000000 --- a/src/ZM/Event/SwooleEvent/OnMessage.php +++ /dev/null @@ -1,67 +0,0 @@ -debug('Calling Swoole "message" from fd=' . $frame->fd . ': ' . TermColor::ITALIC . $frame->data . TermColor::RESET); - unset(Context::$context[Coroutine::getCid()]); - $conn = ManagerGM::get($frame->fd); - set_coroutine_params(['server' => $server, 'frame' => $frame, 'connection' => $conn]); - - resolve(ContainerServicesProvider::class)->registerServices('message'); - - $dispatcher1 = new EventDispatcher(OnMessageEvent::class); - $dispatcher1->setRuleFunction(function ($v) use ($conn) { - return $v->connect_type === $conn->getName() && ($v->getRule() === '' || eval('return ' . $v->getRule() . ';')); - }); - - $dispatcher = new EventDispatcher(OnSwooleEvent::class); - $dispatcher->setRuleFunction(function ($v) { - if ($v->getRule() == '') { - return strtolower($v->type) == 'message'; - } - /* @noinspection PhpUnreachableStatementInspection - * @noinspection RedundantSuppression - */ - if (strtolower($v->type) == 'message' && eval('return ' . $v->getRule() . ';')) { - return true; - } - return false; - }); - try { - // $starttime = microtime(true); - $dispatcher1->dispatchEvents($conn); - $dispatcher->dispatchEvents($conn); - // Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); - } catch (Throwable $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00017') . 'Uncaught ' . get_class($e) . ' when calling "message": ' . $error_msg); - Console::trace(); - } finally { - resolve(ContainerServicesProvider::class)->cleanup(); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnOpen.php b/src/ZM/Event/SwooleEvent/OnOpen.php deleted file mode 100644 index bd276103..00000000 --- a/src/ZM/Event/SwooleEvent/OnOpen.php +++ /dev/null @@ -1,98 +0,0 @@ -debug('Calling Swoole "open" event from fd=' . $request->fd); - unset(Context::$context[Coroutine::getCid()]); - - $type = strtolower($request->header['x-client-role'] ?? $request->get['type'] ?? ''); - $access_token = explode(' ', $request->header['authorization'] ?? '')[1] ?? $request->get['token'] ?? ''; - $token = ZMConfig::get('global', 'access_token'); - if ($token instanceof Closure) { - if (!$token($access_token)) { - $server->close($request->fd); - logger()->warning(zm_internal_errcode('E00018') . 'Unauthorized access_token: ' . $access_token); - return; - } - } elseif (is_string($token)) { - if ($access_token !== $token && $token !== '') { - $server->close($request->fd); - logger()->warning(zm_internal_errcode('E00019') . 'Unauthorized access_token: ' . $access_token); - return; - } - } - $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]); - $conn->setOption('connect_id', (string) ($request->header['x-self-id'] ?? '')); - - resolve(ContainerServicesProvider::class)->registerServices('connection'); - - $dispatcher1 = new EventDispatcher(OnOpenEvent::class); - $dispatcher1->setRuleFunction(function ($v) { - return ctx()->getConnection()->getName() == $v->connect_type && eval('return ' . $v->getRule() . ';'); - }); - - $dispatcher = new EventDispatcher(OnSwooleEvent::class); - $dispatcher->setRuleFunction(function ($v) { - if ($v->getRule() == '') { - return strtolower($v->type) == 'open'; - } - if (strtolower($v->type) == 'open' && eval('return ' . $v->getRule() . ';')) { - return true; - } - return false; - }); - try { - $obb_onebot = ZMConfig::get('global', 'onebot') ?? - ZMConfig::get('global', 'modules')['onebot'] ?? - ['status' => true, 'single_bot_mode' => false, 'message_level' => 99]; - $onebot_status = $obb_onebot['status']; - if ($conn->getName() === 'qq' && $onebot_status === true) { - if ($obb_onebot['single_bot_mode']) { - LightCacheInside::set('connect', 'conn_fd', $request->fd); - } - } - $dispatcher1->dispatchEvents($conn); - $dispatcher->dispatchEvents($conn); - } catch (Exception $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00020') . '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(zm_internal_errcode('E00020') . 'Uncaught ' . get_class($e) . ' when calling "open": ' . $error_msg); - Console::trace(); - } finally { - resolve(ContainerServicesProvider::class)->cleanup(); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnPipeMessage.php b/src/ZM/Event/SwooleEvent/OnPipeMessage.php deleted file mode 100644 index 2c08812b..00000000 --- a/src/ZM/Event/SwooleEvent/OnPipeMessage.php +++ /dev/null @@ -1,33 +0,0 @@ -getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00021') . 'Uncaught ' . get_class($e) . ' when calling "pipeMessage": ' . $error_msg); - Console::trace(); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnRequest.php b/src/ZM/Event/SwooleEvent/OnRequest.php deleted file mode 100644 index 5573f41f..00000000 --- a/src/ZM/Event/SwooleEvent/OnRequest.php +++ /dev/null @@ -1,139 +0,0 @@ - $v) { - $response->setHeader($k, $v); - } - unset(Context::$context[Coroutine::getCid()]); - logger()->debug('Calling Swoole "request" event from fd=' . $request->fd); - set_coroutine_params(['request' => $request, 'response' => $response]); - - resolve(ContainerServicesProvider::class)->registerServices('request'); - - $dis1 = new EventDispatcher(OnRequestEvent::class); - $dis1->setRuleFunction(function ($v) { - return (bool) eval('return ' . $v->getRule() . ';'); - }); - - $dis = new EventDispatcher(OnSwooleEvent::class); - $dis->setRuleFunction(function ($v) { - if ($v->getRule() == '') { - return strtolower($v->type) == 'request'; - } - if (strtolower($v->type) == 'request' && eval('return ' . $v->getRule() . ';')) { - return true; - } - return false; - }); - - try { - $dis1->dispatchEvents($request, $response); - $dis->dispatchEvents($request, $response); - if ($dis->status === EventDispatcher::STATUS_NORMAL && $dis1->status === EventDispatcher::STATUS_NORMAL) { - $result = HttpUtil::parseUri($request, $response, $request->server['request_uri'], $node, $params); - if ($result === true) { - ctx()->setCache('params', $params); - $dispatcher = new EventDispatcher(RequestMapping::class); - $div = new RequestMapping($node['route']); - $div->params = $params; - $div->method = $node['method']; - $div->request_method = $node['request_method']; - $div->class = $node['class']; - $dispatcher->dispatchEvent($div, null, $params, $request, $response); - - if (!$response->isEnd()) { - $this->response($response, $dispatcher->store); - } - } - } - if (!$response->isEnd()) { - HttpUtil::responseCodePage($response, 404); - } - } catch (InterruptException $e) { - // do nothing - } catch (Exception $e) { - $response->status(500); - logger()->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(zm_internal_errcode('E00023') . 'Internal server exception: ' . $e->getMessage()); - } else { - $response->end(zm_internal_errcode('E00023') . 'Internal server error.'); - } - } - Console::error(zm_internal_errcode('E00023') . 'Internal server exception (500), caused by ' . get_class($e) . ': ' . $e->getMessage()); - Console::log($e->getTraceAsString(), 'gray'); - } catch (Error $e) { - $response->status(500); - logger()->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(zm_internal_errcode('E00023') . 'Internal server error: ' . $error_msg); - } else { - $response->end(zm_internal_errcode('E00023') . 'Internal server error.'); - } - } - Console::error(zm_internal_errcode('E00023') . 'Internal server error (500), caused by ' . get_class($e) . ': ' . $e->getMessage()); - Console::log($e->getTraceAsString(), 'gray'); - } finally { - resolve(ContainerServicesProvider::class)->cleanup(); - } - } - - /** - * 返回响应 - * - * @param mixed $result - */ - private function response(Response $response, $result): void - { - if (is_string($result)) { - $response->end($result); - } else { - try { - $response->header('Content-Type', 'application/json'); - $response->end(json_encode($result, JSON_UNESCAPED_UNICODE)); - } catch (Exception $e) { - Console::error('无法将响应转换为JSON:' . $e->getMessage()); - $response->end(json_encode(zm_internal_errcode('E00023') . 'Internal server error.')); - } - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnShutdown.php b/src/ZM/Event/SwooleEvent/OnShutdown.php deleted file mode 100644 index f4bae602..00000000 --- a/src/ZM/Event/SwooleEvent/OnShutdown.php +++ /dev/null @@ -1,29 +0,0 @@ -debug('正在关闭 Master 进程,pid=' . posix_getpid()); - ProcessManager::removeProcessState(ZM_PROCESS_MASTER); - if (DataProvider::scanDirFiles(_zm_pid_dir()) == []) { - rmdir(_zm_pid_dir()); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnStart.php b/src/ZM/Event/SwooleEvent/OnStart.php deleted file mode 100644 index 4581a0b4..00000000 --- a/src/ZM/Event/SwooleEvent/OnStart.php +++ /dev/null @@ -1,32 +0,0 @@ -debug('Calling onStart event(1)'); - if (!Framework::$argv['disable-safe-exit']) { - SignalListener::signalMaster($server); - } - ProcessManager::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [ - 'stdout' => ZMConfig::get('global')['swoole']['log_file'], - 'daemon' => (bool) Framework::$argv['daemon'], - ]); - } -} diff --git a/src/ZM/Event/SwooleEvent/OnTask.php b/src/ZM/Event/SwooleEvent/OnTask.php deleted file mode 100644 index 4e32d373..00000000 --- a/src/ZM/Event/SwooleEvent/OnTask.php +++ /dev/null @@ -1,74 +0,0 @@ -data['task'])) { - $dispatcher = new EventDispatcher(\ZM\Annotation\Swoole\OnTask::class); - $dispatcher->setRuleFunction(function ($v) use ($task) { - /* @var \ZM\Annotation\Swoole\OnTask $v */ - return $v->task_name == $task->data['task']; - }); - $dispatcher->setReturnFunction(function ($return) { - EventDispatcher::interrupt($return); - }); - $params = $task->data['params']; - try { - $dispatcher->dispatchEvents(...$params); - } catch (Throwable $e) { - $finish['throw'] = $e; - } - if ($dispatcher->status === EventDispatcher::STATUS_EXCEPTION) { - $finish['result'] = null; - $finish['retcode'] = -1; - } else { - $finish['result'] = $dispatcher->store; - $finish['retcode'] = 0; - } - if (zm_atomic('server_is_stopped')->get() === 1) { - return; - } - $task->finish($finish); - } else { - try { - $dispatcher = new EventDispatcher(OnTaskEvent::class); - $dispatcher->setRuleFunction(function ($v) { - /* @var OnTaskEvent $v */ - return eval('return ' . $v->getRule() . ';'); - }); - $dispatcher->dispatchEvents(); - } catch (Exception $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00026') . 'Uncaught exception ' . get_class($e) . ' when calling "task": ' . $error_msg); - Console::trace(); - } catch (Error $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00026') . 'Uncaught ' . get_class($e) . ' when calling "task": ' . $error_msg); - Console::trace(); - } - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnWorkerExit.php b/src/ZM/Event/SwooleEvent/OnWorkerExit.php deleted file mode 100644 index 68e45946..00000000 --- a/src/ZM/Event/SwooleEvent/OnWorkerExit.php +++ /dev/null @@ -1,32 +0,0 @@ -debug('正在结束 Worker #' . $worker_id . ',进程内可能有事务在运行...'); - } -} diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php deleted file mode 100644 index 4eab9ae8..00000000 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ /dev/null @@ -1,314 +0,0 @@ -debug('Calling onWorkerStart event(1)'); - if (!Framework::$argv['disable-safe-exit']) { - SignalListener::signalWorker($server, $worker_id); - } - unset(Context::$context[Coroutine::getCid()]); - - Framework::$server = $server; - - resolve(ContainerServicesProvider::class)->registerServices('global'); - - if ($server->taskworker === false) { - ProcessManager::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]); - zm_atomic('_#worker_' . $worker_id)->set($server->worker_pid); - if (LightCacheInside::get('wait_api', 'wait_api') !== null) { - LightCacheInside::unset('wait_api', 'wait_api'); - } - try { - register_shutdown_function(function () use ($server) { - $error = error_get_last(); - if (($error['type'] ?? 0) != 0) { - Console::error(zm_internal_errcode('E00027') . 'Internal fatal error: ' . $error['message'] . ' at ' . $error['file'] . "({$error['line']})"); - zm_dump($error); - } elseif (!isset($error['type'])) { - return; - } - // DataProvider::saveBuffer(); - $server->shutdown(); - }); - - logger()->debug("Worker #{$server->worker_id} starting"); - // ZMBuf::resetCache(); //清空变量缓存 - // ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 - - // TODO: 单独抽出来MySQL和Redis连接池 - $this->initMySQLPool(); - - // 开箱即用的Redis - $redis = ZMConfig::get('global', 'redis_config'); - if ($redis !== null && $redis['host'] != '') { - if (!extension_loaded('redis')) { - Console::error(zm_internal_errcode('E00029') . "Can not find redis extension.\n"); - } else { - ZMRedisPool::init($redis); - } - } - - $this->loadAnnotations(); // 加载composer资源、phar外置包、注解解析注册等 - CronManager::initCronTasks(); // 初始化定时任务 - EventManager::registerTimerTick(); // 启动计时器 - - 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); - if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) { - logger()->debug('@OnStart 执行完毕'); - } else { - logger()->warning('@OnStart 执行异常!'); - } - logger()->debug('Worker #' . $worker_id . ' started'); - $this->gatherWorkerStartStatus(); - } catch (Exception $e) { - if ($e->getMessage() === 'swoole exit') { - return; - } - Console::error('Worker #' . $server->worker_id . ' 加载出错!停止服务!'); - Console::error(zm_internal_errcode('E00030') . 'Uncaught ' . get_class($e) . ': ' . $e->getMessage() . "\n" . $e->getTraceAsString()); - Process::kill($server->master_pid, SIGTERM); - return; - } catch (Error $e) { - Console::error(zm_internal_errcode('E00030') . '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'); - if (!Framework::$argv['watch']) { // 在热更新模式下不能退出 - Process::kill($server->master_pid, SIGTERM); - } - } - } else { - // 这里是TaskWorker初始化的内容部分 - ProcessManager::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]); - try { - $this->loadAnnotations(); - logger()->debug('TaskWorker #' . $server->worker_id . ' started'); - } catch (Exception $e) { - Console::error('TaskWorker #' . $server->worker_id . ' 加载出错!停止服务!'); - Console::error(zm_internal_errcode('E00030') . $e->getMessage() . "\n" . $e->getTraceAsString()); - Process::kill($server->master_pid, SIGTERM); - return; - } catch (Error $e) { - Console::error(zm_internal_errcode('E00030') . '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'); - Process::kill($server->master_pid, SIGTERM); - } - } - } - - /** - * @throws ReflectionException - * @throws Exception - */ - private function loadAnnotations() - { - if (Framework::$instant_mode) { - goto skip; - } - // 加载各个模块的注解类,以及反射 - logger()->debug('Mapping annotations'); - $parser = new AnnotationParser(); - $composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true); - $merge = array_merge($composer['autoload']['psr-4'] ?? [], $composer['autoload-dev']['psr-4'] ?? []); - $exclude_annotations = array_merge($composer['extra']['exclude_annotate'] ?? [], $composer['extra']['zm']['exclude-annotation-path'] ?? []); - foreach ($merge as $k => $v) { - if (is_dir(DataProvider::getSourceRootDir() . '/' . $v)) { - if (in_array($v, $exclude_annotations)) { - continue; - } - if (trim($k, '\\') == 'ZM') { - continue; - } - $parser->addRegisterPath(DataProvider::getSourceRootDir() . '/' . $v . '/', trim($k, '\\')); - } - } - - // 检查是否允许热加载phar模块,允许的话将遍历phar内的文件 - $plugin_enable_hotload = ZMConfig::get('global', 'module_loader')['enable_hotload'] ?? false; - if ($plugin_enable_hotload) { - $list = ModuleManager::getPackedModules(); - foreach ($list as $k => $v) { - if (\server()->worker_id === 0) { - logger()->info('Loading packed module: ' . $k); - } - require_once $v['phar-path']; - $func = 'loader' . $v['generated-id']; - $func(); - $parser->addRegisterPath('phar://' . $v['phar-path'] . '/' . $v['module-root-path'], $v['namespace']); - } - } - - // 检查所有的Composer模块,并加载注解 - $list = ModuleManager::getComposerModules(); - foreach ($list as $k => $v) { - if (\server()->worker_id === 0) { - logger()->info('Loading composer module: ' . $k); - } - $parser->addRegisterPath($v['module-path'], $v['namespace']); - } - - $parser->registerMods(); - EventManager::loadEventByParser($parser); // 加载事件 - - skip: - // 加载自定义的全局函数 - logger()->debug('Loading context class...'); - $context_class = ZMConfig::get('global', 'context_class'); - if (!is_a($context_class, ContextInterface::class, true)) { - throw new ZMKnownException('E00032', 'Context class must implemented from ContextInterface!'); - } - // 加载插件 - $obb_onebot = ZMConfig::get('global', 'onebot') ?? - ZMConfig::get('global', 'modules')['onebot'] ?? - ['status' => true, 'single_bot_mode' => false, 'message_level' => 99999]; - if ($obb_onebot['status']) { - logger()->debug('OneBot support enabled, listening OneBot event(3).'); - $obj = new OnMessageEvent(); - $obj->connect_type = 'qq'; - $obj->class = AdapterInterface::class; - $obj->method = 'handleIncomingRequest'; - $obj->level = $obb_onebot['message_level'] ?? 99; - EventManager::addEvent(OnMessageEvent::class, $obj); - if ($obb_onebot['single_bot_mode']) { - LightCacheInside::set('connect', 'conn_fd', -1); - } else { - LightCacheInside::set('connect', 'conn_fd', -2); - } - } - } - - private function initMySQLPool() - { - if (SqlPoolStorage::$sql_pool !== null) { - SqlPoolStorage::$sql_pool->close(); - SqlPoolStorage::$sql_pool = null; - } - $real_conf = []; - if (isset(ZMConfig::get('global', 'sql_config')['sql_host'])) { - if (ZMConfig::get('global', 'sql_config')['sql_host'] != '') { - if (\server()->worker_id === 0) { - logger()->warning("使用 'sql_config' 配置项和 DB 数据库查询构造器进行查询数据库可能会在下一个大版本中废弃,请使用 'mysql_config' 搭配 doctrine dbal 使用!"); - logger()->warning('详见: `https://framework.zhamao.xin/`'); - } - $origin_conf = ZMConfig::get('global', 'sql_config'); - $real_conf = [ - 'host' => $origin_conf['sql_host'], - 'port' => $origin_conf['sql_port'], - 'username' => $origin_conf['sql_username'], - 'password' => $origin_conf['sql_password'], - 'dbname' => $origin_conf['sql_database'], - 'options' => $origin_conf['sql_options'], - 'unix_socket' => null, - 'charset' => 'utf8mb4', - 'pool_size' => 64, - ]; - } - } - if (isset(ZMConfig::get('global', 'mysql_config')['host'])) { - if (ZMConfig::get('global', 'mysql_config')['host'] != '') { - $real_conf = ZMConfig::get('global', 'mysql_config'); - } - } - if (!empty($real_conf)) { - logger()->info('Connecting to MySQL pool'); - ob_start(); - phpinfo(); // 这个phpinfo是有用的,不能删除 - $str = ob_get_clean(); - $str = explode("\n", $str); - foreach ($str as $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(zm_internal_errcode('E00028') . '未安装 mysqlnd php-mysql扩展。'); - } - } - SqlPoolStorage::$sql_pool = new MySQLPool( - (new PDOConfig()) - ->withHost($real_conf['host']) - ->withPort($real_conf['port']) - // ->withUnixSocket('/tmp/mysql.sock') - ->withDbName($real_conf['dbname']) - ->withCharset($real_conf['charset']) - ->withUsername($real_conf['username']) - ->withPassword($real_conf['password']) - ->withOptions($real_conf['options'] ?? [PDO::ATTR_STRINGIFY_FETCHES => false]) - ); - DB::initTableList($real_conf['dbname']); - } - } - - private function gatherWorkerStartStatus() - { - SpinLock::lock('worker_start_status'); - $ls = LightCacheInside::get('tmp_kv', 'worker_start_status') ?? []; - $ls[\server()->worker_id] = true; - if (count($ls) === \server()->setting['worker_num']) { - LightCacheInside::set('tmp_kv', 'worker_start_status', $ls); - SpinLock::unlock('worker_start_status'); - $used = round((microtime(true) - LightCacheInside::get('tmp_kv', 'start_time')) * 1000, 3); - $worker_count = \server()->setting['worker_num']; - logger()->info("{$worker_count} 个工作进程成功启动,共用时 {$used} ms"); - } else { - LightCacheInside::set('tmp_kv', 'worker_start_status', $ls); - SpinLock::unlock('worker_start_status'); - } - } -} diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStop.php b/src/ZM/Event/SwooleEvent/OnWorkerStop.php deleted file mode 100644 index ea8be6c9..00000000 --- a/src/ZM/Event/SwooleEvent/OnWorkerStop.php +++ /dev/null @@ -1,28 +0,0 @@ -debug('{worker}#{worker_id} 已停止(状态码:{status})', ['worker' => ($server->taskworker ? 'Task' : '') . 'Worker', 'worker_id' => $worker_id, 'status' => $server->getWorkerStatus($worker_id)]); - ProcessManager::removeProcessState($server->taskworker ? ZM_PROCESS_TASKWORKER : ZM_PROCESS_WORKER, $worker_id); - } -} diff --git a/src/ZM/Exception/AnnotationException.php b/src/ZM/Exception/AnnotationException.php deleted file mode 100644 index 5f52df74..00000000 --- a/src/ZM/Exception/AnnotationException.php +++ /dev/null @@ -1,9 +0,0 @@ -module = $module; - } -} diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 5345b600..823d503c 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -4,380 +4,366 @@ declare(strict_types=1); namespace ZM; -use Doctrine\Common\Annotations\AnnotationReader; -use Error; use Exception; -use InvalidArgumentException; +use OneBot\Driver\Driver; +use OneBot\Driver\Event\DriverInitEvent; +use OneBot\Driver\Event\Http\HttpRequestEvent; +use OneBot\Driver\Event\Process\ManagerStartEvent; +use OneBot\Driver\Event\Process\ManagerStopEvent; +use OneBot\Driver\Event\Process\WorkerStartEvent; +use OneBot\Driver\Event\Process\WorkerStopEvent; +use OneBot\Driver\Interfaces\DriverInitPolicy; +use OneBot\Driver\Swoole\SwooleDriver; +use OneBot\Driver\Workerman\Worker; +use OneBot\Driver\Workerman\WorkermanDriver; +use OneBot\Util\Singleton; use Phar; -use ReflectionClass; -use ReflectionException; -use Swoole\Runtime; -use Swoole\Server\Port; -use Swoole\WebSocket\Server; -use Throwable; -use ZM\Annotation\Swoole\SwooleHandler; +use Swoole\Process; +use ZM\Command\Server\ServerStartCommand; use ZM\Config\ZMConfig; -use ZM\ConnectionManager\ManagerGM; -use ZM\Console\Console; +use ZM\Event\EventProvider; +use ZM\Event\Listener\HttpEventListener; +use ZM\Event\Listener\ManagerEventListener; +use ZM\Event\Listener\MasterEventListener; +use ZM\Event\Listener\WorkerEventListener; use ZM\Exception\ConfigException; +use ZM\Exception\InitException; +use ZM\Logger\ConsoleLogger; use ZM\Logger\TablePrinter; -use ZM\Store\LightCache; -use ZM\Store\LightCacheInside; -use ZM\Store\Lock\SpinLock; -use ZM\Store\ZMAtomic; -use ZM\Utils\DataProvider; -use ZM\Utils\Manager\ProcessManager; -use ZM\Utils\Terminal; -use ZM\Utils\ZMUtil; +use ZM\Process\ProcessStateManager; +/** + * 框架入口类 + * @since 3.0 + */ class Framework { - public const VERSION_ID = 609; + use Singleton; - public const VERSION = '3.0.0-alpha1'; + /** @var int 版本ID */ + public const VERSION_ID = 610; + + /** @var string 版本名称 */ + public const VERSION = '3.0.0-alpha2'; + + /** @var array 传入的参数 */ + protected $argv; + + /** @var Driver|SwooleDriver|WorkermanDriver OneBot驱动 */ + protected $driver; + + /** @var array> 启动注解列表 */ + protected $setup_annotations = []; /** - * 框架运行的参数 + * 框架初始化文件 * - * @var array - */ - public static $argv; - - /** - * 通信服务器实例 - * - * @var Server - */ - public static $server; - - /** - * 框架加载的文件 - * - * @var string[] - */ - public static $loaded_files = []; - - /** - * 是否为单文件模式 - * - * @var bool - */ - public static $instant_mode = false; - - /** - * Swoole 服务端配置 - * - * @var null|array - */ - private $swoole_server_config; - - /** - * @var array - */ - private $setup_events = []; - - /** - * 创建一个新的框架实例 - * - * @param array $args 运行参数 - * @param bool $instant_mode 是否为单文件模式 + * @param array $argv 传入的参数(见 ServerStartCommand) + * @throws InitException * @throws ConfigException + * @throws Exception */ - public function __construct(array $args = [], bool $instant_mode = false) + public function __construct(array $argv = []) { - self::$instant_mode = $instant_mode; - self::$argv = $args; - Runtime::enableCoroutine(false); - - // 初始化配置 - ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); - ZMConfig::setEnv($args['env'] ?? 'development'); - if (ZMConfig::get('global') === false) { - echo zm_internal_errcode('E00007') . 'Global config load failed: ' . ZMConfig::$last_error . "\nError path: " . DataProvider::getSourceRootDir() . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n"; - exit(1); + // 单例化整个Framework类 + if (self::$instance !== null) { + throw new InitException(zm_internal_errcode('E00069') . 'Initializing another Framework in one instance is not allowed!'); } + self::$instance = $this; - // 定义常量 - require_once 'global_defines.php'; + // 初始化必需的args参数,如果没有传入的话,使用默认值 + $this->argv = empty($argv) ? ServerStartCommand::exportOptionArray() : $argv; - // 确保目录存在 - DataProvider::createIfNotExists(ZMConfig::get('global', 'zm_data')); - DataProvider::createIfNotExists(ZMConfig::get('global', 'config_dir')); - DataProvider::createIfNotExists(ZMConfig::get('global', 'crash_dir')); + // 执行一些 Driver 前置条件的内容 + $this->initDriverPrerequisites(); - // 初始化连接池? - 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) { - logger()->emergency(zm_internal_errcode('E00008') . $e->getMessage()); - exit(1); - } + // 初始化 Driver 及框架内部需要监听的事件 + $this->initDriver(); - try { - // 初始化日志 - Console::init( - ZMConfig::get('global', 'info_level') ?? 2, - self::$server, - $args['log-theme'] ?? 'default', - ($o = ZMConfig::get('console_color')) === false ? [] : $o - ); - // 是否同步输出到文件 - if ((ZMConfig::get('global', 'runtime')['save_console_log_file'] ?? false) !== false) { - Console::setOutputFile(ZMConfig::get('global', 'runtime')['save_console_log_file']); - } - - // 设置默认时区 - $timezone = ZMConfig::get('global', 'timezone') ?? 'Asia/Shanghai'; - date_default_timezone_set($timezone); - - // 读取 Swoole 配置 - $this->swoole_server_config = ZMConfig::get('global', 'swoole'); - $this->swoole_server_config['log_level'] = SWOOLE_LOG_DEBUG; - - // 是否启用远程终端 - $remote_terminal = ZMConfig::get('global', 'remote_terminal')['status'] ?? false; - - // 加载服务器事件 - if (!$instant_mode) { - $this->loadServerEvents(); - } - - // 解析命令行参数 - [$coroutine_mode, $terminal_id, $remote_terminal] = $this->parseCliArgs(self::$argv, compact('remote_terminal')); - - // 设置默认最长等待时间 - if (!isset($this->swoole_server_config['max_wait_time'])) { - $this->swoole_server_config['max_wait_time'] = 5; - } - // 设置最大 worker 进程数 - $worker = $this->swoole_server_config['worker_num'] ?? swoole_cpu_num(); - define('ZM_WORKER_NUM', $worker); - - // 初始化原子计数器 - ZMAtomic::init(); - - // 非静默模式下打印启动信息 - if (!self::$argv['private-mode']) { - $this->printProperties($remote_terminal, $args); - } - - // 预览模式则直接提出 - if ($args['preview'] ?? false) { - exit(); - } - - // 初始化服务器 - self::$server = new Server( - ZMConfig::get('global', 'host'), - ZMConfig::get('global', 'port'), - ZMConfig::get('global', 'runtime')['swoole_server_mode'] ?? SWOOLE_PROCESS - ); - - // 监听远程终端 - if ($remote_terminal) { - $conf = ZMConfig::get('global', 'remote_terminal') ?? [ - 'status' => true, - 'host' => '127.0.0.1', - 'port' => 20002, - 'token' => '', - ]; - - $welcome_msg = capture_output([logger('trm'), 'info'], ['欢迎使用炸毛远程终端!输入 `help` 查看帮助!']); - $motd = capture_output(function () { - $this->printMotd(); - }); - - /** @var Port $port */ - $port = self::$server->listen($conf['host'], $conf['port'], SWOOLE_SOCK_TCP); - $port->set([ - 'open_http_protocol' => false, - ]); - $port->on('connect', function (\Swoole\Server $serv, $fd) use ($welcome_msg, $conf, $motd) { - ManagerGM::pushConnect($fd, 'terminal'); - // 推送欢迎信息 - $serv->send($fd, $motd); - // 要求输入令牌 - if (!empty($conf['token'])) { - $serv->send($fd, '请输入令牌:'); - } else { - $serv->send($fd, $welcome_msg . "\n>>> "); - } - }); - - $port->on('receive', function ($serv, $fd, $reactor_id, $data) use ($welcome_msg, $conf) { - ob_start(); - try { - $arr = LightCacheInside::get('light_array', 'input_token') ?? []; - if (empty($arr[$fd] ?? '') && $conf['token'] !== '') { - $token = trim($data); - if ($token === $conf['token']) { - SpinLock::transaction('input_token', static function () use ($fd, $token) { - $arr = LightCacheInside::get('light_array', 'input_token'); - $arr[$fd] = $token; - LightCacheInside::set('light_array', 'input_token', $arr); - }); - $serv->send($fd, capture_output([logger('trm'), 'info'], ['令牌验证成功!'])); - $serv->send($fd, $welcome_msg . "\n>>> "); - } else { - $serv->send($fd, capture_output([logger('trm'), 'error'], ['令牌验证失败!'])); - $serv->close($fd); - } - return; - } - if (trim($data) === 'exit' || trim($data) === 'q') { - $serv->send($fd, capture_output([logger('trm'), 'info'], ['再见!'])); - $serv->close($fd); - return; - } - Terminal::executeCommand(trim($data)); - } catch (Exception $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - logger('trm')->error(zm_internal_errcode('E00009') . 'Uncaught exception ' . get_class($e) . ' when calling "open": ' . $error_msg); - logger('trm')->error($e->getTraceAsString()); - } catch (Error $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - logger('trm')->error(zm_internal_errcode('E00009') . 'Uncaught ' . get_class($e) . ' when calling "open": ' . $error_msg); - logger('trm')->error($e->getTraceAsString()); - } - - $r = ob_get_clean(); - if (!empty($r)) { - $serv->send($fd, $r); - } - if (!in_array(trim($data), ['r', 'reload'])) { - $serv->send($fd, '>>> '); - } - }); - - $port->on('close', function ($serv, $fd) { - ManagerGM::popConnect($fd); - }); - } - - // 设置服务器配置 - self::$server->set($this->swoole_server_config); - Console::setServer(self::$server); - - // 非静默模式下,打印欢迎信息 - if (!self::$argv['private-mode']) { - $this->printMotd(); - } - - $global_hook = ZMConfig::get('global', 'runtime')['swoole_coroutine_hook_flags'] ?? (SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)); - if ($coroutine_mode) { - Runtime::enableCoroutine(true, $global_hook); - } else { - Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL); - } - - // 注册 Swoole Server 的事件 - $this->registerServerEvents(); - - // 初始化缓存 - $r = ZMConfig::get('global', 'light_cache') ?? [ - 'size' => 512, // 最多允许储存的条数(需要2的倍数) - 'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数) - 'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多) - 'persistence_path' => DataProvider::getDataFolder() . '_cache.json', - 'auto_save_interval' => 900, - ]; - LightCache::init($r); - LightCacheInside::init(); - - // 初始化自旋锁 - SpinLock::init($r['size']); - - // 注册全局错误处理器 - set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) { - $tips = [ - E_WARNING => ['PHP Warning: ', 'warning'], - E_NOTICE => ['PHP Notice: ', 'notice'], - E_USER_ERROR => ['PHP Error: ', 'error'], - E_USER_WARNING => ['PHP Warning: ', 'warning'], - E_USER_NOTICE => ['PHP Notice: ', 'notice'], - E_STRICT => ['PHP Strict: ', 'notice'], - E_RECOVERABLE_ERROR => ['PHP Recoverable Error: ', 'error'], - E_DEPRECATED => ['PHP Deprecated: ', 'notice'], - E_USER_DEPRECATED => ['PHP User Deprecated: ', 'notice'], - ]; - $level_tip = $tips[$error_no] ?? ['PHP Unknown: ', 'error']; - $error = $level_tip[0] . $error_msg . ' in ' . $error_file . ' on ' . $error_line; - logger()->{$level_tip[1]}($error); - // 如果 return false 则错误会继续递交给 PHP 标准错误处理 - return true; - }, E_ALL | E_STRICT); - } catch (Exception $e) { - logger()->emergency('框架初始化失败,请检查!'); - logger()->emergency(zm_internal_errcode('E00010') . $e->getMessage()); - if (strpos($e->getMessage(), 'Address already in use') !== false) { - if (!ProcessManager::isStateEmpty()) { - logger()->alert('检测到可能残留框架的工作进程,请先通过命令杀死:server:stop --force'); - } - } - logger()->debug($e); - exit; - } - } - - public static function loadFrameworkState() - { - if (!file_exists(DataProvider::getDataFolder() . '.state.json')) { - return []; - } - $r = json_decode(file_get_contents(DataProvider::getDataFolder() . '.state.json'), true); - if ($r === null) { - $r = []; - } - return $r; - } - - public static function saveFrameworkState($data) - { - return file_put_contents(DataProvider::getDataFolder() . '.state.json', json_encode($data, 64 | 128 | 256)); + // 初始化框架的交互以及框架部分自己要监听的事件 + $this->initFramework(); } + /** + * 启动框架 + */ public function start() { - try { - self::$loaded_files = get_included_files(); - LightCacheInside::set('tmp_kv', 'start_time', microtime(true)); - self::$server->start(); - zm_atomic('server_is_stopped')->set(1); - if (!self::$argv['private-mode']) { - logger()->info('炸毛框架已停止!'); - } - } catch (Throwable $e) { - exit(zm_internal_errcode('E00011') . '框架发生未捕获的异常:' . get_class($e) . ': ' . $e->getMessage() . PHP_EOL); + // 对多进程有效,记录当前已经加载的所有文件,最后在 Worker 进程中比较可重载的文件,用于排错 + global $zm_loaded_files; + $zm_loaded_files = get_included_files(); + // 跑! + $this->driver->run(); + } + + /** + * 停止框架运行 + * + * 未测试 + */ + public function stop() + { + switch ($this->driver->getName()) { + case 'swoole': + /* @phpstan-ignore-next-line */ + Process::kill($this->driver->getSwooleServer()->master_pid, SIGTERM); + break; + case 'workerman': + Worker::stopAll(); + break; } } /** - * 打印 MOTD + * 重载框架的 worker 进程,重新加载模块及代码 + * + * 此方法仅限于 Unix 环境下的多进程模式(即存在 Worker 进程的模式)使用,Windows 环境、单进程模式使用无效 + * + * 未测试,需要对单进程等特殊情况做判断,因为单进程等模式无法重启 */ - private function printMotd(): void + public function reload() { - $tty_width = (new TablePrinter([]))->fetchTerminalSize(); - if (file_exists(DataProvider::getSourceRootDir() . '/config/motd.txt')) { - $motd = file_get_contents(DataProvider::getSourceRootDir() . '/config/motd.txt'); - } else { - $motd = file_get_contents(__DIR__ . '/../../config/motd.txt'); + switch ($this->driver->getName()) { + case 'swoole': + /* @phpstan-ignore-next-line */ + $this->driver->getSwooleServer()->reload(); + break; + case 'workerman': + Worker::reloadSelf(); + break; } - $motd = explode("\n", $motd); - foreach ($motd as $k => $v) { - $motd[$k] = substr($v, 0, $tty_width); + } + + /** + * 获取传入的参数 + */ + public function getArgv(): array + { + return $this->argv; + } + + /** + * 获取驱动 + * + * @return Driver|SwooleDriver|WorkermanDriver + */ + public function getDriver() + { + return $this->driver; + } + + /** + * 在框架的 Driver 层初始化前的一些前提条件 + * + * 大致分为初始化 config、解析一些命令行参数、初始化 Logger 等 + * + * @throws ConfigException + */ + private function initDriverPrerequisites() + { + // 寻找配置文件目录 + if ($this->argv['config-dir'] !== null) { // 如果启动参数指定了config寻找目录,那么就在指定的寻找,不在别的地方寻找了 + $find_dir = [$this->argv['config-dir']]; + logger()->debug('使用命令参数指定的config-dir:' . $this->argv['config-dir']); + } else { // 否则就从默认的工作目录或源码根目录寻找 + $find_dir = [WORKING_DIR . '/config', SOURCE_ROOT_DIR . '/config']; } - $motd = implode("\n", $motd); - echo $motd; + foreach ($find_dir as $v) { + if (is_dir($v)) { + ZMConfig::setDirectory($v); + ZMConfig::setEnv($this->argv['env'] ?? 'development'); + $config_done = true; + break; + } + } + // 找不到的话直接崩溃,因为框架依赖全局配置文件(但其实这个错误在 3.0 开始应该永远无法执行到) + if (!isset($config_done)) { + echo zm_internal_errcode('E00007') . 'Global config load failed' . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n"; + exit(1); + } + + // 初始化框架本体运行需要的常量,比如运行时间等 + require zm_dir(__DIR__ . '/../Globals/global_defines_framework.php'); + + // 初始化 Logger,此处为 Master 进程第一次初始化,在后续的多进程环境下,还需要在 Worker 进程中初始化 + if (!ob_logger_registered()) { // 如果没有注册过 Logger,那么就初始化一个,在启动框架前注册的话,就不会初始化了,可替换为其他 Logger + ob_logger_register(new ConsoleLogger($this->argv['log-level'] ?? 'info')); + } + + // 注册自己的EventProvider + global $ob_event_provider; + $ob_event_provider = EventProvider::getInstance(); + + // 初始化时区,默认为上海时区 + date_default_timezone_set(ZMConfig::get('global.runtime.timezone')); + + // 注册全局错误处理器 + set_error_handler(static function ($error_no, $error_msg, $error_file, $error_line) { + $tips = [ + E_WARNING => ['PHP Warning: ', 'warning'], + E_NOTICE => ['PHP Notice: ', 'notice'], + E_USER_ERROR => ['PHP Error: ', 'error'], + E_USER_WARNING => ['PHP Warning: ', 'warning'], + E_USER_NOTICE => ['PHP Notice: ', 'notice'], + E_STRICT => ['PHP Strict: ', 'notice'], + E_RECOVERABLE_ERROR => ['PHP Recoverable Error: ', 'error'], + E_DEPRECATED => ['PHP Deprecated: ', 'notice'], + E_USER_DEPRECATED => ['PHP User Deprecated: ', 'notice'], + ]; + $level_tip = $tips[$error_no] ?? ['PHP Unknown: ', 'error']; + $error = $level_tip[0] . $error_msg . ' in ' . $error_file . ' on ' . $error_line; + logger()->{$level_tip[1]}($error); + // 如果 return false 则错误会继续递交给 PHP 标准错误处理 + return true; + }, E_ALL | E_STRICT); + + // 解析命令行参数 + $this->parseArgs(); + + // 初始化 @OnSetup 事件 + $this->initSetupAnnotations(); + } + + /** + * 初始化驱动及相关事件 + * + * @throws Exception + */ + private function initDriver() + { + switch ($driver = ZMConfig::get('global.driver')) { + case 'swoole': + ZMConfig::$config['global']['swoole_options']['driver_init_policy'] = DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER; + $this->driver = new SwooleDriver(ZMConfig::get('global.swoole_options')); + $this->driver->initDriverProtocols(ZMConfig::get('global.servers')); + break; + case 'workerman': + ZMConfig::$config['global']['workerman_options']['driver_init_policy'] = DriverInitPolicy::MULTI_PROCESS_INIT_IN_MASTER; + $this->driver = new WorkermanDriver(ZMConfig::get('global.workerman_options')); + $this->driver->initDriverProtocols(ZMConfig::get('global.servers')); + break; + default: + logger()->error(zm_internal_errcode('E00081') . '未知的驱动类型 ' . $driver . ' !'); + exit(1); + } + } + + /** + * 初始化框架并输出一些信息 + * @throws ConfigException + */ + private function initFramework() + { + // private-mode 模式下,不输出任何内容 + if (!$this->argv['private-mode']) { + $this->printProperties(); + $this->printMotd(); + } + + // 添加框架需要监听的顶层事件监听器 + // worker 事件 + ob_event_provider()->addEventListener(WorkerStartEvent::getName(), [WorkerEventListener::getInstance(), 'onWorkerStart'], 999); + ob_event_provider()->addEventListener(WorkerStopEvent::getName(), [WorkerEventListener::getInstance(), 'onWorkerStop'], 999); + // Http 事件 + ob_event_provider()->addEventListener(HttpRequestEvent::getName(), [HttpEventListener::getInstance(), 'onRequest'], 999); + // manager 事件 + ob_event_provider()->addEventListener(ManagerStartEvent::getName(), [ManagerEventListener::getInstance(), 'onManagerStart'], 999); + ob_event_provider()->addEventListener(ManagerStopEvent::getName(), [ManagerEventListener::getInstance(), 'onManagerStop'], 999); + // master 事件 + ob_event_provider()->addEventListener(DriverInitEvent::getName(), [MasterEventListener::getInstance(), 'onMasterStart'], 999); + + // 框架多进程依赖 + if (defined('ZM_PID_DIR') && !is_dir(ZM_PID_DIR)) { + mkdir(ZM_PID_DIR); + } + } + + /** + * 打印属性表格 + * @noinspection PhpComposerExtensionStubsInspection + * @throws ConfigException + */ + private function printProperties(): void + { + $properties = []; + // 打印工作目录 + $properties['working_dir'] = WORKING_DIR; + // 打印环境信息 + $properties['environment'] = ($this->argv['env'] ?? null) === null ? 'default' : $this->argv['env']; + // 打印驱动 + $properties['driver'] = ZMConfig::get('global.driver'); + // 打印logger显示等级 + $properties['log_level'] = $this->argv['log-level'] ?? ZMConfig::get('global', 'log_level') ?? 'info'; + // 打印框架版本 + $properties['version'] = self::VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); + // 打印 PHP 版本 + $properties['php_version'] = PHP_VERSION; + // 非 Windows 操作系统打印 master 进程的 pid + if (PHP_OS_FAMILY !== 'Windows') { + $properties['master_pid'] = posix_getpid(); + } + // 打印进程模型 + if ($this->driver->getName() === 'swoole') { + $properties['process_mode'] = 'MST1'; + ProcessStateManager::$process_mode['master'] = 1; + if (ZMConfig::get('global.swoole_options.swoole_server_mode') === SWOOLE_BASE) { + $worker_num = ZMConfig::get('global.swoole_options.swoole_set.worker_num'); + if ($worker_num === null || $worker_num === 1) { + $properties['process_mode'] .= 'MAN0#0'; + ProcessStateManager::$process_mode['manager'] = 0; + ProcessStateManager::$process_mode['worker'] = 0; + } elseif ($worker_num === 0) { + $properties['process_mode'] .= 'MAN0#' . swoole_cpu_num(); + ProcessStateManager::$process_mode['manager'] = 0; + ProcessStateManager::$process_mode['worker'] = swoole_cpu_num(); + } else { + $properties['process_mode'] .= 'MAN0#' . ($worker = ZMConfig::get('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num()); + ProcessStateManager::$process_mode['manager'] = 0; + ProcessStateManager::$process_mode['worker'] = $worker; + } + } else { + $worker = ZMConfig::get('global.swoole_options.swoole_set.worker_num') === 0 ? swoole_cpu_num() : ZMConfig::get('global.swoole_options.swoole_set.worker_num') ?? swoole_cpu_num(); + $properties['process_mode'] .= 'MAN1#' . $worker; + ProcessStateManager::$process_mode['manager'] = 1; + ProcessStateManager::$process_mode['worker'] = $worker; + } + } elseif ($this->driver->getName() === 'workerman') { + $properties['process_mode'] = 'MST1'; + ProcessStateManager::$process_mode['master'] = 1; + $worker_num = ZMConfig::get('global.workerman_options.workerman_worker_num'); + if (DIRECTORY_SEPARATOR === '\\') { + $properties['process_mode'] .= '#0'; + ProcessStateManager::$process_mode['manager'] = 0; + ProcessStateManager::$process_mode['worker'] = 0; + } else { + $worker_num = $worker_num === 0 ? 1 : ($worker_num ?? 1); + $properties['process_mode'] .= '#' . $worker_num; + ProcessStateManager::$process_mode['manager'] = 0; + ProcessStateManager::$process_mode['worker'] = $worker_num; + } + } + // 打印监听端口 + foreach (ZMConfig::get('global.servers') as $k => $v) { + $properties['listen_' . $k] = $v['type'] . '://' . $v['host'] . ':' . $v['port']; + } + // 打印 MySQL 连接信息 + if ((ZMConfig::get('global.mysql_config.host') ?? '') !== '') { + $conf = ZMConfig::get('global', 'mysql_config'); + $properties['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port']; + } + // 打印 Redis 连接信息 + if ((ZMConfig::get('global', 'redis_config')['host'] ?? '') !== '') { + $conf = ZMConfig::get('global', 'redis_config'); + $properties['redis_pool'] = $conf['host'] . ':' . $conf['port']; + } + + if (LOAD_MODE === 0) { + logger()->info('框架正以源码模式启动'); + } + logger()->debug('Starting framework with properties: ' . json_encode($properties, JSON_UNESCAPED_SLASHES)); + $this->translateProperties($properties); + $printer = new TablePrinter($properties); + $printer->setValueColor('random')->printAll(); } /** @@ -387,7 +373,6 @@ class Framework { $translations = [ 'working_dir' => '工作目录', - 'listen' => '监听地址', 'worker' => '工作进程数', 'environment' => '环境类型', 'log_level' => '日志级别', @@ -398,26 +383,84 @@ class Framework 'mysql_pool' => '数据库', 'mysql' => '数据库', 'redis_pool' => 'Redis', - 'static_file_server' => '静态文件托管', 'php_version' => 'PHP 版本', - 'swoole_version' => 'Swoole 版本', + 'process_mode' => '进程模型', + 'driver' => '驱动类型', + 'listen_0' => '监听端口1', + 'listen_1' => '监听端口2', + 'listen_2' => '监听端口3', + 'listen_3' => '监听端口4', ]; + // 更换数组键名 foreach ($properties as $k => $v) { if (isset($translations[$k])) { - $this->changeArrayKey($properties, $k, $translations[$k]); + $keys = array_keys($properties); + $keys[array_search($k, $keys, false)] = $translations[$k]; + $properties = ($t = array_combine($keys, $properties)) ? $t : $properties; } } } - private function loadServerEvents() + /** + * 打印 MOTD + */ + private function printMotd(): void + { + // 先获取终端宽度,防止超过边界换行 + $tty_width = (new TablePrinter([]))->fetchTerminalSize(); + // 从源码目录、框架本身的初始目录寻找 MOTD 文件 + if (file_exists(SOURCE_ROOT_DIR . '/config/motd.txt')) { + $motd = file_get_contents(SOURCE_ROOT_DIR . '/config/motd.txt'); + } else { + $motd = file_get_contents(FRAMEWORK_ROOT_DIR . '/config/motd.txt'); + } + $motd = explode("\n", $motd); + foreach ($motd as $k => $v) { + $motd[$k] = substr($v, 0, $tty_width); + } + $motd = implode("\n", $motd); + echo $motd; + } + + /** + * 解析 argv 参数 + */ + private function parseArgs() + { + foreach ($this->argv as $x => $y) { + // 当值为 true/false 时,表示该参数为可选参数。当值为 null 时,表示该参数必定会有一个值,如果是 null,说明没指定 + if ($y === false || is_null($y)) { + continue; + } + switch ($x) { + case 'driver': // 动态设置驱动类型 + ZMConfig::$config['global']['driver'] = $y; + break; + case 'worker-num': // 动态设置 Worker 数量 + ZMConfig::$config['global']['swoole_options']['swoole_set']['worker_num'] = intval($y); + ZMConfig::$config['global']['workerman_options']['workerman_worker_num'] = intval($y); + break; + case 'daemon': // 启动为守护进程 + ZMConfig::$config['global']['swoole_options']['swoole_set']['daemonize'] = 1; + Worker::$daemonize = true; + break; + } + } + } + + /** + * 初始化 OnSetup 注解 + */ + private function initSetupAnnotations() { if (Phar::running() !== '') { - ob_start(); - include_once DataProvider::getFrameworkRootDir() . '/src/ZM/script_setup_loader.php'; - $r = ob_get_clean(); + // 在 Phar 下,不需要新启动进程了,因为 Phar 没办法重载,自然不需要考虑多进程的加载 reload 问题 + require FRAMEWORK_ROOT_DIR . '/src/Globals/script_setup_loader.php'; + $r = _zm_setup_loader(); $result_code = 0; } else { - $r = exec(PHP_BINARY . ' ' . DataProvider::getFrameworkRootDir() . '/src/ZM/script_setup_loader.php', $output, $result_code); + // 其他情况下,需要启动一个新的进程执行结果后退出内存中加载的内容,以便重载模块 + $r = exec(PHP_BINARY . ' ' . FRAMEWORK_ROOT_DIR . '/src/Globals/script_setup_loader.php', $output, $result_code); } if ($result_code !== 0) { logger()->emergency('代码解析错误!'); @@ -425,218 +468,16 @@ class Framework } $json = json_decode($r, true); if (!is_array($json)) { - logger()->error(zm_internal_errcode('E00012') . '解析 @SwooleHandler 及 @OnSetup 时发生错误,请检查代码!'); + logger()->error(zm_internal_errcode('E00012') . '解析 @OnSetup 时发生错误,请检查代码!'); + return; } - $this->setup_events = $json; - } - - /** - * 从全局配置文件里读取注入系统事件的类 - * - * @throws ReflectionException - * @throws ReflectionException - */ - private function registerServerEvents() - { - $reader = new AnnotationReader(); - $all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . '/src/ZM/Event/SwooleEvent/', 'ZM\\Event\\SwooleEvent'); - foreach ($all as $v) { - $class = new $v(); - $reflection_class = new ReflectionClass($class); - $anno_class = $reader->getClassAnnotation($reflection_class, SwooleHandler::class); - if ($anno_class !== null) { // 类名形式的注解 - $this->setup_events['event'][] = [ - 'class' => $v, - 'method' => 'onCall', - 'event' => $anno_class->event, - ]; - } - } - - foreach (($this->setup_events['setup'] ?? []) as $v) { + $this->setup_annotations = $json['setup']; + foreach (($this->setup_annotations) as $v) { logger()->debug('Calling @OnSetup: ' . $v['class']); - $c = ZMUtil::getModInstance($v['class']); + $cname = $v['class']; + $c = new $cname(); $method = $v['method']; $c->{$method}(); } - - foreach ($this->setup_events['event'] as $v) { - self::$server->on($v['event'], function (...$param) use ($v) { - ZMUtil::getModInstance($v['class'])->{$v['method']}(...$param); - }); - } - } - - /** - * 解析命令行的 $argv 参数 - * - * @param array $args 命令行参数 - * @throws InvalidArgumentException 参数不正确,框架需要捕获并终止启动 - * @return array 解析后并需要处理的参数 - */ - private function parseCliArgs(array $args, array $defaults): array - { - $coroutine_mode = true; - global $terminal_id; - $terminal_id = uuidgen(); - $error_occur = false; - $remote_terminal = $defaults['remote_terminal']; - - foreach ($args as $x => $y) { - if ($y === false || is_null($y)) { - continue; - } - switch ($x) { - case 'worker-num': - if ((int) $y >= 1 && (int) $y <= 1024) { - $this->swoole_server_config['worker_num'] = (int) $y; - } else { - logger()->emergency(zm_internal_errcode('E00013') . '传入的 worker_num 参数不合法!必须在 1-1024 之间!'); - $error_occur = true; - } - break; - case 'task-worker-num': - if ((int) $y >= 1 && (int) $y <= 1024) { - $this->swoole_server_config['task_worker_num'] = (int) $y; - $this->swoole_server_config['task_enable_coroutine'] = true; - } else { - logger()->emergency(zm_internal_errcode('E00013') . '传入的 task_worker_num 参数不合法!必须在 1-1024 之间!'); - $error_occur = true; - } - break; - case 'disable-coroutine': - $coroutine_mode = false; - break; - case 'debug-mode': - self::$argv['disable-safe-exit'] = true; - $coroutine_mode = false; - $terminal_id = null; - self::$argv['watch'] = true; - logger()->notice('已进入调试模式,请勿在生产环境中使用!'); - break; - case 'daemon': - $this->swoole_server_config['daemonize'] = 1; - Console::$theme = 'no-color'; - Console::log('已启用守护进程,输出重定向到 ' . $this->swoole_server_config['log_file']); - $terminal_id = null; - break; - case 'disable-console-input': - case 'no-interaction': - $terminal_id = null; - break; - case 'log-error': - Console::setLevel(0); - break; - case 'log-warning': - Console::setLevel(1); - break; - case 'log-info': - Console::setLevel(2); - break; - case 'log-verbose': - case 'verbose': - Console::setLevel(3); - break; - case 'log-debug': - Console::setLevel(4); - break; - case 'audit-mode': - logger()->notice('审计模式已开启,请正常执行需要审计的流程,然后Ctrl+C正常结束框架'); - logger()->notice('审计的日志文件将存放到:' . DataProvider::getWorkingDir() . '/audit.log'); - if (file_exists(DataProvider::getWorkingDir() . '/audit.log')) { - unlink(DataProvider::getWorkingDir() . '/audit.log'); - } - logger()->notice('框架将于5秒后开始启动...'); - Console::setOutputFile(DataProvider::getWorkingDir() . '/audit.log'); - Console::setLevel(4); - sleep(5); - break; - case 'log-theme': - Console::$theme = $y; - break; - case 'remote-terminal': - $remote_terminal = true; - break; - case 'show-php-ver': - default: - break; - } - } - if ($error_occur) { - throw new InvalidArgumentException('命令行参数解析错误,请参阅上方日志!'); - } - return [$coroutine_mode, $terminal_id, $remote_terminal]; - } - - /** - * 更换数组键名 - * - * @param mixed $old_key - * @param mixed $new_key - */ - private function changeArrayKey(array &$arr, $old_key, $new_key): void - { - $keys = array_keys($arr); - $keys[array_search($old_key, $keys, false)] = $new_key; - $arr = ($t = array_combine($keys, $arr)) ? $t : $arr; - } - - /** - * 打印属性表格 - */ - private function printProperties(bool $remote_terminal, array $args): void - { - $properties = []; - $properties['working_dir'] = DataProvider::getWorkingDir(); - $properties['listen'] = ZMConfig::get('global', 'host') . ':' . ZMConfig::get('global', 'port'); - if (!isset($this->swoole_server_config['worker_num'])) { - if ((ZMConfig::get('global', 'runtime')['swoole_server_mode'] ?? SWOOLE_PROCESS) === SWOOLE_PROCESS) { - $properties['worker'] = swoole_cpu_num() . ' (auto)'; - } else { - $properties['single_proc_mode'] = 'true'; - } - } else { - $properties['worker'] = $this->swoole_server_config['worker_num']; - } - $properties['environment'] = ($args['env'] ?? null) === null ? 'default' : $args['env']; - $properties['log_level'] = strtoupper(zm_config('logging.level')); - $properties['version'] = self::VERSION . (LOAD_MODE === 0 ? (' (build ' . ZM_VERSION_ID . ')') : ''); - $properties['master_pid'] = posix_getpid(); - if (APP_VERSION !== 'unknown') { - $properties['app_version'] = APP_VERSION; - } - if (isset($this->swoole_server_config['task_worker_num'])) { - $properties['task_worker'] = $this->swoole_server_config['task_worker_num']; - } - if ((ZMConfig::get('global', 'sql_config')['sql_host'] ?? '') !== '') { - $conf = ZMConfig::get('global', 'sql_config'); - $properties['mysql_pool'] = $conf['sql_database'] . '@' . $conf['sql_host'] . ':' . $conf['sql_port']; - } - if ((ZMConfig::get('global', 'mysql_config')['host'] ?? '') !== '') { - $conf = ZMConfig::get('global', 'mysql_config'); - $properties['mysql'] = $conf['dbname'] . '@' . $conf['host'] . ':' . $conf['port']; - } - if (ZMConfig::get('global', 'redis_config')['host'] !== '') { - $conf = ZMConfig::get('global', 'redis_config'); - $properties['redis_pool'] = $conf['host'] . ':' . $conf['port']; - } - if (ZMConfig::get('global', 'static_file_server')['status'] !== false) { - $properties['static_file_server'] = 'enabled'; - } - if (self::$argv['show-php-ver'] !== false) { - $properties['php_version'] = PHP_VERSION; - $properties['swoole_version'] = SWOOLE_VERSION; - } - - if ($remote_terminal) { - $conf = ZMConfig::get('global', 'remote_terminal'); - $properties['terminal'] = $conf['host'] . ':' . $conf['port']; - } - if (LOAD_MODE === 0) { - logger()->info('框架正以源码模式启动'); - } - $this->translateProperties($properties); - $printer = new TablePrinter($properties); - $printer->setValueColor('random')->printAll(); } } diff --git a/src/ZM/Http/MiddlewareInterface.php b/src/ZM/Http/MiddlewareInterface.php deleted file mode 100644 index 8d2e3313..00000000 --- a/src/ZM/Http/MiddlewareInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -response = $response; - $this->fd = $response->fd; - $this->socket = $response->socket; - $this->header = $response->header; - $this->cookie = $response->cookie; - if (isset($response->trailer)) { - $this->trailer = $response->trailer; - } - } - - public function __destruct() - { - } - - /** - * @return mixed - */ - public function initHeader() - { - return $this->response->initHeader(); - } - - /** - * @param string $name 名称 - * @param mixed ...$params 参数 - * @return mixed 返回值 - */ - public function cookie(string $name, ...$params) - { - return empty($params) ? $this->response->rawcookie($name) : $this->response->rawcookie($name, ...$params); - } - - /** - * @param mixed ...$params - * @param mixed $name - * @return mixed - */ - public function setCookie($name, ...$params) - { - return empty($params) ? $this->response->rawcookie($name) : $this->response->setCookie($name, ...$params); - } - - /** - * @param mixed ...$params - * @param mixed $name - * @return mixed - */ - public function rawcookie($name, ...$params) - { - return empty($params) ? $this->response->rawcookie($name) : $this->response->rawcookie($name, ...$params); - } - - /** - * @param mixed ...$params - * @return mixed - */ - public function status(int $http_code, ...$params) - { - $this->status_code = $http_code; - if (!$this->is_end) { - return empty($params) ? $this->response->status($http_code) : $this->response->status($http_code, ...$params); - } - return false; - } - - public function getStatusCode() - { - return $this->status_code ?? 200; - } - - /** - * @param mixed ...$params - * @return mixed - */ - public function setStatusCode(int $http_code, ...$params) - { - if (!$this->is_end) { - return empty($params) ? $this->response->status($http_code) : $this->response->status($http_code, ...$params); - } - return false; - } - - /** - * @param array|string $value - * @param null|array|string $ucwords - * @return mixed - */ - public function header(string $key, $value, $ucwords = null) - { - if (!$this->is_end) { - return $ucwords === null ? $this->response->header($key, $value) : $this->response->header($key, $value, $ucwords); - } - return false; - } - - /** - * @param array|string $value - * @param null|array|string $ucwords - * @return mixed - */ - public function setHeader(string $key, $value, $ucwords = null) - { - if (!$this->is_end) { - return $ucwords === null ? $this->response->setHeader($key, $value) : $this->response->setHeader($key, $value, $ucwords); - } - return false; - } - - /** - * @param array|string $value - * @return mixed - */ - public function trailer(string $key, $value) - { - return $this->response->trailer($key, $value); - } - - /** - * @return mixed - */ - public function ping() - { - return $this->response->ping(); - } - - /** - * @param string|Stringable $content - * @return mixed - */ - public function write($content) - { - return $this->response->write($content); - } - - /** - * @param null|string|Stringable $content - * @return mixed - */ - public function end($content = null) - { - if (!$this->is_end) { - $this->is_end = true; - return $this->response->end($content); - } - return false; - } - - public function isEnd() - { - return $this->is_end; - } - - public function endWithStatus($status_code = 200, $content = null) - { - $this->status($status_code); - $this->end($content); - } - - /** - * @param null|int|string $offset - * @param null|int|string $length - * @return mixed - */ - public function sendfile(string $filename, $offset = null, $length = null) - { - return $this->response->sendfile($filename, $offset, $length); - } - - /** - * @return mixed - */ - public function redirect(string $location, ?int $http_code = null) - { - $this->is_end = true; - return $this->response->redirect($location, $http_code); - } - - /** - * @return mixed - */ - public function detach() - { - return $this->response->detach(); - } - - /** - * @param mixed $fd - * @return mixed - */ - public static function create($fd) - { - return \Swoole\Http\Response::create($fd); - } - - /** - * @return mixed - */ - public function upgrade() - { - return $this->response->upgrade(); - } - - /** - * @param mixed $data - * @param mixed $opcode - * @param mixed $flags - * @return mixed - */ - public function push($data, $opcode = null, $flags = null) - { - return $this->response->push($data, $opcode, $flags); - } - - /** - * @return mixed - */ - public function recv() - { - return $this->response->recv(); - } - - /** - * @return mixed - */ - public function close() - { - return $this->response->close(); - } -} diff --git a/src/ZM/Http/RouteManager.php b/src/ZM/Http/RouteManager.php deleted file mode 100644 index 549aef46..00000000 --- a/src/ZM/Http/RouteManager.php +++ /dev/null @@ -1,14 +0,0 @@ -getResponse(); - logger()->debug('Full path: ' . $full_path); - if ($full_path !== false) { - if (strpos($full_path, $path) !== 0) { - $response->status(403); - $response->end('403 Forbidden'); - return; - } - if (is_file($full_path)) { - $exp = strtolower(pathinfo($full_path)['extension'] ?? 'unknown'); - $response->setHeader('Content-Type', ZMConfig::get('file_header')[$exp] ?? 'application/octet-stream'); - $response->end(file_get_contents($full_path)); - return; - } - } - $response->status(404); - $response->end(HttpUtil::getHttpCodePage(404)); - } -} diff --git a/src/ZM/Module/InstantModule.php b/src/ZM/Module/InstantModule.php deleted file mode 100644 index 4f4ec5f6..00000000 --- a/src/ZM/Module/InstantModule.php +++ /dev/null @@ -1,23 +0,0 @@ - $v) { - if (is_string($k)) { - $class->{$k} = $v; - } - } - $class->method = $callable; - $this->events[] = $class; - } -} diff --git a/src/ZM/Module/ModuleBase.php b/src/ZM/Module/ModuleBase.php deleted file mode 100644 index 1a64e017..00000000 --- a/src/ZM/Module/ModuleBase.php +++ /dev/null @@ -1,42 +0,0 @@ -module_name = $module_name; - } - - /** - * 获取模块名称 - * @return string - */ - public function getModuleName() - { - return $this->module_name; - } - - /** - * 获取事件列表 - */ - public function getEvents(): array - { - return $this->events; - } -} diff --git a/src/ZM/Module/ModulePacker.php b/src/ZM/Module/ModulePacker.php deleted file mode 100644 index f83c91bd..00000000 --- a/src/ZM/Module/ModulePacker.php +++ /dev/null @@ -1,261 +0,0 @@ -module = $module; - } - - /** - * 设置输出文件夹 - * @param string $path 输出路径 - */ - public function setOutputPath(string $path) - { - $this->output_path = $path; - if (!is_dir($path)) { - mkdir($path, 0755, true); - } - } - - /** - * 设置是否覆盖 - */ - public function setOverride(bool $override = true) - { - $this->override = $override; - } - - /** - * 获取模块名字 - * @return mixed - */ - public function getName() - { - return $this->module['name']; - } - - /** - * 获取打包的文件名绝对路径 - */ - public function getFileName(): string - { - return $this->filename; - } - - /** - * 打包模块 - * @throws ZMException - */ - public function pack() - { - $this->filename = $this->output_path . '/' . $this->module['name']; - if (isset($this->module['version'])) { - $this->filename .= '_' . $this->module['version']; - } - $this->filename .= '.phar'; - if ($this->override) { - if (file_exists($this->filename)) { - logger()->info('Overwriting ' . $this->filename); - unlink($this->filename); - } - } - - $this->phar = new Phar($this->filename); - $this->phar->startBuffering(); - logger()->info('模块输出文件:' . $this->filename); - - $this->addFiles(); // 添加文件 - $this->addLightCacheStore(); // 保存light-cache-store指定的项 - $this->addModuleConfig(); // 生成module-config.json - $this->addZMDataFiles(); // 添加需要保存的zm_data下的目录或文件 - $this->addEntry(); // 生成模块的入口文件module_entry.php - - $this->phar->stopBuffering(); - } - - private function addFiles() - { - $file_list = DataProvider::scanDirFiles($this->module['module-path'], true, false); - foreach ($file_list as $v) { - $this->phar->addFile($v, $this->getRelativePath($v)); - } - } - - private function getRelativePath($path) - { - return str_replace(realpath(DataProvider::getSourceRootDir()) . '/', '', realpath($path)); - } - - private function generatePharAutoload(): array - { - $pos = strpos($this->module['module-path'], DataProvider::getSourceRootDir() . '/'); - if ($pos === 0) { - $path_value = substr($this->module['module-path'], strlen(DataProvider::getSourceRootDir() . '/')); - } else { - throw new ModulePackException(zm_internal_errcode('E99999')); // 未定义的错误 - } - return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, $path_value); - } - - private function getComposerAutoloadItems(): array - { - $composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true); - $path = self::getRelativePath($this->module['module-path']); - $item = []; - foreach (($composer['autoload']['psr-4'] ?? []) as $k => $v) { - if (strpos($path, $v) === 0) { - $item['psr-4'][$k] = $v; - } - } - foreach (($composer['autoload']['files'] ?? []) as $v) { - if (strpos($v, $path) === 0) { - $item['files'][] = $v; - } - } - return $item; - } - - /** - * @throws ZMException - * @throws Exception - */ - private function addLightCacheStore() - { - if (isset($this->module['light-cache-store'])) { - $store = []; - $r = ZMConfig::get('global', 'light_cache') ?? [ - 'size' => 512, // 最多允许储存的条数(需要2的倍数) - 'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数) - 'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多) - 'persistence_path' => DataProvider::getDataFolder() . '_cache.json', - 'auto_save_interval' => 900, - ]; - LightCache::init($r); - foreach ($this->module['light-cache-store'] as $v) { - $r = LightCache::get($v); - if ($r === null) { - logger()->warning(zm_internal_errcode('E00045') . 'LightCache 项:' . $v . ' 不存在或值为null,无法为其保存。'); - } else { - $store[$v] = $r; - logger()->info('打包LightCache持久化项:' . $v); - } - } - $this->phar->addFromString('light_cache_store.json', json_encode($store, 128 | 256)); - } - } - - private function addModuleConfig() - { - $stub_values = [ - 'zm-module' => true, - 'generated-id' => sha1(strval(microtime(true))), - 'module-packer-version' => self::ZM_MODULE_PACKER_VERSION, - 'module-root-path' => $this->getRelativePath($this->module['module-path']), - 'namespace' => $this->module['namespace'], - 'hotload-psr-4' => $this->generatePharAutoload(), - 'unpack' => [ - 'composer-autoload-items' => $this->getComposerAutoloadItems(), - 'global-config-override' => $this->module['global-config-override'] ?? false, - ], - 'allow-hotload' => $this->module['allow-hotload'] ?? false, - 'pack-time' => time(), - ]; - if (isset($stub_values['unpack']['composer-autoload-items']['files'])) { - $stub_values['hotload-files'] = $stub_values['unpack']['composer-autoload-items']['files']; - } - $this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - $this->module_config = $stub_values; - } - - private function addEntry() - { - $stub_replace = [ - 'generated_id' => $this->module_config['generated-id'], - ]; - - $stub_template = str_replace( - array_map(function ($x) { - return '__' . $x . '__'; - }, array_keys($stub_replace)), - array_values($stub_replace), - file_get_contents(DataProvider::getFrameworkRootDir() . '/src/ZM/script_phar_stub.php') - ); - $this->phar->addFromString('module_entry.php', $stub_template); - - $this->phar->setStub($this->phar->createDefaultStub('module_entry.php')); - } - - /** - * @throws ModulePackException - */ - private function addZMDataFiles() - { - $base_dir = realpath(DataProvider::getDataFolder()); - if (is_array($this->module['zm-data-store'] ?? null)) { - foreach ($this->module['zm-data-store'] as $v) { - if (is_dir($base_dir . '/' . $v)) { - $v = rtrim($v, '/'); - logger()->info('Adding external zm_data dir: ' . $v); - $files = DataProvider::scanDirFiles($base_dir . '/' . $v, true, true); - foreach ($files as $single) { - $this->phar->addFile($base_dir . '/' . $v . '/' . $single, 'zm_data/' . $v . '/' . $single); - } - } elseif (is_file($base_dir . '/' . $v)) { - logger()->info('Add external zm_data file: ' . $v); - $this->phar->addFile($base_dir . '/' . $v, 'zm_data/' . $v); - } else { - throw new ModulePackException(zm_internal_errcode('E00066') . '`zmdata-store` 指定的文件或目录不存在'); - } - } - } - } -} diff --git a/src/ZM/Module/ModuleUnpacker.php b/src/ZM/Module/ModuleUnpacker.php deleted file mode 100644 index 875e19ab..00000000 --- a/src/ZM/Module/ModuleUnpacker.php +++ /dev/null @@ -1,242 +0,0 @@ -module = $module; - } - - /** - * 解包模块 - * - * @param mixed $ignore_depends - * @throws ModulePackException - * @throws ZMException - */ - public function unpack(bool $override_light_cache = false, bool $override_data_files = false, bool $override_source = false, $ignore_depends = false): array - { - $this->checkConfig(); - $this->checkDepends($ignore_depends); - $this->checkLightCacheStore(); - $this->checkZMDataStore(); - - $this->mergeComposer(); - $this->copyZMDataStore($override_data_files); - $this->copyLightCacheStore($override_light_cache); - - $this->mergeGlobalConfig(); - $this->copySource($override_source); - return $this->module; - } - - /** - * 检查模块配置文件是否正确地放在phar包的位置中 - */ - private function checkConfig() - { - $config = 'phar://' . $this->module['phar-path'] . '/' . $this->module['module-root-path'] . '/zm.json'; - $this->module_config = json_decode(file_get_contents($config), true); - } - - /** - * 检查模块依赖关系 - * - * @param mixed $ignore_depends - * @throws ModulePackException - * @throws ZMException - */ - private function checkDepends($ignore_depends = false) - { - $configured = ModuleManager::getConfiguredModules(); - $depends = $this->module_config['depends'] ?? []; - foreach ($depends as $k => $v) { - if (!isset($configured[$k]) && !$ignore_depends) { - throw new ModulePackException(zm_internal_errcode('E00064') . '模块 ' . $this->module_config['name'] . " 依赖的模块 {$k} 不存在"); - } - $current_ver = $configured[$k]['version'] ?? '1.0'; - if (!VersionComparator::compareVersionRange($current_ver, $v) && !$ignore_depends) { - throw new ModulePackException(zm_internal_errcode('E00063') . '模块 ' . $this->module_config['name'] . " 依赖的模块 {$k} 版本依赖不符合条件(现有版本: " . $current_ver . ', 需求版本: ' . $v . ')'); - } - } - } - - /** - * 检查 light-cache-store 项是否合规 - * @throws ModulePackException - */ - private function checkLightCacheStore() - { - if (isset($this->module_config['light-cache-store'])) { - $file = json_decode(file_get_contents('phar://' . $this->module['phar-path'] . '/light_cache_store.json'), true); - if ($file === null) { - throw new ModulePackException(zm_internal_errcode('E00065') . '模块系统检测到打包的模块文件中未含有 `light_cache_store.json` 文件'); - } - $this->light_cache = $file; - } - } - - /** - * @throws ModulePackException - */ - private function checkZMDataStore() - { - if (is_array($this->module_config['zm-data-store'] ?? null)) { - foreach ($this->module_config['zm-data-store'] as $v) { - if (!file_exists('phar://' . $this->module['phar-path'] . '/zm_data/' . $v)) { - throw new ModulePackException(zm_internal_errcode('E00067') . '压缩包损坏,内部找不到待解压的 zm_data 原始数据'); - } - $file = 'phar://' . $this->module['phar-path'] . '/zm_data/' . $v; - if (is_dir($file)) { - $all = DataProvider::scanDirFiles($file, true, true); - foreach ($all as $single) { - $this->unpack_data_files[$file . '/' . $single] = DataProvider::getDataFolder() . $v . '/' . $single; - } - } else { - $this->unpack_data_files[$file] = DataProvider::getDataFolder() . $v; - } - } - } - } - - private function mergeComposer() - { - $composer_file = DataProvider::getWorkingDir() . '/composer.json'; - if (!file_exists($composer_file)) { - throw new ModulePackException(zm_internal_errcode('E00068')); - } - $composer = json_decode(file_get_contents($composer_file), true); - if (isset($this->module_config['unpack']['composer-autoload-items'])) { - $autoload = $this->module_config['unpack']['composer-autoload-items']; - if (isset($autoload['psr-4'])) { - logger()->info('Adding extended autoload psr-4 for composer'); - $composer['autoload']['psr-4'] = isset($composer['autoload']['psr-4']) ? array_merge($composer['autoload']['psr-4'], $autoload['psr-4']) : $autoload['psr-4']; - } - if (isset($autoload['files'])) { - logger()->info('Adding extended autoload file for composer'); - $composer['autoload']['files'] = isset($composer['autoload']['files']) ? array_merge($composer['autoload']['files'], $autoload['files']) : $autoload['files']; - } - } - - if (isset($this->module_config['composer-extend-require'])) { - foreach ($this->module_config['composer-extend-require'] as $k => $v) { - logger()->info('Adding extended required composer library: ' . $k); - if (!isset($composer[$k])) { - $composer[$k] = $v; - } - } - } - file_put_contents($composer_file, json_encode($composer, 64 | 128 | 256)); - } - - /** - * @param mixed $override_data - * @throws ModulePackException - */ - private function copyZMDataStore($override_data) - { - foreach ($this->unpack_data_files as $k => $v) { - $pathinfo = pathinfo($v); - if (!is_dir($pathinfo['dirname'])) { - @mkdir($pathinfo['dirname'], 0755, true); - } - if (is_file($v) && $override_data !== true) { - logger()->info('Skipping zm_data file (not overwriting): ' . $v); - continue; - } - logger()->info('Copying zm_data file: ' . $v); - if (copy($k, $v) !== true) { - throw new ModulePackException(zm_internal_errcode('E00068') . 'Cannot copy file: ' . $v); - } - } - } - - private function copyLightCacheStore($override) - { - $r = ZMConfig::get('global', 'light_cache') ?? [ - 'size' => 512, // 最多允许储存的条数(需要2的倍数) - 'max_strlen' => 32768, // 单行字符串最大长度(需要2的倍数) - 'hash_conflict_proportion' => 0.6, // Hash冲突率(越大越好,但是需要的内存更多) - 'persistence_path' => DataProvider::getDataFolder() . '_cache.json', - 'auto_save_interval' => 900, - ]; - LightCache::init($r); - foreach (($this->light_cache ?? []) as $k => $v) { - if (LightCache::isset($k) && $override !== true) { - continue; - } - LightCache::addPersistence($k); - LightCache::set($k, $v); - } - } - - private function mergeGlobalConfig() - { - if ($this->module['unpack']['global-config-override'] !== false) { - $prompt = !is_string($this->module['unpack']['global-config-override']) ? '请根据模块提供者提供的要求进行修改 global.php 中对应的配置项' : $this->module['unpack']['global-config-override']; - logger()->warning('模块作者要求用户手动修改 global.php 配置文件中的项目:'); - logger()->warning('*' . $prompt); - if (STDIN === false) { // @phpstan-ignore-line - logger()->warning('检测到终端无法输入,请手动修改 global.php 配置文件中的项目'); - return; - } - logger()->notice('请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N] '); - $r = strtolower(trim(fgets(STDIN))); - switch ($r) { - case 'y': - system('vim ' . escapeshellarg(DataProvider::getWorkingDir() . '/config/global.php') . ' > `tty`'); - logger()->info('已使用 vim 修改!'); - break; - case 'e': - logger()->notice('请修改后文件点击回车即可继续 [Enter] '); - fgets(STDIN); - break; - case 'n': - logger()->info('暂不修改 global.php'); - break; - } - } - } - - private function copySource(bool $override_source) - { - $origin_base = 'phar://' . $this->module['phar-path'] . '/' . $this->module['module-root-path']; - $dir = DataProvider::scanDirFiles($origin_base, true, true); - $base = DataProvider::getSourceRootDir() . '/' . $this->module['module-root-path']; - foreach ($dir as $v) { - $info = pathinfo($base . '/' . $v); - if (!is_dir($info['dirname'])) { - @mkdir($info['dirname'], 0755, true); - } - if (is_file($base . '/' . $v) && $override_source !== true) { - logger()->info('Skipping source file (not overwriting): ' . $v); - continue; - } - logger()->info('Releasing source file: ' . $this->module['module-root-path'] . '/' . $v); - - if (copy($origin_base . '/' . $v, $base . '/' . $v) !== true) { - throw new ModulePackException(zm_internal_errcode('E00068') . 'Cannot copy file: ' . $v); - } - } - } -} diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php deleted file mode 100644 index f445ae6d..00000000 --- a/src/ZM/Module/QQBot.php +++ /dev/null @@ -1,264 +0,0 @@ -getFrame()->data, true); - $this->handle($data); - } - - /** - * @param array|Iterator $data 数据包 - * @param int $level 递归等级 - * @throws InterruptException - * @throws ZMKnownException - * @throws Exception - */ - public function handle($data, int $level = 0) - { - try { - if ($level > 10) { - return; - } - set_coroutine_params(['data' => $data]); - if (isset($data['post_type'])) { - // echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL; - ctx()->setCache('level', $level); - // logger()->debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd()); - if ($data['post_type'] != 'meta_event') { - $r = $this->dispatchBeforeEvents($data, 'pre'); // before在这里执行,元事件不执行before为减少不必要的调试日志 - if ($r->store === 'block') { - EventDispatcher::interrupt(); - } - } - // Console::warning("最上数据包:".json_encode($data)); - } - if (isset($data['echo']) || isset($data['post_type'])) { - if (CoMessage::resumeByWS()) { - EventDispatcher::interrupt(); - } - } - if (($data['post_type'] ?? 'meta_event') != 'meta_event') { - $r = $this->dispatchBeforeEvents($data, 'post'); // before在这里执行,元事件不执行before为减少不必要的调试日志 - if ($r->store === 'block') { - EventDispatcher::interrupt(); - } - } - if (isset($data['post_type'])) { - $this->dispatchEvents($data); - } else { - $this->dispatchAPIResponse($data); - } - - if (($data['post_type'] ?? 'meta_event') != 'meta_event') { - $r = $this->dispatchAfterEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志 - if ($r->store === 'block') { - EventDispatcher::interrupt(); - } - } - } /* @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) { - if (($data['post_type'] ?? 'meta_event') != 'meta_event') { - $r = $this->dispatchAfterEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志 - if ($r->store === 'block') { - EventDispatcher::interrupt(); - } - } - $e->module->finalReply($e->getMessage()); - } catch (InterruptException $e) { - if (($data['post_type'] ?? 'meta_event') != 'meta_event') { - $r = $this->dispatchAfterEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志 - if ($r->store === 'block') { - EventDispatcher::interrupt(); - } - } - throw $e; - } - // 这里修复CQAfter不能使用的问题,我竟然一直没写,绝了 - } - - /** - * @param array|Iterator $data 数据包 - * @param string $time 类型(pre或post) - * @throws Exception - */ - public function dispatchBeforeEvents($data, string $time): EventDispatcher - { - $before = new EventDispatcher(CQBefore::class); - if ($time === 'pre') { - $before->setRuleFunction(function ($v) use ($data) { - return $v->level >= 200 && $v->cq_event == $data['post_type']; - }); - } else { - $before->setRuleFunction(function ($v) use ($data) { - return $v->level < 200 && $v->cq_event == $data['post_type']; - }); - } - $before->setReturnFunction(function ($result) { - if (!$result) { - EventDispatcher::interrupt('block'); - } - }); - $before->dispatchEvents($data); - return $before; - } - - /** - * @param array|Iterator $data 数据包 - * @throws InterruptException - * @throws Exception - */ - private function dispatchEvents($data) - { - // Console::warning("最xia数据包:".json_encode($data)); - switch ($data['post_type']) { - case 'message': - // 分发CQCommand事件 - $dispatcher = new EventDispatcher(CQCommand::class); - $dispatcher->setReturnFunction(function ($result) { - if (is_string($result)) { - ctx()->reply($result); - } - if (ctx()->getCache('has_reply') === true) { - EventDispatcher::interrupt(); - } - }); - $msg = $data['message']; - if (is_array($msg)) { - $msg = MessageUtil::arrayToStr($msg); - } - $s = MessageUtil::matchCommand($msg, ctx()->getData()); - if ($s->status !== false) { - $match = $s->match; - - $input_arguments = MessageUtil::checkArguments($s->object->class, $s->object->method, $match); - if (!empty($match)) { - ctx()->setCache('match', $match); - } - $dispatcher->dispatchEvent($s->object, null, ...$input_arguments); - if (is_string($dispatcher->store)) { - ctx()->reply($dispatcher->store); - } - if (ctx()->getCache('has_reply') === true) { - $policy = ZMConfig::get('global', 'onebot')['message_command_policy'] ?? 'interrupt'; - switch ($policy) { - case 'interrupt': - EventDispatcher::interrupt(); - break; - case 'continue': - break; - default: - throw new Exception('未知的消息命令策略:' . $policy); - } - } - } - - // 分发CQMessage事件 - $msg_dispatcher = new EventDispatcher(CQMessage::class); - $msg_dispatcher->setRuleFunction(function ($v) { - return ($v->message == '' || ($v->message == ctx()->getMessage())) - && ($v->user_id == 0 || ($v->user_id == ctx()->getUserId())) - && ($v->group_id == 0 || ($v->group_id == (ctx()->getGroupId() ?? 0))) - && ($v->message_type == '' || ($v->message_type == ctx()->getMessageType())) - && ($v->raw_message == '' || ($v->raw_message == context()->getData()['raw_message'])); - }); - $msg_dispatcher->setReturnFunction(function ($result) { - if (is_string($result)) { - ctx()->reply($result); - } - }); - $msg_dispatcher->dispatchEvents(ctx()->getMessage()); - return; - case 'meta_event': - // Console::success("当前数据包:".json_encode(ctx()->getData())); - $dispatcher = new EventDispatcher(CQMetaEvent::class); - $dispatcher->setRuleFunction(function (CQMetaEvent $v) { - return $v->meta_event_type == '' || ($v->meta_event_type == ctx()->getData()['meta_event_type']); - }); - // eval(BP); - $dispatcher->dispatchEvents(ctx()->getData()); - return; - case 'notice': - $dispatcher = new EventDispatcher(CQNotice::class); - $dispatcher->setRuleFunction(function (CQNotice $v) { - return - ($v->notice_type == '' || ($v->notice_type == ctx()->getData()['notice_type'])) - && ($v->sub_type == '' || ($v->sub_type == ctx()->getData()['sub_type'])) - && ($v->group_id == '' || ($v->group_id == ctx()->getData()['group_id'])) - && ($v->operator_id == '' || ($v->operator_id == ctx()->getData()['operator_id'])); - }); - $dispatcher->dispatchEvents(ctx()->getData()); - return; - case 'request': - $dispatcher = new EventDispatcher(CQRequest::class); - $dispatcher->setRuleFunction(function (CQRequest $v) { - return ($v->request_type == '' || ($v->request_type == ctx()->getData()['request_type'])) - && ($v->sub_type == '' || ($v->sub_type == ctx()->getData()['sub_type'])) - && ($v->user_id == 0 || ($v->user_id == ctx()->getData()['user_id'])) - && ($v->comment == '' || ($v->comment == ctx()->getData()['comment'])); - }); - $dispatcher->dispatchEvents(ctx()->getData()); - return; - } - } - - /** - * @param mixed $data 分发事件数据包 - * @throws Exception - */ - private function dispatchAfterEvents($data): EventDispatcher - { - $after = new EventDispatcher(CQAfter::class); - $after->setRuleFunction(function ($v) use ($data) { - return $v->cq_event == $data['post_type']; - }); - $after->dispatchEvents($data); - return $after; - } - - /** - * @param mixed $req - * @throws Exception - */ - private function dispatchAPIResponse($req) - { - set_coroutine_params(['cq_response' => $req]); - $dispatcher = new EventDispatcher(CQAPIResponse::class); - $dispatcher->setRuleFunction(function (CQAPIResponse $response) { - return $response->retcode == ctx()->getCQResponse()['retcode']; - }); - $dispatcher->dispatchEvents($req); - } -} diff --git a/src/ZM/MySQL/MySQLConnection.php b/src/ZM/MySQL/MySQLConnection.php deleted file mode 100644 index 7ea3dead..00000000 --- a/src/ZM/MySQL/MySQLConnection.php +++ /dev/null @@ -1,123 +0,0 @@ -debug('Constructing...'); - $this->conn = SqlPoolStorage::$sql_pool->getConnection(); - } - - public function __destruct() - { - logger()->debug('Destructing!!!'); - SqlPoolStorage::$sql_pool->putConnection($this->conn); - } - - /** - * @param mixed $sql - * @param mixed $options - * @throws DbException - */ - public function prepare($sql, $options = []) - { - try { - logger()->debug('Running SQL prepare: ' . $sql); - $statement = $this->conn->prepare($sql, $options); - assert($statement !== false); - } catch (PDOException $exception) { - throw new DbException($exception->getMessage(), $exception->getCode(), $exception); - } - return new MySQLStatement($statement); - } - - /** - * @throws DbException - */ - public function query(...$args) - { - try { - $statement = $this->conn->query(...$args); - assert($statement !== false); - } catch (PDOException $exception) { - throw new DbException($exception->getMessage(), $exception->getCode(), $exception); - } - return new MySQLStatement($statement); - } - - public function quote($value, $type = ParameterType::STRING) - { - return $this->conn->quote($value, $type); - } - - /** - * @param mixed $sql - * @throws DbException - */ - public function exec($sql) - { - try { - logger()->debug('Running SQL exec: ' . $sql); - $statement = $this->conn->exec($sql); - assert($statement !== false); - return $statement; - } catch (PDOException $exception) { - throw new DbException($exception->getMessage(), $exception->getCode(), $exception); - } - } - - /** - * @param null|mixed $name - * @throws DbException - */ - public function lastInsertId($name = null) - { - try { - return $name === null ? $this->conn->lastInsertId() : $this->conn->lastInsertId($name); - } catch (PDOException $exception) { - throw new DbException($exception->getMessage(), $exception->getCode(), $exception); - } - } - - public function beginTransaction() - { - return $this->conn->beginTransaction(); - } - - public function commit() - { - return $this->conn->commit(); - } - - public function rollBack() - { - return $this->conn->rollBack(); - } - - public function errorCode() - { - return $this->conn->errorCode(); - } - - public function errorInfo() - { - return $this->conn->errorInfo(); - } -} diff --git a/src/ZM/MySQL/MySQLDriver.php b/src/ZM/MySQL/MySQLDriver.php deleted file mode 100644 index ec8c9c2f..00000000 --- a/src/ZM/MySQL/MySQLDriver.php +++ /dev/null @@ -1,44 +0,0 @@ -debug('Requiring new connection'); - return new MySQLConnection(); - } - - public function getDatabasePlatform(): MySqlPlatform - { - return new MySqlPlatform(); - } - - public function getSchemaManager($conn) - { - return new MySqlSchemaManager($conn); - } - - public function getName() - { - return 'pdo_mysql_pool'; - } - - public function getDatabase($conn) - { - $params = ZMConfig::get('global', 'mysql_config'); - - if (isset($params['dbname'])) { - return $params['dbname']; - } - return ''; - } -} diff --git a/src/ZM/MySQL/MySQLManager.php b/src/ZM/MySQL/MySQLManager.php deleted file mode 100644 index 4f3f94bf..00000000 --- a/src/ZM/MySQL/MySQLManager.php +++ /dev/null @@ -1,13 +0,0 @@ -count; - return parent::get(); - } - - /** - * @param PDO|PDOProxy $connection - */ - public function putConnection($connection) - { - --$this->count; - parent::put($connection); - } - - public function getCount() - { - return $this->count; - } -} diff --git a/src/ZM/MySQL/MySQLQueryBuilder.php b/src/ZM/MySQL/MySQLQueryBuilder.php deleted file mode 100644 index 189efe8b..00000000 --- a/src/ZM/MySQL/MySQLQueryBuilder.php +++ /dev/null @@ -1,31 +0,0 @@ -getConnection()); - $this->wrapper = $wrapper; - } - - /** - * @throws DbException - * @return int|MySQLStatementWrapper - */ - public function execute() - { - if ($this->getType() === self::SELECT) { - return $this->wrapper->executeQuery($this->getSQL(), $this->getParameters(), $this->getParameterTypes()); - } - return $this->wrapper->executeStatement($this->getSQL(), $this->getParameters(), $this->getParameterTypes()); - } -} diff --git a/src/ZM/MySQL/MySQLStatement.php b/src/ZM/MySQL/MySQLStatement.php deleted file mode 100644 index 23b17869..00000000 --- a/src/ZM/MySQL/MySQLStatement.php +++ /dev/null @@ -1,123 +0,0 @@ -statement = $obj; - } - - public function closeCursor() - { - return $this->statement->closeCursor(); - } - - public function columnCount() - { - return $this->statement->columnCount(); - } - - public function setFetchMode($fetchMode, $arg2 = null, $arg3 = []) - { - if ($arg2 !== null && $arg3 !== []) { - return $this->statement->setFetchMode($fetchMode, $arg2, $arg3); - } - if ($arg2 !== null && $arg3 === []) { - return $this->statement->setFetchMode($fetchMode, $arg2); - } - if ($arg2 === null && $arg3 !== []) { - return $this->statement->setFetchMode($fetchMode, $arg2, $arg3); - } - - return $this->statement->setFetchMode($fetchMode); - } - - public function fetch($fetchMode = PDO::FETCH_ASSOC, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) - { - return $this->statement->fetch($fetchMode, $cursorOrientation, $cursorOffset); - } - - public function fetchAll($fetchMode = PDO::FETCH_ASSOC, $fetchArgument = null, $ctorArgs = null) - { - if ($fetchArgument === null && $ctorArgs === null) { - return $this->statement->fetchAll($fetchMode); - } - if ($fetchArgument !== null && $ctorArgs === null) { - return $this->statement->fetchAll($fetchMode, $fetchArgument); - } - - return $this->statement->fetchAll($fetchMode, $fetchArgument, $ctorArgs); - } - - public function fetchColumn($columnIndex = 0) - { - return $this->statement->fetchColumn($columnIndex); - } - - public function bindValue($param, $value, $type = ParameterType::STRING) - { - return $this->statement->bindValue($param, $value, $type); - } - - public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) - { - return $this->statement->bindParam($param, $variable, $type, $length); - } - - public function errorCode() - { - return $this->statement->errorCode(); - } - - public function errorInfo() - { - return $this->statement->errorInfo(); - } - - public function execute($params = null) - { - return $this->statement->execute($params); - } - - public function rowCount() - { - return $this->statement->rowCount(); - } - - public function getIterator(): Traversable - { - while (($result = $this->statement->fetch()) !== false) { - yield $result; - } - } - - /** - * @deprecated 最好不使用此方法,此方法可能存在 Bug - * @return mixed - */ - public function current() - { - if (method_exists($this->statement, 'current')) { - return $this->statement->current(); - } - return null; - } -} diff --git a/src/ZM/MySQL/MySQLStatementWrapper.php b/src/ZM/MySQL/MySQLStatementWrapper.php deleted file mode 100644 index 6256887f..00000000 --- a/src/ZM/MySQL/MySQLStatementWrapper.php +++ /dev/null @@ -1,240 +0,0 @@ -stmt = $stmt; - } - - /** - * 获取结果的迭代器 - * wrapper method - * @return ResultStatement - */ - public function getIterator() - { - return $this->stmt->getIterator(); - } - - /** - * 获取列数 - * wrapper method - * @return int - */ - public function columnCount() - { - return $this->stmt->columnCount(); - } - - /** - * wrapper method - * @throws DbException - * @return array|false|mixed - */ - public function fetchNumeric() - { - try { - return $this->stmt->fetchNumeric(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - * @return array|false|mixed - */ - public function fetchAssociative() - { - try { - return $this->stmt->fetchAssociative(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - * @return false|mixed - */ - public function fetchOne() - { - try { - return $this->stmt->fetchOne(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function fetchAllNumeric(): array - { - try { - return $this->stmt->fetchAllNumeric(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function fetchAllAssociative(): array - { - try { - return $this->stmt->fetchAllAssociative(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function fetchAllKeyValue(): array - { - try { - return $this->stmt->fetchAllKeyValue(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function fetchAllAssociativeIndexed(): array - { - try { - return $this->stmt->fetchAllAssociativeIndexed(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function fetchFirstColumn(): array - { - try { - return $this->stmt->fetchFirstColumn(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function iterateNumeric(): Traversable - { - try { - return $this->stmt->iterateNumeric(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function iterateAssociative(): Traversable - { - try { - return $this->stmt->iterateAssociative(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function iterateKeyValue(): Traversable - { - try { - return $this->stmt->iterateKeyValue(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function iterateAssociativeIndexed(): Traversable - { - try { - return $this->stmt->iterateAssociativeIndexed(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function iterateColumn(): Traversable - { - try { - return $this->stmt->iterateColumn(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - * @return int - */ - public function rowCount() - { - try { - return $this->stmt->rowCount(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - */ - public function free(): void - { - $this->stmt->free(); - } -} diff --git a/src/ZM/MySQL/MySQLWrapper.php b/src/ZM/MySQL/MySQLWrapper.php deleted file mode 100644 index ba010ada..00000000 --- a/src/ZM/MySQL/MySQLWrapper.php +++ /dev/null @@ -1,597 +0,0 @@ -connection = DriverManager::getConnection(['driverClass' => MySQLDriver::class]); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - public function __destruct() - { - $this->connection->close(); - } - - /** - * wrapper method - */ - public function getDatabase(): string - { - return $this->connection->getDatabase(); - } - - /** - * wrapper method - */ - public function isAutoCommit(): bool - { - return $this->connection->isAutoCommit(); - } - - /** - * wrapper method - */ - public function setAutoCommit(bool $auto_commit) - { - $this->connection->setAutoCommit($auto_commit); - } - - /** - * wrapper method - * @throws DbException - * @return array|false - */ - public function fetchAssociative(string $query, array $params = [], array $types = []) - { - try { - return $this->connection->fetchAssociative($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - * @return array|false - */ - public function fetchNumeric(string $query, array $params = [], array $types = []) - { - try { - return $this->connection->fetchNumeric($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * @throws DbException - * @return false|mixed - */ - public function fetchOne(string $query, array $params = [], array $types = []) - { - try { - return $this->connection->fetchOne($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - */ - public function isTransactionActive(): bool - { - return $this->connection->isTransactionActive(); - } - - /** - * @param string $table 表 - * @throws DbException - */ - public function delete(string $table, array $criteria, array $types = []): int - { - try { - return $this->connection->delete($table, $criteria, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param int $level Sets the transaction isolation level - */ - public function setTransactionIsolation(int $level): int - { - return $this->connection->setTransactionIsolation($level); - } - - /** - * wrapper method - */ - public function getTransactionIsolation(): ?int - { - return $this->connection->getTransactionIsolation(); - } - - /** - * wrapper method - * @param string $table 表名 - * @throws DbException - */ - public function update(string $table, array $data, array $criteria, array $types = []): int - { - try { - return $this->connection->update($table, $data, $criteria, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $table 表名 - * @throws DbException - */ - public function insert(string $table, array $data, array $types = []): int - { - try { - return $this->connection->insert($table, $data, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $str The name to be quoted - */ - public function quoteIdentifier(string $str): string - { - return $this->connection->quoteIdentifier($str); - } - - /** - * wrapper method - * @param mixed $value - * @param null|int|string|Type $type - */ - public function quote($value, $type = ParameterType::STRING) - { - return $this->connection->quote($value, $type); - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return array> - */ - public function fetchAllNumeric(string $query, array $params = [], array $types = []): array - { - try { - return $this->connection->fetchAllNumeric($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return array> - */ - public function fetchAllAssociative(string $query, array $params = [], array $types = []): array - { - try { - return $this->connection->fetchAllAssociative($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - */ - public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array - { - try { - return $this->connection->fetchAllKeyValue($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return array> - */ - public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array - { - try { - return $this->connection->fetchAllAssociativeIndexed($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return array - */ - public function fetchFirstColumn(string $query, array $params = [], array $types = []): array - { - try { - return $this->connection->fetchFirstColumn($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return Traversable> - */ - public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable - { - try { - return $this->connection->iterateNumeric($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return Traversable> - */ - public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable - { - try { - return $this->connection->iterateAssociative($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return Traversable - */ - public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable - { - try { - return $this->connection->iterateKeyValue($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return Traversable> - */ - public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable - { - try { - return $this->connection->iterateAssociativeIndexed($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $query SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return Traversable - */ - public function iterateColumn(string $query, array $params = [], array $types = []): Traversable - { - try { - return $this->connection->iterateColumn($query, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $sql SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * - * @throws DbException - */ - public function executeQuery(string $sql, array $params = [], array $types = [], ?QueryCacheProfile $qcp = null): MySQLStatementWrapper - { - try { - $query = $this->connection->executeQuery($sql, $params, $types, $qcp); - return new MySQLStatementWrapper($query); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $sql SQL query - * @param array|array $params Query parameters - * @param array|array $types Parameter types - * @throws DbException - */ - public function executeCacheQuery(string $sql, array $params, array $types, QueryCacheProfile $qcp): MySQLStatementWrapper - { - try { - $query = $this->connection->executeCacheQuery($sql, $params, $types, $qcp); - return new MySQLStatementWrapper($query); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $sql SQL statement - * @param array|array $params Statement parameters - * @param array|array $types Parameter types - * - * @throws DbException - * @return int|string the number of affected rows - */ - public function executeStatement(string $sql, array $params = [], array $types = []) - { - try { - return $this->connection->executeStatement($sql, $params, $types); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - */ - public function getTransactionNestingLevel(): int - { - return $this->connection->getTransactionNestingLevel(); - } - - /** - * wrapper method - * @param null|string $name name of the sequence object from which the ID should be returned - * @return false|int|string a string representation of the last inserted ID - */ - public function lastInsertId(?string $name = null) - { - return $this->connection->lastInsertId($name); - } - - /** - * overwrite method to $this->connection->transactional() - * @throws DbException - * @return mixed - */ - public function transactional(Closure $func) - { - $this->beginTransaction(); - try { - $res = $func($this); - $this->commit(); - return $res; - } catch (Throwable $e) { - $this->rollBack(); - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function setNestTransactionsWithSavepoints(bool $nest_transactions_with_savepoints) - { - try { - $this->connection->setNestTransactionsWithSavepoints($nest_transactions_with_savepoints); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - */ - public function getNestTransactionsWithSavepoints(): bool - { - return $this->connection->getNestTransactionsWithSavepoints(); - } - - /** - * wrapper method - */ - public function beginTransaction(): bool - { - return $this->connection->beginTransaction(); - } - - /** - * wrapper method - * @throws DbException - */ - public function commit(): bool - { - try { - return $this->connection->commit(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function rollBack(): bool - { - try { - return $this->connection->rollBack(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $savepoint the name of the savepoint to create - * @throws DbException - */ - public function createSavepoint(string $savepoint) - { - try { - $this->connection->createSavepoint($savepoint); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $savepoint the name of the savepoint to release - * @throws DbException - */ - public function releaseSavepoint(string $savepoint) - { - try { - $this->connection->releaseSavepoint($savepoint); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @param string $savepoint the name of the savepoint to rollback to - * @throws DbException - */ - public function rollbackSavepoint(string $savepoint) - { - try { - $this->connection->rollbackSavepoint($savepoint); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function setRollbackOnly() - { - try { - $this->connection->setRollbackOnly(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * wrapper method - * @throws DbException - */ - public function isRollbackOnly(): bool - { - try { - return $this->connection->isRollbackOnly(); - } catch (Throwable $e) { - throw new DbException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * overwrite method to $this->connection->createQueryBuilder - */ - public function createQueryBuilder(): MySQLQueryBuilder - { - return new MySQLQueryBuilder($this); - } - - public function getConnection(): Connection - { - return $this->connection; - } -} diff --git a/src/ZM/Utils/Manager/ProcessManager.php b/src/ZM/Process/ProcessStateManager.php similarity index 79% rename from src/ZM/Utils/Manager/ProcessManager.php rename to src/ZM/Process/ProcessStateManager.php index 727850b1..4a889521 100644 --- a/src/ZM/Utils/Manager/ProcessManager.php +++ b/src/ZM/Process/ProcessStateManager.php @@ -1,29 +1,18 @@ intval($pid), 'stdout' => $data['stdout'], @@ -149,19 +138,19 @@ class ProcessManager file_put_contents($file, json_encode($json, JSON_UNESCAPED_UNICODE)); return; case ZM_PROCESS_MANAGER: - $file = _zm_pid_dir() . '/manager.pid'; + $file = ZM_PID_DIR . '/manager.pid'; file_put_contents($file, strval($pid)); return; case ZM_PROCESS_WORKER: - $file = _zm_pid_dir() . '/worker.' . $data['worker_id'] . '.pid'; + $file = ZM_PID_DIR . '/worker.' . $data['worker_id'] . '.pid'; file_put_contents($file, strval($pid)); return; case ZM_PROCESS_USER: - $file = _zm_pid_dir() . '/user.' . $data['process_name'] . '.pid'; + $file = ZM_PID_DIR . '/user.' . $data['process_name'] . '.pid'; file_put_contents($file, strval($pid)); return; case ZM_PROCESS_TASKWORKER: - $file = _zm_pid_dir() . '/taskworker.' . $data['worker_id'] . '.pid'; + $file = ZM_PID_DIR . '/taskworker.' . $data['worker_id'] . '.pid'; file_put_contents($file, strval($pid)); return; } @@ -169,7 +158,7 @@ class ProcessManager public static function isStateEmpty(): bool { - $ls = DataProvider::scanDirFiles(_zm_pid_dir(), false, true); + $ls = FileSystem::scanDirFiles(ZM_PID_DIR, false, true); return empty($ls); } } diff --git a/src/ZM/Store/FileSystem.php b/src/ZM/Store/FileSystem.php new file mode 100644 index 00000000..e5f4f76c --- /dev/null +++ b/src/ZM/Store/FileSystem.php @@ -0,0 +1,170 @@ +warning(zm_internal_errcode('E00080') . '扫描目录失败,目录不存在'); + return false; + } + logger()->debug('扫描' . $dir); + // 套上 zm_dir + $scan_list = scandir($dir); + if ($scan_list === false) { + logger()->warning(zm_internal_errcode('E00080') . '扫描目录失败,目录无法读取: ' . $dir); + return false; + } + $list = []; + // 将 relative 置为相对目录的前缀 + if ($relative === true) { + $relative = $dir; + } + // 遍历目录 + foreach ($scan_list as $v) { + // Unix 系统排除这俩目录 + if ($v == '.' || $v == '..') { + continue; + } + $sub_file = zm_dir($dir . '/' . $v); + if (is_dir($sub_file) && $recursive) { // 如果是目录且设置了递归的话,就递归扫描并合并 + $list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative)); + } elseif (is_file($sub_file)) { // 如果是文件就直接加入列表 + if (is_string($relative) && strpos($sub_file, $relative) === 0) { + $list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '\\/'); + } elseif ($relative === false) { + $list[] = $sub_file; + } else { + logger()->warning(zm_internal_errcode('E00058') . "Relative path is not generated: wrong base directory ({$relative})"); + return false; + } + } + } + return $list; + } + + /** + * 检查路径是否为相对路径(根据第一个字符是否为"/"来判断) + * + * @param string $path 路径 + * @return bool 返回结果 + * @since 2.5 + */ + public static function isRelativePath(string $path): bool + { + return strlen($path) > 0 && $path[0] !== '/'; + } + + /** + * 创建目录(如果不存在) + * + * @param string $path 目录路径 + */ + public static function createDir(string $path): void + { + if (!is_dir($path) && !mkdir($path, 0777, true) && !is_dir($path)) { + throw new RuntimeException(sprintf('无法建立目录:%s', $path)); + } + } + + /** + * 在工作进程中返回可以通过reload重新加载的php文件列表 + * + * @return string[]|string[][] + */ + public static function getReloadableFiles(): array + { + $array_map = []; + global $zm_loaded_files; + foreach (array_diff( + get_included_files(), + $zm_loaded_files + ) as $key => $x) { + $array_map[$key] = str_replace(SOURCE_ROOT_DIR . '/', '', $x); + } + return $array_map; + } + + /** + * 使用Psr-4标准获取目录下的所有类 + * @param string $dir 目录 + * @param string $base_namespace 基础命名空间 + * @param null|mixed $rule 规则 + * @param bool|string $return_path_value 是否返回文件路径,返回文件路径的话传入字符串 + * @return string[] + */ + public static function getClassesPsr4(string $dir, string $base_namespace, $rule = null, $return_path_value = false): array + { + // 预先读取下composer的file列表 + $composer = json_decode(file_get_contents(zm_dir(SOURCE_ROOT_DIR . '/composer.json')), true); + $classes = []; + // 扫描目录,使用递归模式,相对路径模式,因为下面此路径要用作转换成namespace + $files = FileSystem::scanDirFiles($dir, true, true); + foreach ($files as $v) { + $pathinfo = pathinfo($v); + if (($pathinfo['extension'] ?? '') == 'php') { + $path = rtrim($dir, '/') . '/' . rtrim($pathinfo['dirname'], './') . '/' . $pathinfo['basename']; + + // 过滤不包含类的文件 + $tokens = token_get_all(file_get_contents($path)); + $found = false; + foreach ($tokens as $token) { + if (!is_array($token)) { + continue; + } + if ($token[0] === T_CLASS) { + $found = true; + break; + } + } + if (!$found) { + continue; + } + + if ($rule === null) { // 规则未设置回调时候,使用默认的识别过滤规则 + /*if (substr(file_get_contents($dir . '/' . $v), 6, 6) == '#plain') { + continue; + }*/ + if (file_exists($dir . '/' . $pathinfo['basename'] . '.plain')) { + continue; + } + if (mb_substr($pathinfo['basename'], 0, 7) == 'global_' || mb_substr($pathinfo['basename'], 0, 7) == 'script_') { + continue; + } + foreach (($composer['autoload']['files'] ?? []) as $fi) { + if (md5_file(SOURCE_ROOT_DIR . '/' . $fi) == md5_file($dir . '/' . $v)) { + continue 2; + } + } + } elseif (is_callable($rule) && !$rule($dir, $pathinfo)) { + continue; + } + $dirname = $pathinfo['dirname'] == '.' ? '' : (str_replace('/', '\\', $pathinfo['dirname']) . '\\'); + $class_name = $base_namespace . '\\' . $dirname . $pathinfo['filename']; + if (is_string($return_path_value)) { + $classes[$class_name] = $return_path_value . '/' . $v; + } else { + $classes[] = $class_name; + } + } + } + return $classes; + } +} diff --git a/src/ZM/Store/InternalGlobals.php b/src/ZM/Store/InternalGlobals.php new file mode 100644 index 00000000..c4adb738 --- /dev/null +++ b/src/ZM/Store/InternalGlobals.php @@ -0,0 +1,19 @@ +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, 8); - $result = self::$kv_table->create(); - // 加载内容 - if ($result === true && isset($config['persistence_path'])) { - if (file_exists($config['persistence_path'])) { - $r = json_decode(file_get_contents($config['persistence_path']), true); - if ($r === null) { - $r = []; - } - foreach ($r as $k => $v) { - $write = self::set($k, $v); - logger()->debug('Writing LightCache: ' . $k); - if ($write === false) { - self::$last_error = zm_internal_errcode('E00051') . '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; - return false; - } - } - } - } - if ($result === false) { - self::$last_error = zm_internal_errcode('E00050') . '系统内存不足,申请失败'; - } else { - $obj = Framework::loadFrameworkState(); - foreach (($obj['expiring_light_cache'] ?? []) as $k => $v) { - $value = $v['value']; - if (is_array($value)) { - $value = json_encode($value, JSON_UNESCAPED_UNICODE); - if (strlen($value) >= self::$config['max_strlen']) { - return false; - } - $data_type = 'json'; - } elseif (is_string($value)) { - $data_type = ''; - } elseif (is_int($value)) { - $data_type = 'int'; - } elseif (is_bool($value)) { - $data_type = 'bool'; - $value = json_encode($value); - } else { - return false; - } - $result = self::$kv_table->set($k, [ - 'value' => $value, - 'expire' => $v['expire'], - 'data_type' => $data_type, - ]); - if ($result === false) { - return false; - } - } - } - return $result; - } - - /** - * @throws ZMException - * @return null|mixed - */ - public static function get(string $key) - { - if (self::$kv_table === null) { - throw new LightCacheException('E00048', 'not initialized LightCache'); - } - self::checkExpire($key); - $r = self::$kv_table->get($key); - return $r === false ? null : self::parseGet($r); - } - - /** - * @throws ZMException - * @return null|mixed - */ - public static function getExpire(string $key) - { - if (self::$kv_table === null) { - throw new LightCacheException('E00048', 'not initialized LightCache'); - } - self::checkExpire($key); - $r = self::$kv_table->get($key, 'expire'); - return $r === false ? null : $r - time(); - } - - /** - * @throws ZMException - * @return null|mixed - * @since 2.4.3 - */ - public static function getExpireTS(string $key) - { - if (self::$kv_table === null) { - throw new LightCacheException('E00048', 'not initialized LightCache'); - } - self::checkExpire($key); - $r = self::$kv_table->get($key, 'expire'); - return $r === false ? null : $r; - } - - /** - * @param array|int|string $value - * @throws ZMException - * @return bool - */ - public static function set(string $key, $value, int $expire = -1) - { - if (self::$kv_table === null) { - throw new LightCacheException('E00048', 'not initialized LightCache'); - } - if (is_array($value)) { - $value = json_encode($value, JSON_UNESCAPED_UNICODE); - if (strlen($value) >= self::$config['max_strlen']) { - return false; - } - $data_type = 'json'; - } elseif (is_string($value)) { - $data_type = ''; - } elseif (is_int($value)) { - $data_type = 'int'; - } elseif (is_bool($value)) { - $data_type = 'bool'; - $value = json_encode($value); - } else { - throw new LightCacheException('E00049', 'Only can set string, array and int'); - } - try { - return self::$kv_table->set($key, [ - 'value' => $value, - 'expire' => $expire >= 0 ? $expire + time() : $expire, - 'data_type' => $data_type, - ]); - } catch (Exception $e) { - return false; - } - } - - /** - * @param mixed $value - * @throws ZMException - * @return bool - */ - public static function update(string $key, $value) - { - if (self::$kv_table === null) { - throw new LightCacheException('E00048', 'not initialized LightCache.'); - } - if (is_array($value)) { - $value = json_encode($value, JSON_UNESCAPED_UNICODE); - if (strlen($value) >= self::$config['max_strlen']) { - return false; - } - $data_type = 'json'; - } elseif (is_string($value)) { - $data_type = ''; - } elseif (is_int($value)) { - $data_type = 'int'; - } else { - throw new LightCacheException('E00048', 'Only can set string, array and int'); - } - try { - if (self::$kv_table->get($key) === false) { - return false; - } - return self::$kv_table->set($key, [ - 'value' => $value, - 'data_type' => $data_type, - ]); - } catch (Exception $e) { - return false; - } - } - - public static function getMemoryUsage() - { - return self::$kv_table->getMemorySize(); - } - - /** - * @throws Exception - */ - public static function isset(string $key): bool - { - return self::get($key) !== null; - } - - public static function unset(string $key) - { - return self::$kv_table->del($key); - } - - public static function getAll(): array - { - $r = []; - $del = []; - foreach (self::$kv_table as $k => $v) { - if ($v['expire'] <= time() && $v['expire'] >= 0) { - $del[] = $k; - continue; - } - $r[$k] = self::parseGet($v); - } - foreach ($del as $v) { - self::unset($v); - } - return $r; - } - - public static function addPersistence($key) - { - if (file_exists(self::$config['persistence_path'])) { - $r = json_decode(file_get_contents(self::$config['persistence_path']), true); - if ($r === null) { - $r = []; - } - if (!isset($r[$key])) { - $r[$key] = null; - } - file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256)); - return true; - } - return false; - } - - public static function removePersistence($key) - { - if (file_exists(self::$config['persistence_path'])) { - $r = json_decode(file_get_contents(self::$config['persistence_path']), true); - if ($r === null) { - $r = []; - } - if (isset($r[$key])) { - unset($r[$key]); - } - file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256)); - return true; - } - return false; - } - - /** - * 这个只能在唯一一个工作进程中执行 - * @throws Exception - */ - public static function savePersistence() - { - if (server()->worker_id !== MAIN_WORKER) { - WorkerManager::sendActionToWorker(MAIN_WORKER, 'save_persistence', []); - return; - } - $dispatcher = new EventDispatcher(OnSave::class); - $dispatcher->dispatchEvents(); - - if (self::$kv_table === null) { - return; - } - - if (!empty(self::$config['persistence_path'])) { - if (file_exists(self::$config['persistence_path'])) { - $r = json_decode(file_get_contents(self::$config['persistence_path']), true); - } else { - $r = []; - } - if ($r === null) { - $r = []; - } - foreach ($r as $k => $v) { - logger()->debug('Saving ' . $k); - $r[$k] = self::get($k); - } - file_put_contents(self::$config['persistence_path'], json_encode($r, 64 | 128 | 256)); - } - - $obj = Framework::loadFrameworkState(); - $obj['expiring_light_cache'] = []; - $del = []; - foreach (self::$kv_table as $k => $v) { - if ($v['expire'] <= time() && $v['expire'] >= 0) { - $del[] = $k; - } elseif ($v['expire'] > time()) { - $obj['expiring_light_cache'][$k] = [ - 'expire' => $v['expire'], - 'value' => self::parseGet($v), - ]; - } - } - foreach ($del as $v) { - self::unset($v); - } - Framework::saveFrameworkState($obj); - logger()->debug('Saved.'); - } - - private static function checkExpire($key) - { - if (($expire = self::$kv_table->get($key, 'expire')) >= 0) { - if ($expire <= time()) { - self::$kv_table->del($key); - } - } - } - - private static function parseGet($r) - { - switch ($r['data_type']) { - case 'json': - case 'int': - case 'bool': - return json_decode($r['value'], true); - case '': - default: - return $r['value']; - } - } -} diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php deleted file mode 100644 index 347223c2..00000000 --- a/src/ZM/Store/LightCacheInside.php +++ /dev/null @@ -1,73 +0,0 @@ -get($key); - return $r === false ? null : json_decode($r['value'], true); - } - - /** - * @param array|int|string $value - */ - public static function set(string $table, string $key, $value): bool - { - try { - return self::$kv_table[$table]->set($key, [ - 'value' => json_encode($value, 256), - ]); - } catch (Exception $e) { - return false; - } - } - - public static function unset(string $table, string $key) - { - return self::$kv_table[$table]->del($key); - } - - /** - * @param float|int $conflict_proportion - * @throws ZMException - */ - private static function createTable(string $name, int $size, int $str_size, $conflict_proportion = 0) - { - self::$kv_table[$name] = new Table($size, $conflict_proportion); - self::$kv_table[$name]->column('value', Table::TYPE_STRING, $str_size); - $r = self::$kv_table[$name]->create(); - if ($r === false) { - throw new LightCacheException('E00050', '内存不足,创建静态表失败!'); - } - } -} diff --git a/src/ZM/Store/Lock/FileLock.php b/src/ZM/Store/Lock/FileLock.php new file mode 100644 index 00000000..989362c0 --- /dev/null +++ b/src/ZM/Store/Lock/FileLock.php @@ -0,0 +1,45 @@ +critical("Can not create lock file {$lock_file}\n"); + throw new ZMKnownException('E99999', 'Can not create lock file ' . $lock_file); + } + if (!flock(self::$lock_file_handle[$name], LOCK_EX)) { + logger()->error("File Lock \"{$name}\"already exists.\n"); + } + } + + /** + * 解锁 + * + * @param string $name 锁名 + */ + public static function unlock(string $name) + { + if ((self::$lock_file_handle[$name] ?? false) !== false) { + fclose(self::$lock_file_handle[$name]); + } + } +} diff --git a/src/ZM/Store/Lock/SpinLock.php b/src/ZM/Store/Lock/SpinLock.php deleted file mode 100644 index 4990507f..00000000 --- a/src/ZM/Store/Lock/SpinLock.php +++ /dev/null @@ -1,52 +0,0 @@ -column('lock_num', Table::TYPE_INT, 8); - return self::$kv_lock->create(); - } - - public static function lock(string $key) - { - while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { // 此资源已经被锁上了 - usleep(self::$delay * 1000); - } - } - - public static function tryLock(string $key): bool - { - if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { - return false; - } - return true; - } - - public static function unlock(string $key) - { - return self::$kv_lock->set($key, ['lock_num' => 0]); - } - - public static function transaction(string $key, callable $function) - { - SpinLock::lock($key); - $function(); - SpinLock::unlock($key); - } -} diff --git a/src/ZM/Store/MySQL/SqlPoolStorage.php b/src/ZM/Store/MySQL/SqlPoolStorage.php deleted file mode 100644 index c335798d..00000000 --- a/src/ZM/Store/MySQL/SqlPoolStorage.php +++ /dev/null @@ -1,13 +0,0 @@ -conn = ZMRedisPool::$pool->get(); - } - - public function __destruct() - { - if (isset($this->conn->wasted)) { - ZMRedisPool::$pool->put(null); - } else { - ZMRedisPool::$pool->put($this->conn); - } - } - - /** - * @throws NotInitializedException - * @return mixed - */ - public static function call(callable $callable) - { - if (ZMRedisPool::$pool === null) { - throw new NotInitializedException('Redis pool is not initialized.'); - } - $r = ZMRedisPool::$pool->get(); - $result = $callable($r); - if (isset($r->wasted)) { - ZMRedisPool::$pool->put(null); - } else { - ZMRedisPool::$pool->put($r); - } - return $result; - } - - public function get(): Redis - { - return $this->conn; - } -} diff --git a/src/ZM/Store/Redis/ZMRedisPool.php b/src/ZM/Store/Redis/ZMRedisPool.php deleted file mode 100644 index 9f8af472..00000000 --- a/src/ZM/Store/Redis/ZMRedisPool.php +++ /dev/null @@ -1,41 +0,0 @@ -withHost($config['host']) - ->withPort($config['port']) - ->withAuth($config['auth']) - ->withDbIndex($config['db_index']) - ->withTimeout($config['timeout'] ?? 1) - ); - try { - $r = self::$pool->get()->ping('123'); - if (strpos(strtolower($r), '123') !== false) { - logger()->debug('成功连接redis连接池!'); - } else { - var_dump($r); - } - } catch (RedisException $e) { - Console::error(zm_internal_errcode('E00047') . 'Redis init failed! ' . $e->getMessage()); - self::$pool = null; - } - } -} diff --git a/src/ZM/Store/WorkerCache.php b/src/ZM/Store/WorkerCache.php deleted file mode 100644 index b4d7e130..00000000 --- a/src/ZM/Store/WorkerCache.php +++ /dev/null @@ -1,107 +0,0 @@ - 0]; - if ($config['worker'] === server()->worker_id) { - return self::$store[$key] ?? null; - } - $action = ['action' => 'getWorkerCache', 'key' => $key, 'cid' => zm_cid()]; - server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config['worker']); - zm_yield(); - $p = self::$transfer[zm_cid()] ?? null; - unset(self::$transfer[zm_cid()]); - return $p; - } - - public static function set($key, $value, $async = false) - { - $config = self::$config ?? ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if ($config['worker'] === server()->worker_id) { - self::$store[$key] = $value; - return true; - } - $action = ['action' => $async ? 'asyncSetWorkerCache' : 'setWorkerCache', 'key' => $key, 'value' => $value, 'cid' => zm_cid()]; - return self::processRemote($action, $async, $config); - } - - public static function hasKey($key, $subkey) - { - $config = self::$config ?? ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if ($config['worker'] === server()->worker_id) { - return isset(self::$store[$key][$subkey]); - } - $action = ['hasKeyWorkerCache', 'key' => $key, 'subkey' => $subkey, 'cid' => zm_cid()]; - return self::processRemote($action, false, $config); - } - - public static function unset($key, $async = false) - { - $config = self::$config ?? ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if ($config['worker'] === server()->worker_id) { - unset(self::$store[$key]); - return true; - } - $action = ['action' => $async ? 'asyncUnsetWorkerCache' : 'unsetWorkerCache', 'key' => $key, 'cid' => zm_cid()]; - return self::processRemote($action, $async, $config); - } - - public static function add($key, int $value, $async = false) - { - $config = self::$config ?? ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if ($config['worker'] === server()->worker_id) { - if (!isset(self::$store[$key])) { - self::$store[$key] = 0; - } - self::$store[$key] += $value; - return true; - } - $action = ['action' => $async ? 'asyncAddWorkerCache' : 'addWorkerCache', 'key' => $key, 'value' => $value, 'cid' => zm_cid()]; - return self::processRemote($action, $async, $config); - } - - public static function sub($key, int $value, $async = false) - { - $config = self::$config ?? ZMConfig::get('global', 'worker_cache') ?? ['worker' => 0]; - if ($config['worker'] === server()->worker_id) { - if (!isset(self::$store[$key])) { - self::$store[$key] = 0; - } - self::$store[$key] -= $value; - return true; - } - $action = ['action' => $async ? 'asyncSubWorkerCache' : 'subWorkerCache', 'key' => $key, 'value' => $value, 'cid' => zm_cid()]; - return self::processRemote($action, $async, $config); - } - - private static function processRemote($action, $async, $config) - { - $ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config['worker']); - if (!$ss) { - return false; - } - if ($async) { - return true; - } - zm_yield(); - $p = self::$transfer[zm_cid()] ?? null; - unset(self::$transfer[zm_cid()]); - return $p; - } -} diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php deleted file mode 100644 index 6a2977b2..00000000 --- a/src/ZM/Store/ZMAtomic.php +++ /dev/null @@ -1,44 +0,0 @@ - $v) { - self::$atomics[$k] = new Atomic($v); - } - self::$atomics['stop_signal'] = new Atomic(0); - self::$atomics['_int_is_reload'] = new Atomic(0); - self::$atomics['wait_msg_id'] = new Atomic(0); - self::$atomics['_event_id'] = new Atomic(0); - self::$atomics['server_is_stopped'] = new Atomic(0); - if (!defined('ZM_WORKER_NUM')) { - define('ZM_WORKER_NUM', 1); - } - for ($i = 0; $i < ZM_WORKER_NUM; ++$i) { - self::$atomics['_#worker_' . $i] = new Atomic(0); - } - for ($i = 0; $i < 10; ++$i) { - self::$atomics['_tmp_' . $i] = new Atomic(0); - } - self::$atomics['ss'] = new Atomic(1); - } -} diff --git a/src/ZM/Store/ZMBuf.php b/src/ZM/Store/ZMBuf.php deleted file mode 100644 index a19c24ec..00000000 --- a/src/ZM/Store/ZMBuf.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ - public static $context_class = []; - - /** - * 终端输入流? - * - * 目前等用于 STDIN - * - * @var resource - */ - public static $terminal; -} diff --git a/src/ZM/Utils/CoMessage.php b/src/ZM/Utils/CoMessage.php deleted file mode 100644 index 42560000..00000000 --- a/src/ZM/Utils/CoMessage.php +++ /dev/null @@ -1,90 +0,0 @@ -add(); - $hang['compare'] = $compare; - $hang['coroutine'] = $cid; - $hang['worker_id'] = server()->worker_id; - $hang['result'] = null; - SpinLock::lock('wait_api'); - $wait = LightCacheInside::get('wait_api', 'wait_api'); - $wait[$api_id] = $hang; - LightCacheInside::set('wait_api', 'wait_api', $wait); - SpinLock::unlock('wait_api'); - $id = swoole_timer_after($timeout * 1000, function () use ($api_id) { - $r = LightCacheInside::get('wait_api', 'wait_api')[$api_id] ?? null; - if (is_array($r)) { - Coroutine::resume($r['coroutine']); - } - }); - Coroutine::suspend(); - SpinLock::lock('wait_api'); - $sess = LightCacheInside::get('wait_api', 'wait_api'); - $result = $sess[$api_id]['result'] ?? null; - unset($sess[$api_id]); - LightCacheInside::set('wait_api', 'wait_api', $sess); - SpinLock::unlock('wait_api'); - Timer::clear($id); - if ($result === null) { - return false; - } - return $result; - } - - /** - * @throws Exception - */ - public static function resumeByWS(): bool - { - $dat = ctx()->getData(); - $last = null; - SpinLock::lock('wait_api'); - $all = LightCacheInside::get('wait_api', 'wait_api') ?? []; - foreach ($all as $k => $v) { - if (!isset($v['compare'])) { - continue; - } - foreach ($v['compare'] as $vs) { - if (!isset($v[$vs], $dat[$vs])) { - continue 2; - } - if ($v[$vs] != $dat[$vs]) { - continue 2; - } - } - $last = $k; - } - if ($last !== null) { - $all[$last]['result'] = $dat; - LightCacheInside::set('wait_api', 'wait_api', $all); - SpinLock::unlock('wait_api'); - if ($all[$last]['worker_id'] != server()->worker_id) { - WorkerManager::sendActionToWorker($all[$last]['worker_id'], 'resume_ws_message', $all[$last]); - } else { - Coroutine::resume($all[$last]['coroutine']); - } - return true; - } - SpinLock::unlock('wait_api'); - return false; - } -} diff --git a/src/ZM/Utils/CommandInfoUtil.php b/src/ZM/Utils/CommandInfoUtil.php deleted file mode 100644 index 05ab9f9f..00000000 --- a/src/ZM/Utils/CommandInfoUtil.php +++ /dev/null @@ -1,221 +0,0 @@ - 'string', 'call' => 'callable', 'descriptions' => ['string'], 'triggers' => ['trigger_name' => ['string']], 'args' => ['arg_name' => ['name' => 'string', 'type' => 'string', 'description' => 'string', 'default' => 'mixed', 'required' => 'bool']]]])] - public function get(): array - { - if (!$this->exists()) { - return $this->generateCommandList(); - } - return WorkerCache::get('commands'); - } - - /** - * 重新生成命令信息 - */ - public function regenerate(): void - { - $this->generateCommandList(); - } - - /** - * 获取命令帮助 - * - * @param string $command_id 命令ID,为 `class@method` 格式 - * @param bool $simple 是否仅输出简易信息(只有命令触发条件和描述) - */ - public function getHelp(string $command_id, bool $simple = false): string - { - $command = $this->get()[$command_id]; - - $formats = [ - 'match' => '%s', - 'pattern' => '符合”%s“', - 'regex' => '匹配“%s”', - 'start_with' => '以”%s“开头', - 'end_with' => '以”%s“结尾', - 'keyword' => '包含“%s”', - 'alias' => '%s', - ]; - $triggers = []; - foreach ($command['triggers'] as $trigger => $conditions) { - if (count($conditions) === 0) { - continue; - } - if (isset($formats[$trigger])) { - $format = $formats[$trigger]; - } else { - logger()->warning("未知的命令触发条件:{$trigger}"); - continue; - } - foreach ($conditions as $condition) { - $condition = sprintf($format, $condition); - $triggers[] = $condition; - } - } - $name = array_shift($triggers); - if (count($triggers) > 0) { - $name .= '(' . implode(',', $triggers) . ')'; - } - - if (empty($command['descriptions'])) { - $description = '作者很懒,啥也没说'; - } else { - $description = implode(';', $command['descriptions']); - } - - if ($simple) { - return "{$name}:{$description}"; - } - - $lines = []; - - $lines[0][] = $name; - $lines[1][] = $description; - - foreach ($command['args'] as $arg_name => $arg_info) { - if ($arg_info['required']) { - $lines[0][] = "<{$arg_name}: {$arg_info['type']}>"; - } else { - $buffer = "[{$arg_name}: {$arg_info['type']}"; - if (!empty($arg_info['default'])) { - $buffer .= " = {$arg_info['default']}"; - } - $lines[0][] = $buffer . ']'; - } - - $lines[][] = "{$arg_name};{$arg_info['description']}"; - } - - $buffer = []; - foreach ($lines as $line) { - $buffer[] = implode(' ', $line); - } - return implode("\n", $buffer); - } - - /** - * 缓存命令信息 - */ - protected function save(array $helps): void - { - WorkerCache::set('commands', $helps); - } - - /** - * 根据注解树生成命令信息(内部) - */ - protected function generateCommandList(): array - { - $commands = []; - - foreach (EventManager::$events[CQCommand::class] ?? [] as $annotation) { - $id = "{$annotation->class}@{$annotation->method}"; - - try { - $reflection = new ReflectionMethod($annotation->class, $annotation->method); - } catch (ReflectionException $e) { - logger()->warning('命令 ' . $id . ' 注解解析错误:' . $e->getMessage()); - continue; - } - - $doc = $reflection->getDocComment(); - if ($doc) { - // 匹配出不以@开头,且后接中文或任意非空格字符,并以换行符结尾的字符串,也就是命令描述 - preg_match_all('/\*\s((?!@)[\x{4e00}-\x{9fa5}\S]+)(\r\n|\r|\n)/u', $doc, $descriptions); - $descriptions = $descriptions[1]; - } - - $command = [ - 'id' => $id, - 'call' => [$annotation->class, $annotation->method], - 'descriptions' => $descriptions ?? [], - 'triggers' => [], - 'args' => [], - ]; - - if (empty($command['descriptions'])) { - logger()->warning("命令没有描述信息:{$id}"); - } - - // 可能的触发条件,顺序会影响命令帮助的生成结果 - $possible_triggers = ['match', 'pattern', 'regex', 'start_with', 'end_with', 'keyword', 'alias']; - foreach ($possible_triggers as $trigger) { - if (isset($annotation->{$trigger}) && !empty($annotation->{$trigger})) { - // 部分触发条件可能存在多个 - if (is_iterable($annotation->{$trigger})) { - foreach ($annotation->{$trigger} as $item) { - $command['triggers'][$trigger][] = $item; - } - } else { - $command['triggers'][$trigger][] = $annotation->{$trigger}; - } - } - } - if (empty($command['triggers'])) { - logger()->warning("命令没有触发条件:{$id}"); - continue; - } - - $command['args'] = $this->generateCommandArgumentList($id); - - $commands[$id] = $command; - } - - $this->save($commands); - return $commands; - } - - /** - * 生成指定命令的参数列表 - * - * @param string $id 命令 ID - */ - protected function generateCommandArgumentList(string $id): array - { - [$class, $method] = explode('@', $id); - $map = EventManager::$event_map[$class][$method]; - - $args = []; - - foreach ($map as $annotation) { - if (!$annotation instanceof CommandArgument) { - continue; - } - - $args[$annotation->name] = [ - 'name' => $annotation->name, - 'type' => $annotation->type, - 'description' => $annotation->description, - 'default' => $annotation->default, - 'required' => $annotation->required, - ]; - } - - return $args; - } -} diff --git a/src/ZM/Utils/CoroutinePool.php b/src/ZM/Utils/CoroutinePool.php deleted file mode 100644 index 4654332d..00000000 --- a/src/ZM/Utils/CoroutinePool.php +++ /dev/null @@ -1,62 +0,0 @@ -= (self::$sizes[$name] ?? self::$default_size)) { - self::$yields[] = Coroutine::getCid(); - Coroutine::suspend(); - } - go(function () use ($func, $name) { - self::$cids[$name][] = Coroutine::getCid(); - // logger()->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; - } - - public static function getRunningCoroutineCount($name = 'default') - { - return count(self::$cids[$name]); - } - - 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/DataProvider.php b/src/ZM/Utils/DataProvider.php deleted file mode 100644 index 5905b8e1..00000000 --- a/src/ZM/Utils/DataProvider.php +++ /dev/null @@ -1,197 +0,0 @@ -warning(zm_internal_errcode('E00057') . '存储失败,文件名只能有一级目录'); - return false; - } else { - $name = $r[0]; - } - return file_put_contents($path . $name . '.json', json_encode($file_array, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); - } - - /** - * 从json加载变量到内存 - * - * @param string $filename 文件名 - * @throws ConfigException - * @return null|mixed 返回文件内容数据或null - */ - public static function loadFromJson(string $filename) - { - $path = ZMConfig::get('global', 'config_dir'); - if (file_exists($path . $filename . '.json')) { - return json_decode(file_get_contents($path . $filename . '.json'), true); - } - return null; - } - - /** - * 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表 - * - * @param string $dir 目录 - * @param bool $recursive 是否递归扫描子目录 - * @param bool|string $relative 是否返回相对目录,如果为true则返回相对目录,如果为false则返回绝对目录 - * @return array|false - * @since 2.5 - */ - public static function scanDirFiles(string $dir, bool $recursive = true, $relative = false) - { - $dir = rtrim($dir, '/'); - if (!is_dir($dir)) { - return false; - } - $r = scandir($dir); - if ($r === false) { - return false; - } - $list = []; - if ($relative === true) { - $relative = $dir; - } - foreach ($r as $v) { - if ($v == '.' || $v == '..') { - continue; - } - $sub_file = $dir . '/' . $v; - if (is_dir($sub_file) && $recursive) { - $list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative)); - } elseif (is_file($sub_file)) { - if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) { - $list[] = ltrim(mb_substr($sub_file, mb_strlen($relative)), '/'); - } elseif ($relative === false) { - $list[] = $sub_file; - } else { - logger()->warning(zm_internal_errcode('E00058') . "Relative path is not generated: wrong base directory ({$relative})"); - return false; - } - } - } - return $list; - } - - /** - * 检查路径是否为相对路径(根据第一个字符是否为"/"来判断) - * - * @param string $path 路径 - * @return bool 返回结果 - * @since 2.5 - */ - public static function isRelativePath(string $path): bool - { - return strlen($path) > 0 && $path[0] !== '/'; - } - - /** - * 创建目录(如果不存在) - * - * @param string $path 目录路径 - */ - public static function createIfNotExists(string $path): void - { - if (!is_dir($path) && !mkdir($path, 0777, true) && !is_dir($path)) { - throw new RuntimeException(sprintf('无法建立目录:%s', $path)); - } - } -} diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php deleted file mode 100644 index 418dba6b..00000000 --- a/src/ZM/Utils/HttpUtil.php +++ /dev/null @@ -1,110 +0,0 @@ -setMethod($request->server['request_method']); - - try { - $matcher = new UrlMatcher(RouteManager::$routes ?? new RouteCollection(), $context); - $matched = $matcher->match($uri); - } catch (ResourceNotFoundException $e) { - if (ZMConfig::get('global', 'static_file_server')['status']) { - HttpUtil::handleStaticPage($request->server['request_uri'], $response); - return null; - } - $matched = null; - } catch (MethodNotAllowedException $e) { - $matched = null; - } - if ($matched !== null) { - $node = [ - 'route' => RouteManager::$routes->get($matched['_route'])->getPath(), - 'class' => $matched['_class'], - 'method' => $matched['_method'], - 'request_method' => $request->server['request_method'], - ]; - unset($matched['_class'], $matched['_method']); - $params = $matched; - return true; - } - return false; - } - - public static function getHttpCodePage(int $http_code) - { - if (isset(ZMConfig::get('global', 'http_default_code_page')[$http_code])) { - return Coroutine::readFile(DataProvider::getResourceFolder() . 'html/' . ZMConfig::get('global', 'http_default_code_page')[$http_code]); - } - return null; - } - - /** - * @param Response|\Swoole\Http\Response $response - */ - public static function handleStaticPage(string $uri, $response, array $settings = []): bool - { - $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) { - logger()->info('[403] ' . $uri); - self::responseCodePage($response, 403); - return true; - } - if (is_dir($path)) { - if (mb_substr($uri, -1, 1) != '/') { - logger()->info('[302] ' . $uri); - $response->redirect($uri . '/', 302); - return true; - } - foreach ($base_index as $vp) { - if (is_file($path . '/' . $vp)) { - logger()->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)) { - logger()->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; - } - } - logger()->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/Macroable.php b/src/ZM/Utils/Macroable.php deleted file mode 100644 index 1524e98c..00000000 --- a/src/ZM/Utils/Macroable.php +++ /dev/null @@ -1,45 +0,0 @@ -bindTo($this, static::class), $parameters); - } - return call_user_func_array(static::$macros[$method], $parameters); - } - - public static function macro($name, callable $macro) - { - static::$macros[$name] = $macro; - } - - public static function hasMacro($name) - { - return isset(static::$macros[$name]); - } -} diff --git a/src/ZM/Utils/Manager/CronManager.php b/src/ZM/Utils/Manager/CronManager.php deleted file mode 100644 index 42e13fda..00000000 --- a/src/ZM/Utils/Manager/CronManager.php +++ /dev/null @@ -1,110 +0,0 @@ -worker_id !== $v->worker_id && $v->worker_id != -1) { - return; - } - try { - if (strpos($v->expression, '\\') !== 0) { - $v->expression = str_replace('\\', '/', $v->expression); - } - $cron = new CronExpression($v->expression); - $cron->setMaxIterationCount($v->max_iteration_count); - $plain_class = $v->class; - logger()->debug("Cron task checker starting {$plain_class}:{$v->method}, next run at {$cron->getNextRunDate()->format('Y-m-d H:i:s')}"); - if ($v->check_delay_time > 60000 || $v->check_delay_time < 1000) { - logger()->warning(zm_internal_errcode('E00076') . 'Delay time must be between 1000 and 60000, reset to 20000'); - $v->check_delay_time = 20000; - } - } catch (InvalidArgumentException $e) { - logger()->error(zm_internal_errcode('E00075') . 'Invalid cron expression or arguments, please check it!'); - throw $e; - } - - Timer::tick($v->check_delay_time, static function () use ($v, $dispatcher, $cron) { - set_coroutine_params([]); - if (ZMAtomic::get('stop_signal')->get() != 0) { - Timer::clearAll(); - return; - } - try { - logger()->debug('Cron: ' . ($cron->isDue() ? 'true' : 'false') . ', last: ' . $cron->getPreviousRunDate()->format('Y-m-d H:i:s') . ', next: ' . $cron->getNextRunDate()->format('Y-m-d H:i:s')); - if ($cron->isDue()) { - if ($v->getStatus() === 0) { - self::startExecute($v, $dispatcher, $cron); - } elseif ($v->getStatus() === 2) { - if ($v->getRecordNextTime() !== $cron->getNextRunDate()->getTimestamp()) { - self::startExecute($v, $dispatcher, $cron); - } - } - } else { - if ($v->getStatus() === 2 && $v->getRecordNextTime()) { - $v->setStatus(0); - } - } - } catch (Exception $e) { - Console::error(zm_internal_errcode('E00034') . 'Uncaught error from Cron: ' . $e->getMessage() . ' at ' . $e->getFile() . "({$e->getLine()})"); - } catch (Error $e) { - Console::error(zm_internal_errcode('E00034') . 'Uncaught fatal error from Cron: ' . $e->getMessage()); - echo Console::setColor($e->getTraceAsString(), 'gray'); - Console::error('Please check your code!'); - } - }); - } - } - - /** - * @throws InterruptException - * @throws AnnotationException - * @throws Exception - */ - private static function startExecute(Cron $v, EventDispatcher $dispatcher, CronExpression $cron) - { - logger()->debug("Cron task {$v->class}:{$v->method} is due, running at " . date('Y-m-d H:i:s') . ($v->getRecordNextTime() === 0 ? '' : (', offset ' . (time() - $v->getRecordNextTime()) . 's'))); - $v->setStatus(1); - $starttime = microtime(true); - $pre_next_time = $cron->getNextRunDate()->getTimestamp(); - $dispatcher->dispatchEvent($v, null, $cron); - logger()->debug("Cron task {$v->class}:{$v->method} is done, using " . round(microtime(true) - $starttime, 3) . 's'); - if ($pre_next_time !== $cron->getNextRunDate()->getTimestamp()) { // 这一步用于判断运行的Cron是否已经覆盖到下一个运行区间 - if (time() + round($v->check_delay_time / 1000) >= $pre_next_time) { // 假设检测到下一个周期运行时间已经要超过了预计的时间,则警告运行超时 - logger()->warning(zm_internal_errcode('E00077') . 'Cron task ' . $v->class . ':' . $v->method . ' is timeout'); - } - } else { - logger()->debug('Next run at ' . date('Y-m-d H:i:s', $cron->getNextRunDate()->getTimestamp())); - } - $v->setRecordNextTime($pre_next_time); - $v->setStatus(2); - } -} diff --git a/src/ZM/Utils/Manager/ModuleManager.php b/src/ZM/Utils/Manager/ModuleManager.php deleted file mode 100644 index 3e628e21..00000000 --- a/src/ZM/Utils/Manager/ModuleManager.php +++ /dev/null @@ -1,219 +0,0 @@ - $vs) { - if (strpos($relative_path, $vs) === 0) { - $remain = trim(substr($relative_path, strlen($vs)), '/'); - $remain = str_replace('/', '\\', $remain); - $json['namespace'] = $ks . $remain; - break; - } - } - // $json['namespace'] = str_replace('/', '\\', $pathinfo['dirname']); - - if (isset($modules[$json['name']])) { - throw new ZMKnownException('E00053', '重名模块:' . $json['name']); - } - $modules[$json['name']] = $json; - } - } - return $modules; - } - - public static function getPackedModules(): array - { - $dir = ZMConfig::get('global', 'module_loader')['load_path'] ?? (ZM_DATA . 'modules'); - $ls = DataProvider::scanDirFiles($dir, true, false); - if ($ls === false) { - return []; - } - $modules = []; - foreach ($ls as $v) { - $pathinfo = pathinfo($v); - if (($pathinfo['extension'] ?? '') != 'phar') { - continue; - } - $file = 'phar://' . $v; - if (!is_file($file . '/module_entry.php') || !is_file($file . '/zmplugin.json')) { - continue; - } - $module_config = json_decode(file_get_contents($file . '/zmplugin.json'), true); - if ($module_config === null) { - continue; - } - if (!is_file($file . '/' . $module_config['module-root-path'] . '/zm.json')) { - logger()->warning(zm_internal_errcode('E00054') . '模块(插件)文件 ' . $pathinfo['basename'] . ' 无法找到模块配置文件(zm.json)!'); - continue; - } - $module_file = json_decode(file_get_contents($file . '/' . $module_config['module-root-path'] . '/zm.json'), true); - if ($module_file === null) { - logger()->warning(zm_internal_errcode('E000555') . '模块(插件)文件 ' . $pathinfo['basename'] . ' 无法正常读取模块配置文件(zm.json)!'); - continue; - } - $module_config['phar-path'] = $v; - $module_config['name'] = $module_file['name'] ?? null; - if ($module_config['name'] === null) { - continue; - } - $module_config['module-config'] = $module_file; - $modules[$module_config['name']] = $module_config; - } - return $modules; - } - - public static function getComposerModules() - { - $vendor_file = DataProvider::getSourceRootDir() . '/vendor/composer/installed.json'; - $obj = json_decode(file_get_contents($vendor_file), true); - if ($obj === null) { - return []; - } - $modules = []; - foreach ($obj['packages'] as $v) { - if (isset($v['extra']['zm']['module-path'])) { - if (is_array($v['extra']['zm']['module-path'])) { - foreach ($v['extra']['zm']['module-path'] as $module_path) { - $m = self::getComposerModuleInfo($v, $module_path); - if ($m !== null) { - $modules[$m['name']] = $m; - } - } - } elseif (is_string($v['extra']['zm']['module-path'])) { - $m = self::getComposerModuleInfo($v, $v['extra']['zm']['module-path']); - if ($m !== null) { - $modules[$m['name']] = $m; - } - } - } - } - return $modules; - } - - /** - * 打包模块 - * @param array $module 模块信息 - * @param string $target 目标路径 - * @throws ZMException - */ - public static function packModule(array $module, string $target): bool - { - try { - $packer = new ModulePacker($module); - if (!is_dir(DataProvider::getDataFolder())) { - throw new ModulePackException(zm_internal_errcode('E00070') . 'zm_data dir not found!'); - } - $path = realpath($target); - if ($path === false) { - mkdir($path = $target, 0755, true); - } - $packer->setOutputPath($path); - $packer->setOverride(); - $packer->pack(); - return true; - } catch (ModulePackException $e) { - Console::error($e->getMessage()); - return false; - } - } - - /** - * 解包模块 - * @param array|Iterator $module 模块信息 - * @return array|false 返回解包的信息或false - */ - public static function unpackModule($module, array $options = []) - { - try { - $packer = new ModuleUnpacker($module); - return $packer->unpack((bool) $options['overwrite-light-cache'], (bool) $options['overwrite-zm-data'], (bool) $options['overwrite-source'], (bool) $options['ignore-depends']); - } catch (ZMException $e) { - Console::error($e->getMessage()); - return false; - } - } - - private static function getComposerModuleInfo($v, $module_path) - { - $module_root_path = realpath(DataProvider::getSourceRootDir() . '/vendor/composer/' . $v['install-path'] . '/' . $module_path); - if ($module_root_path === false) { - logger()->warning(zm_internal_errcode('E00055') . '无法找到Composer发布的插件配置路径在包 `' . $v['name'] . '` 中!'); - return null; - } - $json = json_decode(file_get_contents($module_root_path . '/zm.json'), true); - if ($json === null) { - logger()->warning(zm_internal_errcode('E00054') . 'Composer包内无法正常读取 ' . $v['name'] . ' 的内的配置文件(zm.json)!'); - return null; - } - if (!isset($json['name'])) { - return null; - } - $json['composer-name'] = $v['name']; - $json['module-root-path'] = realpath(DataProvider::getSourceRootDir() . '/vendor/composer/' . $v['install-path']); - $json['module-path'] = realpath($json['module-root-path'] . '/' . $module_path); - if (isset($v['autoload']['psr-4'])) { - foreach ($v['autoload']['psr-4'] as $ks => $vs) { - $vs = trim($vs, '/'); - if (strpos($module_path, $vs) === 0) { - $json['namespace'] = trim($ks . str_replace('/', '\\', trim(substr($module_path, strlen($vs)), '/')), '\\'); - break; - } - } - } - if (!isset($json['namespace'])) { - logger()->warning(zm_internal_errcode('E00055') . '无法获取Composer发布的模块命名空间!'); - return null; - } - return $json; - } -} diff --git a/src/ZM/Utils/Manager/RouteManager.php b/src/ZM/Utils/Manager/RouteManager.php deleted file mode 100644 index b2c691ac..00000000 --- a/src/ZM/Utils/Manager/RouteManager.php +++ /dev/null @@ -1,66 +0,0 @@ -prefix; - break; - } - } - $tail = trim($vss->route, '/'); - $route_name = $prefix . ($tail === '' ? '' : '/') . $tail; - logger()->debug('添加路由:' . $route_name); - $route = new Route($route_name, ['_class' => $class, '_method' => $method]); - $route->setMethods($vss->request_method); - - self::$routes->add(md5($route_name), $route); - } - - public static function addStaticFileRoute($route, $path) - { - $tail = trim($route, '/'); - $route_name = ($tail === '' ? '' : '/') . $tail . '/{filename}'; - logger()->debug('添加静态文件路由:' . $route_name); - $route = new Route($route_name, ['_class' => __CLASS__, '_method' => 'onStaticRoute'], [], compact('path')); - - self::$routes->add(md5($route_name), $route); - } - - public function onStaticRoute(array $params) - { - if (($path = self::$routes->get($params['_route'])->getOption('path')) === null) { - ctx()->getResponse()->endWithStatus(404); - return false; - } - unset($params['_route']); - $obj = array_shift($params); - return new StaticFileHandler($obj, $path); - } -} diff --git a/src/ZM/Utils/Manager/TaskManager.php b/src/ZM/Utils/Manager/TaskManager.php deleted file mode 100644 index a38a48d7..00000000 --- a/src/ZM/Utils/Manager/TaskManager.php +++ /dev/null @@ -1,26 +0,0 @@ -setting['task_worker_num'])) { - logger()->warning(zm_internal_errcode('E00056') . '未开启 TaskWorker 进程,请先修改 global 配置文件启用!'); - return false; - } - $r = server()->taskwait(['task' => $task_name, 'params' => $params], $timeout); - return $r === false ? false : $r['result']; - } -} diff --git a/src/ZM/Utils/Manager/WorkerManager.php b/src/ZM/Utils/Manager/WorkerManager.php deleted file mode 100644 index c881b56e..00000000 --- a/src/ZM/Utils/Manager/WorkerManager.php +++ /dev/null @@ -1,144 +0,0 @@ -debug('Adding short command ' . $data['data'][0]); - $obj = new CQCommand(); - $obj->method = quick_reply_closure($data['data'][1]); - $obj->match = $data['data'][0]; - $obj->class = ''; - EventManager::addEvent(CQCommand::class, $obj); - break; - case 'eval': - eval($data['data']); - break; - case 'call_static': - call_user_func_array([$data['data']['class'], $data['data']['method']], $data['data']['params']); - break; - case 'save_persistence': - LightCache::savePersistence(); - break; - case 'resume_ws_message': - $obj = $data['data']; - Coroutine::resume($obj['coroutine']); - break; - case 'getWorkerCache': - $r = WorkerCache::get($data['key']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'setWorkerCache': - $r = WorkerCache::set($data['key'], $data['value']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'unsetWorkerCache': - $r = WorkerCache::unset($data['key']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'hasKeyWorkerCache': - $r = WorkerCache::hasKey($data['key'], $data['subkey']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'asyncAddWorkerCache': - WorkerCache::add($data['key'], $data['value'], true); - break; - case 'asyncSubWorkerCache': - WorkerCache::sub($data['key'], $data['value'], true); - break; - case 'asyncSetWorkerCache': - WorkerCache::set($data['key'], $data['value'], true); - break; - case 'asyncUnsetWorkerCache': - WorkerCache::unset($data['key'], true); - break; - case 'addWorkerCache': - $r = WorkerCache::add($data['key'], $data['value']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'subWorkerCache': - $r = WorkerCache::sub($data['key'], $data['value']); - $action = ['action' => 'returnWorkerCache', 'cid' => $data['cid'], 'value' => $r]; - $server->sendMessage(json_encode($action, 256), $src_worker_id); - break; - case 'returnWorkerCache': - WorkerCache::$transfer[$data['cid']] = $data['value']; - zm_resume($data['cid']); - break; - default: - $dispatcher = new EventDispatcher(OnPipeMessageEvent::class); - $dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) { - return $v->action == $data['action']; - }); - $dispatcher->dispatchEvents($data); - break; - } - } - - /** - * 给 Worker 进程发送动作指令(包括自身,自身将直接执行) - * @param int $worker_id 进程ID - * @param string $action 动作 - * @param mixed $data 参数 - * @throws Exception - */ - public static function sendActionToWorker(int $worker_id, string $action, $data) - { - $obj = ['action' => $action, 'data' => $data]; - if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) { - logger()->warning(zm_internal_errcode('E00022') . 'Cannot send worker action from master or manager process!'); - return; - } - if (server()->worker_id == $worker_id) { - self::workerAction($worker_id, $obj); - } else { - server()->sendMessage(json_encode($obj), $worker_id); - } - } - - /** - * 向所有 Worker 进程发送动作指令 - */ - public static function resumeAllWorkerCoroutines() - { - if (server()->worker_id === -1) { - logger()->warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!"); - return; - } - foreach ((LightCacheInside::get('wait_api', 'wait_api') ?? []) as $v) { - if (isset($v['coroutine'], $v['worker_id'])) { - if (server()->worker_id == $v['worker_id']) { - Coroutine::resume($v['coroutine']); - } - } - } - } -} diff --git a/src/ZM/Utils/MessageUtil.php b/src/ZM/Utils/MessageUtil.php deleted file mode 100644 index 53869957..00000000 --- a/src/ZM/Utils/MessageUtil.php +++ /dev/null @@ -1,348 +0,0 @@ -warning(zm_internal_errcode('E00059') . '指定的路径错误不存在!'); - return false; - } - $files = []; - $cq = CQ::getAllCQ($msg, true); - foreach ($cq as $v) { - if ($v->type == 'image') { - $result = ZMRequest::downloadFile($v->params['url'], $path . '/' . $v->params['file']); - if ($result === false) { - logger()->warning(zm_internal_errcode('E00060') . '图片 ' . $v->params['url'] . ' 下载失败!'); - return false; - } - $files[] = $path . '/' . $v->params['file']; - } - } - return $files; - } - - /** - * 检查消息中是否含有图片 CQ 码 - * @param array|string $msg 消息或消息数组 - */ - public static function containsImage($msg): bool - { - $cq = CQ::getAllCQ($msg, true); - foreach ($cq as $v) { - if ($v->type == 'image') { - return true; - } - } - return false; - } - - public static function isAtMe($msg, $me_id): bool - { - return strpos($msg, CQ::at($me_id)) !== false; - } - - /** - * 通过本地地址返回图片的 CQ 码 - * type == 0 : 返回图片的 base64 CQ 码 - * type == 1 : 返回图片的 file://路径 CQ 码(路径必须为绝对路径) - * type == 2 : 返回图片的 http://xxx CQ 码(默认为 /images/ 路径就是文件对应所在的目录) - * @param string $file 文件数据 - * @param int $type 文件类型(0,1,2可选,默认为0) - */ - public static function getImageCQFromLocal(string $file, int $type = 0): string - { - switch ($type) { - case 0: - return CQ::image('base64://' . base64_encode(file_get_contents($file))); - case 1: - return CQ::image('file://' . $file); - case 2: - $info = pathinfo($file); - return CQ::image(ZMConfig::get('global', 'http_reverse_link') . '/images/' . $info['basename']); - } - return ''; - } - - /** - * 分割字符,将用户消息通过空格或换行分割为数组 - * @param string $msg 消息内容 - * @return array|string[] - */ - public static function splitCommand(string $msg): array - { - $word = explode_msg(str_replace("\r", '', $msg)); - if (empty($word)) { - $word = ['']; - } - if (count(explode("\n", $word[0])) >= 2) { - $enter = explode("\n", $msg); - $first = split_explode(' ', array_shift($enter)); - $word = array_merge($first, $enter); - foreach ($word as $k => $v) { - $word[$k] = trim($v); - } - } - return $word; - } - - /** - * 根据CQCommand的规则匹配消息,获取是否匹配到对应的注解事件 - * @param array|string $msg 消息内容 - * @param array|Iterator $obj 数据对象 - */ - public static function matchCommand($msg, $obj): MatchResult - { - $ls = EventManager::$events[CQCommand::class] ?? []; - if (is_array($msg)) { - $msg = self::arrayToStr($msg); - } - $word = self::splitCommand($msg); - $matched = new MatchResult(); - foreach ($ls as $v) { - if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], ['']) == []) { - continue; - } - if (($v->user_id == 0 || ($v->user_id == $obj['user_id'])) - && ($v->group_id == 0 || ($v->group_id == ($obj['group_id'] ?? 0))) - && ($v->message_type == '' || ($v->message_type == $obj['message_type'])) - ) { - if (($word[0] != '' && $v->match == $word[0]) || in_array($word[0], $v->alias)) { - array_shift($word); - $matched->match = $word; - $matched->object = $v; - $matched->status = true; - break; - } - if ($v->start_with != '' && mb_substr($msg, 0, mb_strlen($v->start_with)) === $v->start_with) { - $matched->match = [mb_substr($msg, mb_strlen($v->start_with))]; - $matched->object = $v; - $matched->status = true; - break; - } - if ($v->end_with != '' && mb_substr($msg, 0 - mb_strlen($v->end_with)) === $v->end_with) { - $matched->match = [substr($msg, 0, strripos($msg, $v->end_with))]; - $matched->object = $v; - $matched->status = true; - break; - } - if ($v->keyword != '' && mb_strpos($msg, $v->keyword) !== false) { - $matched->match = explode($v->keyword, $msg); - $matched->object = $v; - $matched->status = true; - break; - } - if ($v->pattern != '') { - $match = match_args($v->pattern, $msg); - if ($match !== false) { - $matched->match = $match; - $matched->object = $v; - $matched->status = true; - break; - } - } elseif ($v->regex != '') { - if (preg_match('/' . $v->regex . '/u', $msg, $word2) != 0) { - $matched->match = $word2; - $matched->object = $v; - $matched->status = true; - break; - } - } - } - } - return $matched; - } - - /** - * @param string $command 命令内容 - * @param string $reply 回复内容 - * @throws Exception - */ - public static function addShortCommand(string $command, string $reply) - { - for ($i = 0; $i < ZM_WORKER_NUM; ++$i) { - WorkerManager::sendActionToWorker($i, 'add_short_command', [$command, $reply]); - } - } - - /** - * 字符串转数组 - * @param string $msg 消息内容 - * @param bool $ignore_space 是否忽略空行 - * @param bool $trim_text 是否去除空格 - * @return array 返回数组 - */ - public static function strToArray(string $msg, bool $ignore_space = true, bool $trim_text = false): array - { - $arr = []; - while (($rear = mb_strstr($msg, '[CQ:')) !== false && ($end = mb_strstr($rear, ']', true)) !== false) { - // 把 [CQ: 前面的文字生成段落 - $front = mb_strstr($msg, '[CQ:', true); - // 如果去掉空格都还有文字,或者不去掉空格有字符,且不忽略空格,则生成段落,否则不生成 - if (($trim_front = trim($front)) !== '' || ($front !== '' && !$ignore_space)) { - $arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_front : $front)]]; - } - // 处理 CQ 码 - $content = mb_substr($end, 4); - $cq = explode(',', $content); - $object_type = array_shift($cq); - $object_params = []; - foreach ($cq as $v) { - $key = mb_strstr($v, '=', true); - $object_params[$key] = CQ::decode(mb_substr(mb_strstr($v, '='), 1), true); - } - $arr[] = ['type' => $object_type, 'data' => $object_params]; - $msg = mb_substr(mb_strstr($rear, ']'), 1); - } - if (($trim_msg = trim($msg)) !== '' || ($msg !== '' && !$ignore_space)) { - $arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_msg : $msg)]]; - } - return $arr; - } - - /** - * 数组转字符串 - * 纪念一下,这段代码完全由AI生成,没有人知道它是怎么写的,这句话是我自己写的,不知道是不是有人知道的 - * @author Copilot - */ - public static function arrayToStr(array $array): string - { - $str = ''; - foreach ($array as $v) { - if ($v['type'] == 'text') { - $str .= $v['data']['text']; - } else { - $str .= '[CQ:' . $v['type']; - foreach ($v['data'] as $key => $value) { - $str .= ',' . $key . '=' . CQ::encode($value, true); - } - $str .= ']'; - } - } - return $str; - } - - /** - * @throws WaitTimeoutException - */ - public static function checkArguments(string $class, string $method, array &$match): array - { - $iterator = new EventMapIterator($class, $method, CommandArgument::class); - $offset = 0; - $arguments = []; - foreach ($iterator as $annotation) { - /** @var CommandArgument $annotation */ - switch ($annotation->type) { - case 'string': - case 'any': - if (isset($match[$offset])) { - $arguments[$annotation->name] = $match[$offset++]; - } else { - if ($annotation->required) { - $value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout); - $arguments[$annotation->name] = $value; - } else { - $arguments[$annotation->name] = $annotation->default; - } - } - break; - case 'number': - for ($k = $offset; $k < count($match); ++$k) { - $v = $match[$k]; - if (is_numeric($v)) { - array_splice($match, $k, 1); - $arguments[$annotation->name] = $v / 1; - break 2; - } - } - if (!$annotation->required) { - if (is_numeric($annotation->default)) { - $arguments[$annotation->name] = $annotation->default / 1; - } - } - if (!isset($arguments[$annotation->name])) { - $value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout); - if (!is_numeric($value)) { - if ($annotation->error_prompt_policy === 1) { - $value = ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout); - if (!is_numeric($value)) { - throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt()); - } - } else { - throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt()); - } - } - $arguments[$annotation->name] = $value / 1; - } - break; - case 'bool': - for ($k = $offset; $k < count($match); ++$k) { - $v = strtolower($match[$k]); - if (in_array(strtolower($v), TRUE_LIST)) { - array_splice($match, $k, 1); - $arguments[$annotation->name] = true; - break 2; - } - if (in_array(strtolower($v), FALSE_LIST)) { - array_splice($match, $k, 1); - $arguments[$annotation->name] = false; - break 2; - } - } - if (!$annotation->required) { - $default = $annotation->default === '' ? true : 'true'; - $arguments[$annotation->name] = in_array($default, TRUE_LIST); - } - if (!isset($arguments[$annotation->name])) { - $value = strtolower(ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout)); - if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) { - if ($annotation->error_prompt_policy === 1) { - $value = strtolower(ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout)); - if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) { - throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt()); - } - } else { - throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt()); - } - } - $arguments[$annotation->name] = in_array($value, TRUE_LIST); - } - break; - } - } - container()->instance(InputArguments::class, new InputArguments($arguments)); - return $arguments; - } -} diff --git a/src/ZM/Utils/ReflectionUtil.php b/src/ZM/Utils/ReflectionUtil.php deleted file mode 100644 index 34a288a7..00000000 --- a/src/ZM/Utils/ReflectionUtil.php +++ /dev/null @@ -1,123 +0,0 @@ -getType(); - // 没有声明类型或为基本类型 - if (!$type instanceof ReflectionNamedType || $type->isBuiltin()) { - return null; - } - - // 获取类名 - $class_name = $type->getName(); - - // 如果存在父类 - if (!is_null($class = $parameter->getDeclaringClass())) { - if ($class_name === 'self') { - return $class->getName(); - } - - if ($class_name === 'parent' && $parent = $class->getParentClass()) { - return $parent->getName(); - } - } - - return $class_name; - } - - /** - * 将传入变量转换为字符串 - * - * @param mixed $var - */ - public static function variableToString($var): string - { - switch (true) { - case is_callable($var): - if (is_array($var)) { - if (is_object($var[0])) { - return get_class($var[0]) . '@' . $var[1]; - } - return $var[0] . '::' . $var[1]; - } - return 'closure'; - case is_string($var): - return $var; - case is_array($var): - return 'array' . json_encode($var); - case is_object($var): - return get_class($var); - case is_resource($var): - return 'resource(' . get_resource_type($var) . ')'; - case is_null($var): - return 'null'; - case is_bool($var): - return $var ? 'true' : 'false'; - case is_float($var): - case is_int($var): - return (string) $var; - default: - return 'unknown'; - } - } - - /** - * 判断传入的回调是否为任意类的非静态方法 - * - * @param callable|string $callback 回调 - * @throws ReflectionException - */ - public static function isNonStaticMethod($callback): bool - { - if (is_array($callback) && is_string($callback[0])) { - $reflection = new ReflectionMethod($callback[0], $callback[1]); - return !$reflection->isStatic(); - } - return false; - } - - /** - * 获取传入的回调的反射实例 - * - * 如果传入的是类方法,则会返回 {@link ReflectionMethod} 实例 - * 否则将返回 {@link ReflectionFunction} 实例 - * - * 可传入实现了 __invoke 的类 - * - * @param callable|string $callback 回调 - * @throws ReflectionException - */ - public static function getCallReflector($callback): ReflectionFunctionAbstract - { - if (is_string($callback) && str_contains($callback, '::')) { - $callback = explode('::', $callback); - } elseif (is_object($callback) && !$callback instanceof Closure) { - $callback = [$callback, '__invoke']; - } - - return is_array($callback) - ? new ReflectionMethod($callback[0], $callback[1]) - : new ReflectionFunction($callback); - } -} diff --git a/src/ZM/Utils/SignalListener.php b/src/ZM/Utils/SignalListener.php deleted file mode 100644 index fbf2cc05..00000000 --- a/src/ZM/Utils/SignalListener.php +++ /dev/null @@ -1,110 +0,0 @@ -debug('正在监听 Master 进程 SIGINT'); - Process::signal(SIGINT, function () use ($server) { - if (zm_atomic('_int_is_reload')->get() === 1) { - zm_atomic('_int_is_reload')->set(0); - $server->reload(); - } else { - echo "\r"; - logger()->notice('Master 进程收到中断信号 SIGINT'); - logger()->notice('正在停止服务器'); - Process::kill($server->master_pid, SIGTERM); - } - }); - } - - /** - * 监听Manager进程的Ctrl+C - */ - public static function signalManager() - { - $func = function () { - if (\server()->master_pid == \server()->manager_pid) { - echo "\r"; - logger()->notice('Manager 进程收到中断信号 SIGINT'); - swoole_timer_after(2, function () { - Process::kill(posix_getpid(), SIGTERM); - }); - } else { - logger()->debug('Manager 已中断'); - } - self::processKillerPrompt(); - }; - logger()->debug('正在监听 Manager 进程 SIGINT'); - if (version_compare(SWOOLE_VERSION, '4.6.7') >= 0) { - Process::signal(SIGINT, $func); - } elseif (extension_loaded('pcntl')) { - pcntl_signal(SIGINT, $func); - } - } - - /** - * 监听Worker/TaskWorker进程的Ctrl+C - * - * @param int $worker_id 当前进程的ID - */ - public static function signalWorker(Server $server, int $worker_id) - { - logger()->debug('正在监听 Worker#{worker_id} 进程 SIGINT', compact('worker_id')); - Process::signal(SIGINT, function () use ($server) { - if ($server->master_pid === $server->worker_pid) { // 当Swoole以单进程模型运行的时候,Worker需要监听杀死的信号 - echo "\r"; - logger()->notice('Worker 进程收到中断信号 SIGINT'); - swoole_timer_after(2, function () { - Process::kill(posix_getpid(), SIGTERM); - }); - self::processKillerPrompt(); - } - // logger()->debug("Interrupted in worker"); - // do nothing - }); - } - - /** - * 按5次Ctrl+C后强行杀死框架的处理函数 - */ - private static function processKillerPrompt() - { - if (self::$manager_kill_time > 0) { - if (self::$manager_kill_time >= 5) { - $file_path = _zm_pid_dir(); - $flist = DataProvider::scanDirFiles($file_path, false, true); - foreach ($flist as $file) { - $name = explode('.', $file); - if (end($name) == 'pid' && $name[0] !== 'manager') { - $pid = file_get_contents($file_path . '/' . $file); - Process::kill((int) $pid, SIGKILL); - } - unlink($file_path . '/' . $file); - } - } else { - echo "\r"; - logger()->notice('请再按 {count} 次 Ctrl+C 以强制杀死所有 Worker 进程', ['count' => 5 - self::$manager_kill_time]); - } - } - ++self::$manager_kill_time; - } -} diff --git a/src/ZM/Utils/SingletonTrait.php b/src/ZM/Utils/SingletonTrait.php deleted file mode 100644 index cfe8d81b..00000000 --- a/src/ZM/Utils/SingletonTrait.php +++ /dev/null @@ -1,34 +0,0 @@ -setRuleFunction(function ($v) use ($it) { - /* @var TerminalCommand $v */ - return !empty($it) && ($v->command == $it[0] || $v->alias == $it[0]); - }); - $dispatcher->setReturnFunction(function () { - EventDispatcher::interrupt('none'); - }); - $dispatcher->dispatchEvents($it); - if ($dispatcher->store !== 'none' && $cmd !== '') { - logger()->info('Command not found: ' . $cmd); - return true; - } - return false; - } - - public static function log($type, $log_msg) - { - ob_start(); - if (!in_array($type, ['log', 'info', 'debug', 'success', 'warning', 'error', 'verbose'])) { - ob_get_clean(); - return; - } - Console::$type($log_msg); - $r = ob_get_clean(); - $all = ManagerGM::getAllByName('terminal'); - foreach ($all as $v) { - server()->send($v->getFd(), "\r" . $r); - server()->send($v->getFd(), '>>> '); - } - } - - public static function init() - { - logger()->debug('Initializing Terminal...'); - foreach ((EventManager::$events[TerminalCommand::class] ?? []) as $v) { - if ($v->command == 'help') { - self::$default_commands = true; - break; - } - } - $class = new Terminal(); - $reader = new AnnotationReader(); - $reflection = new ReflectionClass($class); - foreach ($reflection->getMethods() as $v) { - $r = $reader->getMethodAnnotation($v, TerminalCommand::class); - if ($r !== null) { - logger()->debug('adding command ' . $r->command); - $r->class = Terminal::class; - $r->method = $v->getName(); - EventManager::addEvent(TerminalCommand::class, $r); - } - } - self::$default_commands = true; - } - - /** - * @TerminalCommand(command="help",alias="h",description="显示帮助菜单") - */ - public function help() - { - $help = []; - foreach ((EventManager::$events[TerminalCommand::class] ?? []) as $v) { - /** @var TerminalCommand $v */ - $cmd = $v->command . ($v->alias !== '' ? (' | ' . $v->alias) : ''); - $painted = Console::setColor($v->command, 'green') . ($v->alias !== '' ? (' | ' . Console::setColor($v->alias, 'green')) : ''); - $help[] = $painted . ':' . str_pad('', 16 - strlen($cmd) - 1) . ($v->description === '' ? '<无描述>' : $v->description); - } - echo implode("\n", $help) . PHP_EOL; - } - - /** - * @TerminalCommand(command="status",description="显示Swoole Server运行状态(需要安装league/climate组件)") - */ - public function status() - { - if (class_exists('\\League\\CLImate\\CLImate')) { - $class = '\\League\\CLImate\\CLImate'; - $climate = new $class(); - $climate->output->addDefault('buffer'); - - $objs = server()->stats(); - $climate->columns($objs); - $obj = $climate->output->get('buffer')->get(); - $climate->output->get('buffer')->clean(); - echo $obj; - return; - } - logger()->warning('你还没有安装 league/climate 组件,无法使用此功能!'); - } - - /** - * @TerminalCommand(command="logtest",description="测试log的显示等级") - */ - public function testlog() - { - foreach (['debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'] as $level) { - logger()->log($level, 'This is a {level} message.', compact('level')); - } - } - - /** - * @TerminalCommand(command="call",description="用于执行不需要参数的动态函数,比如 `call \Module\Example\Hello hitokoto`") - */ - public function call(array $it) - { - $class_name = $it[1]; - $function_name = $it[2]; - $class = new $class_name([]); - $r = $class->{$function_name}(); - if (is_string($r)) { - Console::success($r); - } - } - - /** - * @TerminalCommand(command="level",description="设置log等级,例如 `level 0|1|2|3|4`") - */ - public function level(array $it) - { - logger()->warning('Sorry, this function is not available yet.'); -// $level = intval(is_numeric($it[1] ?? 99) ? ($it[1] ?? 99) : 99); -// if ($level > 4 || $level < 0) { -// Console::warning("Usage: 'level 0|1|2|3|4'"); -// } else { -// Console::setLevel($level) || Console::success('Success!!'); -// } - } - - /** - * @TerminalCommand(command="bc",description="eval执行代码,但输入必须是将代码base64之后的,如 `bc em1faW5mbygn5L2g5aW9Jyk7`") - */ - public function bc(array $it) - { - $code = base64_decode($it[1] ?? '', true); - try { - eval($code); - } catch (Exception $e) { - } - } - - /** - * @TerminalCommand(command="echo",description="输出内容,用法:`echo hello`") - */ - public function echoI(array $it) - { - logger()->info($it[1]); - } - - /** - * @TerminalCommand(command="stop",description="停止框架") - */ - public function stop() - { - posix_kill(server()->master_pid, SIGTERM); - } - - /** - * @TerminalCommand(command="reload",alias="r",description="重启框架(重载用户代码)") - */ - public function reload() - { - Process::kill(server()->master_pid, SIGUSR1); - } -} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 63f7877a..f53d33a5 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -4,133 +4,6 @@ declare(strict_types=1); namespace ZM\Utils; -use Exception; -use Psr\Log\LogLevel; -use Swoole\Process; -use ZM\Framework; -use ZM\Store\Lock\SpinLock; -use ZM\Store\ZMAtomic; -use function file_get_contents; -use function get_included_files; -use function is_callable; -use function is_string; -use function json_decode; -use function mb_substr; -use function md5_file; -use function pathinfo; -use function server; -use function str_replace; - class ZMUtil { - /** - * @param mixed $error_exit - * @throws Exception - */ - public static function stop($error_exit = false) - { - if (SpinLock::tryLock('_stop_signal') === false) { - return; - } - logger()->notice('正在停止服务器...'); - if (zm_config('logging.level') === LogLevel::DEBUG) { - debug_print_backtrace(); - } - ZMAtomic::get('stop_signal')->set($error_exit ? 2 : 1); - server()->shutdown(); - } - - /** - * @throws Exception - */ - public static function reload() - { - Process::kill(server()->master_pid, SIGUSR1); - } - - public static function getModInstance($class) - { - return resolve($class); - } - - /** - * 在工作进程中返回可以通过reload重新加载的php文件列表 - * @return string[]|string[][] - */ - public static function getReloadableFiles(): array - { - $array_map = []; - foreach (array_diff( - get_included_files(), - Framework::$loaded_files - ) as $key => $x) { - $array_map[$key] = str_replace(DataProvider::getSourceRootDir() . '/', '', $x); - } - return $array_map; - } - - /** - * 使用Psr-4标准获取目录下的所有类 - * @param string $dir 目录 - * @param string $base_namespace 基础命名空间 - * @param null|mixed $rule 规则 - * @param bool|string $return_path_value 是否返回文件路径,返回文件路径的话传入字符串 - * @return string[] - */ - public static function getClassesPsr4(string $dir, string $base_namespace, $rule = null, $return_path_value = false): array - { - // 预先读取下composer的file列表 - $composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true); - $classes = []; - // 扫描目录,使用递归模式,相对路径模式,因为下面此路径要用作转换成namespace - $files = DataProvider::scanDirFiles($dir, true, true); - foreach ($files as $v) { - $pathinfo = pathinfo($v); - if (($pathinfo['extension'] ?? '') == 'php') { - $path = rtrim($dir, '/') . '/' . rtrim($pathinfo['dirname'], './') . '/' . $pathinfo['basename']; - - // 过滤不包含类的文件 - $tokens = token_get_all(file_get_contents($path)); - $found = false; - foreach ($tokens as $token) { - if (!is_array($token)) { - continue; - } - if ($token[0] === T_CLASS) { - $found = true; - } - } - if (!$found) { - continue; - } - - if ($rule === null) { // 规则未设置回调时候,使用默认的识别过滤规则 - /*if (substr(file_get_contents($dir . '/' . $v), 6, 6) == '#plain') { - continue; - }*/ - if (file_exists($dir . '/' . $pathinfo['basename'] . '.plain')) { - continue; - } - if (mb_substr($pathinfo['basename'], 0, 7) == 'global_' || mb_substr($pathinfo['basename'], 0, 7) == 'script_') { - continue; - } - foreach (($composer['autoload']['files'] ?? []) as $fi) { - if (md5_file(DataProvider::getSourceRootDir() . '/' . $fi) == md5_file($dir . '/' . $v)) { - continue 2; - } - } - } elseif (is_callable($rule) && !($rule($dir, $pathinfo))) { - continue; - } - $dirname = $pathinfo['dirname'] == '.' ? '' : (str_replace('/', '\\', $pathinfo['dirname']) . '\\'); - $class_name = $base_namespace . '\\' . $dirname . $pathinfo['filename']; - if (is_string($return_path_value)) { - $classes[$class_name] = $return_path_value . '/' . $v; - } else { - $classes[] = $class_name; - } - } - } - return $classes; - } } diff --git a/src/ZM/ZMServer.php b/src/ZM/ZMServer.php deleted file mode 100644 index 0df2a012..00000000 --- a/src/ZM/ZMServer.php +++ /dev/null @@ -1,74 +0,0 @@ -app_name = $app_name; - } - - /** - * @param mixed $module_class - */ - public function addModule($module_class) - { - $this->modules[] = $module_class; - } - - /** - * @throws InitException - */ - public function run() - { - Console::setLevel(4); - foreach ($this->modules as $module_class) { - foreach ($module_class->getEvents() as $event) { - EventManager::addEvent(get_class($event), $event); - } - } - echo "Running...\n"; - if (defined('WORKING_DIR')) { - throw new InitException(); - } - - _zm_env_check(); - - define('WORKING_DIR', getcwd()); - define('SOURCE_ROOT_DIR', WORKING_DIR); - define('LOAD_MODE', is_dir(SOURCE_ROOT_DIR . '/src/ZM') ? 0 : 1); - define('FRAMEWORK_ROOT_DIR', realpath(__DIR__ . '/../../')); - define('ZM_VERSION_ID', Framework::VERSION_ID); - define('ZM_VERSION', Framework::VERSION); - $options = array_map(function ($x) { - return $x->getDefault(); - }, ServerStartCommand::exportDefinition()->getOptions()); - (new Framework($options, true))->start(); - } - - public function getAppName(): string - { - return $this->app_name; - } -} diff --git a/src/ZM/global_defines.php b/src/ZM/global_defines.php deleted file mode 100644 index 9d9dd956..00000000 --- a/src/ZM/global_defines.php +++ /dev/null @@ -1,37 +0,0 @@ -= 4.5.0 !'); - } - if (version_compare(PHP_VERSION, '7.2') === -1) { - exit(zm_internal_errcode('E00003') . 'PHP >= 7.2 required.'); - } - if (version_compare(SWOOLE_VERSION, '4.6.7') < 0 && !extension_loaded('pcntl')) { - Console::error(zm_internal_errcode('E00004') . 'Swoole 版本必须不低于 4.6.7 或 PHP 安装加载了 pcntl 扩展!'); - exit(); - } -} - -/** - * 分割消息字符串 - * - * @param array $includes 需要进行切割的字符串,默认包含空格及制表符(\t) - */ -function explode_msg(string $msg, array $includes = [' ', "\t"]): array -{ - $msg = trim($msg); - foreach ($includes as $v) { - $msg = str_replace($v, "\n", $msg); - } - $msg_seg = explode("\n", $msg); - $ls = []; - foreach ($msg_seg as $v) { - if (empty(trim($v))) { - continue; - } - $ls[] = trim($v); - } - return $ls; -} - -/** - * 解码Unicode字符串 - */ -function unicode_decode(string $str): ?string -{ - return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', static function ($matches) { - return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UCS-2BE'); - }, $str); -} - -/** - * 格式匹配 - */ -function match_pattern(string $pattern, string $subject): bool -{ - $pattern = str_replace(['\*', '\\\\.*'], ['.*', '\*'], preg_quote($pattern, '/')); - $pattern = '/^' . $pattern . '$/i'; - return preg_match($pattern, $subject) === 1; -} - -/** - * @requires symfony/polyfill-ctype - * @return array|string[] - */ -function split_explode(string $del, string $string, bool $divide_en = false): array -{ - $str = explode($del, $string); - for ($i = 0, $i_max = mb_strlen($str[0]); $i < $i_max; ++$i) { - if ( - is_numeric(mb_substr($str[0], $i, 1)) - && ( - !is_numeric(mb_substr($str[0], $i - 1, 1)) - && mb_substr($str[0], $i - 1, 1) !== ' ' - && ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false - ) - ) { - $str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i); - } elseif ( - $divide_en - && mb_substr($str[0], $i - 1, 1) !== ' ' - && ctype_alnum(mb_substr($str[0], $i, 1)) - && !ctype_alnum(mb_substr($str[0], $i - 1, 1)) - ) { - $str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i); - } - } - $str = implode($del, $str); - - $ls = []; - foreach (explode($del, $str) as $v) { - if (empty(trim($v))) { - continue; - } - $ls[] = $v; - } - return $ls ?: ['']; -} - -/** - * 匹配参数 - * - * @return array|false 成功时返回匹配到的参数数组,失败时返回false - */ -function match_args(string $pattern, string $subject) -{ - $result = []; - if (match_pattern($pattern, $subject)) { - if (mb_strpos($pattern, '*') === false) { - return []; - } - $exp = explode('*', $pattern); - $i = 0; - foreach ($exp as $k => $v) { - if (empty($v) && $k === 0) { - continue; - } - if (empty($v) && $k === count($exp) - 1) { - $subject .= '^EOL'; - $v = '^EOL'; - } - $cur_var = ''; - $ori = $i; - while (($a = mb_substr($subject, $i, mb_strlen($v))) !== $v && !empty($a)) { - $cur_var .= mb_substr($subject, $i, 1); - ++$i; - } - if ($i !== $ori || $k === 1 || $k === count($exp) - 1) { - $result[] = $cur_var; - } - $i += mb_strlen($v); - } - return $result; - } - return false; -} - -/** - * 判断当前连接类型是否为传入的$type - * - * @param string $type 连接类型 - */ -function current_connection_is(string $type): bool -{ - return ctx()->getConnection()->getName() === $type; -} - -/** - * 获取触发当前方法的注解 - */ -function get_annotations(): array -{ - $s = debug_backtrace()[1]; - $list = []; - foreach (EventManager::$events as $v) { - foreach ($v as $vs) { - if ($vs->class === $s['class'] && $vs->method === $s['function']) { - $list[get_class($vs)][] = $vs; - } - } - } - return $list; -} - -/** - * 设置协程参数 - */ -function set_coroutine_params(array $params): void -{ - $cid = co::getCid(); - if ($cid === -1) { - exit(zm_internal_errcode('E00061') . 'Cannot set coroutine params at none coroutine mode.'); - } - if (isset(Context::$context[$cid])) { - Context::$context[$cid] = array_merge(Context::$context[$cid], $params); - } else { - Context::$context[$cid] = $params; - } - foreach (Context::$context as $c => $v) { - if (!co::exists($c)) { - unset(Context::$context[$c], ZMBuf::$context_class[$c]); - } - } -} - -/** - * 获取当前上下文 - */ -function context(): ContextInterface -{ - return ctx(); -} - -/** - * 获取当前上下文 - */ -function ctx(): ContextInterface -{ - $cid = co::getCid(); - $c_class = ZMConfig::get('global', 'context_class'); - if (isset(Context::$context[$cid])) { - return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); - } - logger()->debug("未找到当前协程的上下文({$cid}),正在找父进程的上下文"); - while (($parent_cid = co::getPcid($cid)) !== -1) { - $cid = $parent_cid; - if (isset(Context::$context[$cid])) { - return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); - } - } - logger()->warning('当前环境不是协程环境,将返回独立的非协程的容器'); - return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid)); -} - -/** - * 根据消息类型获取对应的OneBot目标名称 - * - * @return string 如传入的消息类型不被支持,将默认返回`user_id` - */ -function get_onebot_target_id_name(string $message_type): string -{ - switch ($message_type) { - case 'group': - return 'group_id'; - case 'discuss': - return 'discuss_id'; - case 'private': - default: - return 'user_id'; - } -} - -/** - * 协程休眠 - * - * 与 {@link sleep()} 一致,只是增加了协程支持 - * - * @since 2.7.3 此函数不再返回 true - */ -function zm_sleep(int $seconds = 1): void -{ - if (Coroutine::getCid() !== -1) { - System::sleep($seconds); - } else { - usleep($seconds * 1000 * 1000); - } -} - -/** - * 协程执行命令 - * - * 与 {@link exec()} 一致,只是增加了协程支持 - * - * @return array{code: int, signal: int, output: string} - */ -function zm_exec(string $command): array -{ - return System::exec($command); -} - -/** - * 获取当前协程ID - * - * 与 {@link Co::getCid()} 一致 - */ -function zm_cid(): int -{ - return co::getCid(); -} - -/** - * 挂起当前协程 - * - * 与 {@link Co::yield()} 一致 - */ -function zm_yield() -{ - co::yield(); -} - -/** - * 恢复并继续执行指定协程 - * - * 与 {@link Co::resume()} 一致 - */ -function zm_resume(int $cid) -{ - co::resume($cid); -} - -/** - * 指定延迟后执行函数 - * - * @param int $delay 延迟时间,单位毫秒ms - */ -function zm_timer_after(int $delay, callable $runnable) -{ - Swoole\Timer::after($delay, static function () use ($runnable) { - call_with_catch($runnable); - }); -} - -/** - * 重复在指定时间间隔后执行函数 - * - * @param int $interval 间隔时间,单位毫秒ms - * @return false|int 定时器ID,失败返回false - */ -function zm_timer_tick(int $interval, callable $runnable) -{ - if (zm_cid() === -1) { - return go(static function () use ($interval, $runnable) { - logger()->debug('Adding extra timer tick of ' . $interval . ' ms'); - Swoole\Timer::tick($interval, static function () use ($runnable) { - call_with_catch($runnable); - }); - }); - } - - return Swoole\Timer::tick($interval, static function () use ($runnable) { - call_with_catch($runnable); - }); -} - -/** - * 执行函数并记录异常 - */ -function call_with_catch(callable $callable): void -{ - try { - $callable(); - } catch (Exception $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00033') . 'Uncaught exception ' . get_class($e) . ': ' . $error_msg); - Console::trace(); - } catch (Error $e) { - $error_msg = $e->getMessage() . ' at ' . $e->getFile() . '(' . $e->getLine() . ')'; - Console::error(zm_internal_errcode('E00033') . 'Uncaught ' . get_class($e) . ': ' . $error_msg); - Console::trace(); - } -} - -/** - * 生成消息的哈希值 - */ -function hash_message(array $message): string -{ - return md5($message['user_id'] . '^' . $message['self_id'] . '^' . $message['message_type'] . '^' . ($message[$message['message_type'] . '_id'] ?? $message['user_id'])); -} - -/** - * 获取 Swoole Server 实例 - * - * 与 {@link Framework::$server} 一致 - */ -function server(): Server -{ - return Framework::$server; -} - -/** - * 获取缓存当前框架pid的临时目录 - * - * @internal - */ -function _zm_pid_dir(): string -{ - global $_ZM_HASH; - if (!isset($_ZM_HASH)) { - $_ZM_HASH = md5(DataProvider::getWorkingDir()); - } - return '/tmp/.zm_' . $_ZM_HASH; -} - -/** - * 获取 ZMRobot 实例 - * - * 随机返回一个 ZMRobot 实例,效果等同于 {@link ZMRobot::getRandom()}。 - * - * 在单机器人模式下,会直接返回该机器人实例。 - * - * @throws RobotNotFoundException - */ -function bot(): ZMRobot -{ - if (($conn = LightCacheInside::get('connect', 'conn_fd')) === -2) { - return OneBotV11::getRandom(); - } - if ($conn !== -1) { - if (($obj = ManagerGM::get($conn)) !== null) { - return new ZMRobot($obj); - } - throw new RobotNotFoundException('单机器人连接模式可能连接了多个机器人!'); - } - throw new RobotNotFoundException('没有任何机器人连接到框架!'); -} - -/** - * 获取指定连接类型的文件描述符ID - */ -function get_all_fd_of_type(string $type = 'default'): array -{ - $fds = []; - foreach (ManagerGM::getAllByName($type) as $obj) { - $fds[] = $obj->getFd(); - } - return $fds; -} - -/** - * 获取原子计数器 - * - * 与 {@link ZMAtomic::get()} 一致 - */ -function zm_atomic(string $name): ?Atomic -{ - return ZMAtomic::get($name); -} - -/** - * 生成 UUID - */ -function uuidgen(bool $uppercase = false): string -{ - try { - $data = random_bytes(16); - } catch (Exception $e) { - return ''; - } - $data[6] = chr(ord($data[6]) & 0x0F | 0x40); - $data[8] = chr(ord($data[8]) & 0x3F | 0x80); - return $uppercase ? strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))) : - vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); -} - -/** - * 获取框架运行的工作目录 - * - * @example 例如你是从 /root/framework-starter/ 目录启动的框架,vendor/bin/start server,那么 working_dir() 返回的就是 /root/framework-starter。 - */ -function working_dir(): string -{ - return WORKING_DIR; -} - -/** - * 更漂亮地输出变量值 - * - * 可替代 {@link var_dump()} - * - * @param mixed $var - * @return array|mixed 返回传入的变量,传入多个变量则会返回数组 - */ -function zm_dump($var, ...$moreVars) -{ - VarDumper::dump($var); - - foreach ($moreVars as $v) { - VarDumper::dump($v); - } - - if (func_num_args() > 1) { - return func_get_args(); - } - - return $var; -} - -/** - * 输出info日志 - * - * 与 {@link logger()->info()} 一致 - * - * @param mixed $obj - */ -function zm_info($obj): void -{ - logger()->info($obj); -} - -/** - * 输出warning日志 - * - * 与 {@link Console::warning()} 一致 - * - * @param mixed $obj - */ -function zm_warning($obj): void -{ - logger()->warning($obj); -} - -/** - * 输出success日志 - * - * 与 {@link Console::success()} 一致 - * - * @param mixed $obj - */ -function zm_success($obj): void -{ - throw new \RuntimeException('the success level logger has been deprecated.'); -} - -/** - * 输出debug日志 - * - * 与 {@link logger()->debug()} 一致 - * - * @param mixed $obj - */ -function zm_debug($obj): void -{ - logger()->debug($obj); -} - -/** - * 输出verbose日志 - * - * 与 {@link logger()->debug()} 一致 - * - * @param mixed $obj - */ -function zm_verbose($obj): void -{ - logger()->debug($obj); -} - -/** - * 输出error日志 - * - * 与 {@link Console::error()} 一致 - * - * @param mixed $obj - */ -function zm_error($obj): void -{ - logger()->error($obj); -} - -/** - * 获取配置项 - * - * 与 {@link ZMConfig::get()} 一致 - * - * @return mixed - */ -function zm_config(string $name, string $key = '') -{ - return ZMConfig::get($name, $key); -} - -/** - * 生成快速回复闭包 - * - * @param mixed $reply - */ -function quick_reply_closure($reply): Closure -{ - return static function () use ($reply) { - return $reply; - }; -} - -/** - * 获取内部错误码 - * - * @param int|string $code - */ -function zm_internal_errcode($code): string -{ - return "[ErrCode:{$code}] "; -} - -if (!function_exists('chain')) { - define('CARRY', '{carry}'); - - /** - * 链式调用对象方法 - * - * 如需使用上一步的返回值作为参数,请使用 CARRY 常量替换对应的参数值 - * - * @param object $object 需要进行链式调用的对象 - * @return object 链式操作对象,可当成传入的对象使用 - */ - function chain(object $object): object - { - return new class($object) { - /** - * 最后一次执行的方法的返回值 - * - * @var mixed - */ - protected $return; - - /** - * 需要进行链式调用的对象 - * - * @var object - */ - protected $wrapped; - - /** - * 构造链式调用匿名类 - */ - public function __construct(object $object) - { - $this->wrapped = $object; - } - - /** - * 代理所有调用 - */ - public function __call(string $name, array $arguments) - { - if (($index = array_search(CARRY, $arguments, true)) !== false) { - $arguments[$index] = $this->return; - } - $this->return = $this->wrapped->{$name}(...$arguments); - return $this; - } - - /** - * 返回最后执行结果 - */ - public function __toString() - { - return (string) $this->return; - } - - /** - * 允许调用最后返回结果 - */ - public function __invoke() - { - return $this->return; - } - - /** - * 使用链式操作对象作为参数,执行回调 - */ - public function tap(callable $callback): self - { - $callback($this->wrapped); - return $this; - } - }; - } -} - -if (!function_exists('stopwatch')) { - /** - * 执行回调函数并返回平均执行耗时 - * - * @param int $times 执行次数 - * @return float 平均执行耗时 - */ - function stopwatch(callable $callback, int $times = 1): float - { - $total = 0; - for ($i = 0; $i < $times; ++$i) { - $start = microtime(true); - $callback(); - $total += microtime(true) - $start; - } - return $total / $times; - } -} - -/** - * 将可能为数组的参数转换为字符串 - * - * 如传入字符串则为原样返回 - * - * @param array|string $string_or_array - */ -function implode_when_necessary($string_or_array): string -{ - return is_array($string_or_array) ? implode(', ', $string_or_array) : $string_or_array; -} - -/** - * 获取容器(请求级)实例 - */ -function container(): ContainerInterface -{ - return Container::getInstance(); -} - -/** - * 解析类实例(使用容器) - * - * @template T - * @param class-string $abstract - * @return Closure|mixed|T - */ -function resolve(string $abstract, array $parameters = []) -{ - return Container::getInstance()->make($abstract, $parameters); -} - -/** - * 获取容器实例 - * - * @template T - * @param null|class-string $abstract - * @return Closure|ContainerInterface|mixed|T - */ -function app(string $abstract = null, array $parameters = []) -{ - if (is_null($abstract)) { - return container(); - } - - return resolve($abstract, $parameters); -} - -/** - * 根据键名比较对象和数组 - * - * @param object $object 对象 - * @param array $array 数组 - * @param array $keys 键名 - */ -function compare_object_and_array_by_keys(object $object, array $array, array $keys): bool -{ - foreach ($keys as $key) { - if (!isset($object->{$key}, $array[$key]) || $object->{$key} !== $array[$key]) { - return false; - } - } - return true; -} - -/** - * 判断传入的数组是否为关联数组 - */ -function is_assoc_array(array $array): bool -{ - return !empty($array) && array_keys($array) !== range(0, count($array) - 1); -} - -/** - * 返回 Logger 实例 - */ -function logger(string $prefix = null): LoggerInterface -{ - return new ZM\Logger\ConsoleLogger('info'); -} - -/** - * 捕获输出 - */ -function capture_output(callable $callback, array $args = []): string -{ - ob_start(); - $callback(...$args); - return ob_get_clean(); -} diff --git a/src/ZM/script_phar_stub.php b/src/ZM/script_phar_stub.php deleted file mode 100644 index 3650b878..00000000 --- a/src/ZM/script_phar_stub.php +++ /dev/null @@ -1,21 +0,0 @@ - false]; diff --git a/src/ZM/script_setup_loader.php b/src/ZM/script_setup_loader.php deleted file mode 100644 index cae434b6..00000000 --- a/src/ZM/script_setup_loader.php +++ /dev/null @@ -1,72 +0,0 @@ -initEnv(); - } catch (InitException $e) { - } - $base_path = DataProvider::getSourceRootDir(); - $scan_paths = []; - $composer = json_decode(file_get_contents($base_path . '/composer.json'), true); - $exclude_annotations = array_merge($composer['extra']['exclude_annotate'] ?? [], $composer['extra']['zm']['exclude-annotation-path'] ?? []); - foreach (($composer['autoload']['psr-4'] ?? []) as $k => $v) { - if (is_dir($base_path . '/' . $v) && !in_array($v, $exclude_annotations)) { - $scan_paths[trim($k, '\\')] = $base_path . '/' . $v; - } - } - foreach (($composer['autoload-dev']['psr-4'] ?? []) as $k => $v) { - if (is_dir($base_path . '/' . $v) && !in_array($v, $exclude_annotations)) { - $scan_paths[trim($k, '\\')] = $base_path . '/' . $v; - } - } - $all_event_class = []; - foreach ($scan_paths as $namespace => $autoload_path) { - $all_event_class = array_merge($all_event_class, ZMUtil::getClassesPsr4($autoload_path, $namespace)); - } - - $reader = new DualReader(new AnnotationReader(), new AttributeReader()); - $event_list = []; - $setup_list = []; - foreach ($all_event_class as $v) { - $reflection_class = new ReflectionClass($v); - $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC); - foreach ($methods as $vs) { - $method_annotations = $reader->getMethodAnnotations($vs); - if ($method_annotations != []) { - $annotation = $method_annotations[0]; - if ($annotation instanceof SwooleHandler) { - $event_list[] = [ - 'class' => $v, - 'method' => $vs->getName(), - 'event' => $annotation->event, - ]; - } elseif ($annotation instanceof OnSetup) { - $setup_list[] = [ - 'class' => $v, - 'method' => $vs->getName(), - ]; - } - } - } - } - echo json_encode(['setup' => $setup_list, 'event' => $event_list]); -} catch (Throwable $e) { - $stderr = fopen('php://stderr', 'w'); - fwrite($stderr, zm_internal_errcode('E00031') . $e->getMessage() . ' in ' . $e->getFile() . ' at line ' . $e->getLine() . PHP_EOL); - fclose($stderr); - exit(1); -} diff --git a/src/entry.php b/src/entry.php index a43f6af4..d8d57665 100644 --- a/src/entry.php +++ b/src/entry.php @@ -2,7 +2,13 @@ declare(strict_types=1); -/** @noinspection PhpIncludeInspection */ +// CLI Application 入口文件,先引入 Composer 组件 require_once((!is_dir(__DIR__ . '/../vendor')) ? getcwd() : (__DIR__ . '/..')) . '/vendor/autoload.php'; -(new ZM\ConsoleApplication('zhamao-framework'))->initEnv()->run(); +// 适配 Windows 的 conhost 中文显示,因为使用 micro 打包框架运行的时候在 Windows 运行中文部分会变成乱码 +if (DIRECTORY_SEPARATOR === '\\') { + exec('CHCP 65001'); +} + +// 开始运行,运行 symfony console 组件并解析命令 +(new ZM\ConsoleApplication('zhamao-framework'))->run();