From 4ee16d4fc6c62c7ab44718e15c272a1413aea1fb Mon Sep 17 00:00:00 2001 From: jerry Date: Wed, 16 Jun 2021 00:17:30 +0800 Subject: [PATCH] update to 2.5.0-b1 (build 408) --- .gitignore | 1 - bin/phpunit-swoole | 20 +- bin/start | 27 +- composer.json | 12 +- config/global.php | 11 + docs/advanced/manually-install.md | 3 + docs/component/{ => bot}/access-token.md | 0 docs/component/{ => bot}/cqcode.md | 0 docs/component/{ => bot}/message-util.md | 0 docs/component/{ => bot}/robot-api.md | 0 docs/component/{ => bot}/turing-api.md | 0 docs/component/context.md | 2 +- docs/component/{ => store}/atomics.md | 0 docs/component/{ => store}/data-provider.md | 10 + docs/component/{ => store}/light-cache.md | 0 docs/component/{ => store}/mysql.md | 0 docs/component/{ => store}/redis.md | 0 docs/component/{ => store}/spin-lock.md | 0 docs/guide/installation.md | 136 +--- docs/update/v2.md | 63 ++ ext/go-cqhttp/go-cqhttp-down.sh | 2 +- mkdocs.yml | 29 +- src/Module/Example/Hello.php | 1 + src/ZM/API/CQ.php | 44 +- src/ZM/API/CQAPI.php | 5 +- src/ZM/API/OneBotV11.php | 689 ++++++++++++++++++ src/ZM/API/TuringAPI.php | 4 +- src/ZM/API/ZMRobot.php | 676 +---------------- src/ZM/Annotation/AnnotationParser.php | 11 +- src/ZM/Command/BuildCommand.php | 61 +- src/ZM/Command/{ => Daemon}/DaemonCommand.php | 6 +- .../{ => Daemon}/DaemonReloadCommand.php | 2 +- .../{ => Daemon}/DaemonStatusCommand.php | 2 +- .../{ => Daemon}/DaemonStopCommand.php | 2 +- src/ZM/Command/InitCommand.php | 10 +- src/ZM/Command/Module/ModuleListCommand.php | 75 ++ src/ZM/Command/Module/ModulePackCommand.php | 59 ++ src/ZM/Command/Module/ModuleUnpackCommand.php | 57 ++ src/ZM/Command/PureHttpCommand.php | 2 +- src/ZM/Command/RunServerCommand.php | 4 +- src/ZM/Command/SystemdCommand.php | 7 +- src/ZM/ConsoleApplication.php | 58 +- src/ZM/Context/Context.php | 45 +- src/ZM/Event/EventManager.php | 4 +- src/ZM/Event/SwooleEvent/OnClose.php | 8 +- src/ZM/Event/SwooleEvent/OnManagerStart.php | 5 +- src/ZM/Event/SwooleEvent/OnMessage.php | 4 +- src/ZM/Event/SwooleEvent/OnOpen.php | 12 +- src/ZM/Event/SwooleEvent/OnPipeMessage.php | 17 +- src/ZM/Event/SwooleEvent/OnRequest.php | 16 +- src/ZM/Event/SwooleEvent/OnStart.php | 56 +- src/ZM/Event/SwooleEvent/OnTask.php | 4 +- src/ZM/Event/SwooleEvent/OnWorkerStart.php | 51 +- src/ZM/Exception/DbException.php | 6 +- src/ZM/Exception/ExceptionHandler.php | 15 + src/ZM/Exception/ModulePackException.php | 14 + src/ZM/Exception/NotInitializedException.php | 8 +- src/ZM/Exception/RedisException.php | 9 + src/ZM/Exception/RobotNotFoundException.php | 2 +- src/ZM/Framework.php | 154 ++-- src/ZM/Http/RouteManager.php | 60 +- src/ZM/Module/ModulePacker.php | 206 ++++++ src/ZM/Module/ModuleUnpacker.php | 23 + src/ZM/Store/LightCache.php | 25 +- src/ZM/Store/LightCacheInside.php | 2 +- src/ZM/Store/Redis/ZMRedisPool.php | 2 +- src/ZM/Store/ZMAtomic.php | 1 + src/ZM/Utils/CoMessage.php | 12 +- src/ZM/Utils/CoroutinePool.php | 4 + src/ZM/Utils/DataProvider.php | 111 ++- src/ZM/Utils/HttpUtil.php | 6 +- src/ZM/Utils/Manager/ModuleManager.php | 115 +++ src/ZM/Utils/{ => Manager}/ProcessManager.php | 12 +- src/ZM/Utils/Manager/RouteManager.php | 67 ++ src/ZM/Utils/{ => Manager}/TaskManager.php | 7 +- src/ZM/Utils/MessageUtil.php | 7 +- src/ZM/Utils/SignalListener.php | 75 ++ src/ZM/Utils/Terminal.php | 10 +- src/ZM/Utils/ZMUtil.php | 47 +- src/ZM/global_defines.php | 2 +- src/ZM/global_functions.php | 66 +- src/ZM/script_phar_stub.php | 11 + src/ZM/script_setup_loader.php | 59 ++ test/CoroutinePoolTest.php | 28 - test/LightCacheTest.php | 26 - test/ZM/Utils/DataProviderTest.php | 29 + test/ZM/Utils/Manager/ModuleManagerTest.php | 50 ++ test/ZM/Utils/ZMUtilTest.php | 19 + test/ZMTest/Testing/ModuleTest.php | 2 + test/bootstrap.php | 13 + test/{test.php => route_test.php} | 0 test/usage_test.php | 10 - 92 files changed, 2286 insertions(+), 1342 deletions(-) create mode 100644 docs/advanced/manually-install.md rename docs/component/{ => bot}/access-token.md (100%) rename docs/component/{ => bot}/cqcode.md (100%) rename docs/component/{ => bot}/message-util.md (100%) rename docs/component/{ => bot}/robot-api.md (100%) rename docs/component/{ => bot}/turing-api.md (100%) rename docs/component/{ => store}/atomics.md (100%) rename docs/component/{ => store}/data-provider.md (86%) rename docs/component/{ => store}/light-cache.md (100%) rename docs/component/{ => store}/mysql.md (100%) rename docs/component/{ => store}/redis.md (100%) rename docs/component/{ => store}/spin-lock.md (100%) create mode 100644 src/ZM/API/OneBotV11.php rename src/ZM/Command/{ => Daemon}/DaemonCommand.php (87%) rename src/ZM/Command/{ => Daemon}/DaemonReloadCommand.php (95%) rename src/ZM/Command/{ => Daemon}/DaemonStatusCommand.php (97%) rename src/ZM/Command/{ => Daemon}/DaemonStopCommand.php (96%) create mode 100644 src/ZM/Command/Module/ModuleListCommand.php create mode 100644 src/ZM/Command/Module/ModulePackCommand.php create mode 100644 src/ZM/Command/Module/ModuleUnpackCommand.php create mode 100644 src/ZM/Exception/ExceptionHandler.php create mode 100644 src/ZM/Exception/ModulePackException.php create mode 100644 src/ZM/Exception/RedisException.php create mode 100644 src/ZM/Module/ModulePacker.php create mode 100644 src/ZM/Module/ModuleUnpacker.php create mode 100644 src/ZM/Utils/Manager/ModuleManager.php rename src/ZM/Utils/{ => Manager}/ProcessManager.php (93%) create mode 100644 src/ZM/Utils/Manager/RouteManager.php rename src/ZM/Utils/{ => Manager}/TaskManager.php (64%) create mode 100644 src/ZM/Utils/SignalListener.php create mode 100644 src/ZM/script_phar_stub.php create mode 100644 src/ZM/script_setup_loader.php delete mode 100644 test/CoroutinePoolTest.php delete mode 100644 test/LightCacheTest.php create mode 100644 test/ZM/Utils/DataProviderTest.php create mode 100644 test/ZM/Utils/Manager/ModuleManagerTest.php create mode 100644 test/ZM/Utils/ZMUtilTest.php create mode 100644 test/bootstrap.php rename test/{test.php => route_test.php} (100%) delete mode 100644 test/usage_test.php diff --git a/.gitignore b/.gitignore index ee3a7e54..5b4a716e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ /src/test/ /src/webconsole/config/ /vendor/ -zm.json /zm_data/ composer.lock /resources/server.phar diff --git a/bin/phpunit-swoole b/bin/phpunit-swoole index b3580fb9..bd79ba74 100755 --- a/bin/phpunit-swoole +++ b/bin/phpunit-swoole @@ -1,18 +1,20 @@ #!/usr/bin/env php -/** @noinspection ALL */ * Date: 2018/4/14 下午10:58 */ -Co::set([ - 'log_level' => SWOOLE_LOG_INFO, +use Swoole\Coroutine; + +Coroutine::set([ + 'log_level' => SWOOLE_LOG_INFO, 'trace_flags' => 0 ]); if (!ini_get('date.timezone')) { - ini_set('date.timezone', 'UTC'); + ini_set('date.timezone', 'Asia/Shanghai'); } foreach ([ @@ -55,12 +57,12 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) { /** @noinspection PhpIncludeInspection */ require PHPUNIT_COMPOSER_INSTALL; $starttime = microtime(true); -go(function (){ - try{ +go(function () { + try { PHPUnit\TextUI\Command::main(false); - } catch(Exception $e) { - echo $e->getMessage().PHP_EOL; + } catch (Exception $e) { + echo $e->getMessage() . PHP_EOL; } }); Swoole\Event::wait(); -echo "Took ".round(microtime(true) - $starttime, 4). "s\n"; +echo "Took " . round(microtime(true) - $starttime, 4) . "s\n"; diff --git a/bin/start b/bin/start index 6ad3dc2d..d2f111be 100755 --- a/bin/start +++ b/bin/start @@ -1,23 +1,34 @@ #!/bin/sh +# shellcheck disable=SC2068 +# shellcheck disable=SC2181 +# author: crazywhalecc +# since: 2.5.0 + if [ -f "$(pwd)/runtime/php" ]; then executable="$(pwd)/runtime/php" echo "* Framework started with built-in php." else - which php >/dev/null 2>&1 + which php >/dev/null 2>&1 if [ $? -eq 0 ]; then - executable=$(which php) + executable=$(which php) else - echo 'Cannot find any PHP runtime, please use command `./install-runtime.sh` or install PHP manually!' - exit 1 + echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!' + exit 1 fi fi +result=$(echo "$1" | grep -E "module|build") + +if [ "$result" != "" ]; then + executable="$executable -d phar.readonly=off" +fi + if [ -f "$(pwd)/src/entry.php" ]; then - $executable "$(pwd)/src/entry.php" $@ + $executable "$(pwd)/src/entry.php" $@ elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then - $executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@ + $executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@ else - echo "Cannot find zhamao-framework entry file!" - exit 1 + echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!" + exit 1 fi diff --git a/composer.json b/composer.json index 68a47509..3b0108fd 100644 --- a/composer.json +++ b/composer.json @@ -23,17 +23,18 @@ "php": ">=7.2", "ext-json": "*", "ext-posix": "*", - "ext-pcntl": "*", "doctrine/annotations": "~1.12 || ~1.4.0", - "psy/psysh": "@stable", "symfony/polyfill-ctype": "^1.19", "symfony/polyfill-mbstring": "^1.19", "symfony/console": "~5.0 || ~4.0 || ~3.0", "symfony/routing": "~5.0 || ~4.0 || ~3.0", - "zhamao/console": "^1.0.10", + "zhamao/console": "^1.0", "zhamao/config": "^1.0", "zhamao/request": "^1.1", - "zhamao/connection-manager": "^1.0" + "zhamao/connection-manager": "^1.0", + "jelix/version": "^2.0", + "league/climate": "^3.7", + "psy/psysh": "^0.10.8" }, "suggest": { "ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient", @@ -51,6 +52,7 @@ ] }, "require-dev": { - "swoole/ide-helper": "@dev" + "swoole/ide-helper": "@dev", + "phpunit/phpunit": "^8.5 || ^9.0" } } diff --git a/config/global.php b/config/global.php index f2aeb560..d01fd089 100644 --- a/config/global.php +++ b/config/global.php @@ -34,6 +34,11 @@ $config['swoole'] = [ //'task_enable_coroutine' => true ]; +/** 一些框架与Swoole运行时设置的调整 */ +$config['runtime'] = [ + 'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL) +]; + /** 轻量字符串缓存,默认开启 */ $config['light_cache'] = [ 'size' => 512, //最多允许储存的条数(需要2的倍数) @@ -122,4 +127,10 @@ $config['remote_terminal'] = [ 'token' => '' ]; +/** 模块(插件)加载器的相关设置 */ +$config['module_loader'] = [ + 'enable_hotload' => true, + 'load_path' => $config['zm_data'] . 'modules' +]; + return $config; diff --git a/docs/advanced/manually-install.md b/docs/advanced/manually-install.md new file mode 100644 index 00000000..c53a49a5 --- /dev/null +++ b/docs/advanced/manually-install.md @@ -0,0 +1,3 @@ +# 手动部署环境教程 + +TODO: 还没写 \ No newline at end of file diff --git a/docs/component/access-token.md b/docs/component/bot/access-token.md similarity index 100% rename from docs/component/access-token.md rename to docs/component/bot/access-token.md diff --git a/docs/component/cqcode.md b/docs/component/bot/cqcode.md similarity index 100% rename from docs/component/cqcode.md rename to docs/component/bot/cqcode.md diff --git a/docs/component/message-util.md b/docs/component/bot/message-util.md similarity index 100% rename from docs/component/message-util.md rename to docs/component/bot/message-util.md diff --git a/docs/component/robot-api.md b/docs/component/bot/robot-api.md similarity index 100% rename from docs/component/robot-api.md rename to docs/component/bot/robot-api.md diff --git a/docs/component/turing-api.md b/docs/component/bot/turing-api.md similarity index 100% rename from docs/component/turing-api.md rename to docs/component/bot/turing-api.md diff --git a/docs/component/context.md b/docs/component/context.md index c41d8096..4765b602 100644 --- a/docs/component/context.md +++ b/docs/component/context.md @@ -124,7 +124,7 @@ public function ping() { ## getRobot() - 获取机器人 API 对象 -返回当前上下文关联的机器人 API 调用对象 [ZMRobot](robot-api.md)。 +返回当前上下文关联的机器人 API 调用对象 [ZMRobot](bot/robot-api.md)。 可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()`,`@CQMessage()` 等。 diff --git a/docs/component/atomics.md b/docs/component/store/atomics.md similarity index 100% rename from docs/component/atomics.md rename to docs/component/store/atomics.md diff --git a/docs/component/data-provider.md b/docs/component/store/data-provider.md similarity index 86% rename from docs/component/data-provider.md rename to docs/component/store/data-provider.md index 390da132..dbd399ef 100644 --- a/docs/component/data-provider.md +++ b/docs/component/store/data-provider.md @@ -16,6 +16,16 @@ DataProvider 是框架内提供的一个简易的文件管理类。 获取配置项 `zm_data` 指定的目录。 +如果指定参数 `$second`,则返回二级目录地址,如果二级目录不存在则自动创建。 + +```php +DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framework/zm_data/TestModule/ +``` + +## DataProvider::getFrameworkRootDir() + +返回框架本体的根目录。 + ## DataProvider::saveToJson() 将变量内容保存为 json 格式的文件,存储在 `zm_data/config/` 目录下或子目录下。 diff --git a/docs/component/light-cache.md b/docs/component/store/light-cache.md similarity index 100% rename from docs/component/light-cache.md rename to docs/component/store/light-cache.md diff --git a/docs/component/mysql.md b/docs/component/store/mysql.md similarity index 100% rename from docs/component/mysql.md rename to docs/component/store/mysql.md diff --git a/docs/component/redis.md b/docs/component/store/redis.md similarity index 100% rename from docs/component/redis.md rename to docs/component/store/redis.md diff --git a/docs/component/spin-lock.md b/docs/component/store/spin-lock.md similarity index 100% rename from docs/component/spin-lock.md rename to docs/component/store/spin-lock.md diff --git a/docs/guide/installation.md b/docs/guide/installation.md index b1b89e22..bac652ef 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -2,111 +2,49 @@ > 这篇为炸毛框架以及环境的部署教程。 -框架部署分为环境部署和框架部署。框架部署非常简单,只需要通用的指令,下方主要说环境部署。 +框架部署分为两部分,一部分是安装 PHP 环境,另一部分是通过 Composer 或 GitHub 拉取框架的脚手架。 -## Docker 部署 PHP 环境 -如果你不想干扰主机的环境,可以使用 Docker 进行拉取框架适用的 PHP7 with Swoole Extension Docker Container。本框架安装教程中使用的 DockerHub 及 Dockerfile 构建文件所构建的容器均为独立的容器,和框架无关,此 Docker 也可以用作运行**其他基于 php-cli 模式的项目**。 +## 一键下载静态 PHP 环境和框架脚手架 -方法一、直接拉取远程容器(推荐) -```bash -docker pull zmbot/swoole -``` - -方法二、从 Dockerfile 构建容器 -```bash -git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git -cd zhamao-swoole-docker/ -docker build -t zm . -``` - -!!! note "从 Dockerfile 构建容器的提示" - - 使用 Dockerfile 构建后,需要将下方所有的 `zmbot/swoole` 全部更换成 `zm`,或者你上方指令中的 `-t` 参数后方的名称,具体可以详情查阅 Docker 的文档。 - -## 主机部署 PHP 环境 - -### Debian 系列(Ubuntu、Kali ) - -需要的系统内软件包为:`php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql wget composer` - -下面是一个一键安装的命令行(最小安装,需 root 权限): +从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架,只需运行下面的脚本即可。(开发环境推荐此方法) ```bash -apt-get update && apt-get install -y software-properties-common && add-apt-repository ppa:ondrej/php && apt-get update && apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && apt-get install wget composer -y && wget https://github.com/swoole/swoole-src/archive/v4.5.7.tar.gz && tar -zxvf v4.5.7.tar.gz && cd swoole-src-4.5.7/ && phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) -``` +# 将会把 PHP、框架都安装在此目录下 +mkdir zhamao-app/ # 这里可以取自己的项目名字 +cd zhamao-app/ +bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)" -### macOS (with Homebrew) - -macOS 系统下的部署相对简单很多,只需要使用 Homebrew 安装以下包和执行安装命令即可 - -!!! note "给 macOS 开发者的提示" - - 因为苹果新的 Apple Sillicon 对 Homebrew 的支持目前仅限于 Rosetta2 转译版, - 所以在使用 M1-based Mac 时出现问题暂时无解。 - 使用以下指令可能会遇到报错等问题,如有疑问可直接使用 Docker 或咨询我(炸毛框架开发者)。 - -```bash -brew install php composer -pecl install swoole -``` - -### 其他 Linux 发行版 - -其他 Linux 发行版,如 CentOS,Fedora,Arch 等暂时还没有经过严格的测试需要哪些依赖,大体和 Ubuntu、Debian 系需要的依赖包差不多,可根据安装过程中报错提示依次安装,或者直接使用 Docker 环境。 - -## 安装框架 - -恭喜你,前方通过 Docker 或主机安装环境后可以开始构建框架的开发脚手架了! - -如果你是通过**主机安装 PHP 部署的环境**,下方是通过脚手架来构建项目的命令行。 - -```bash -composer create-project zhamao/framework-starter zhamao-app -cd zhamao-app/ # 这个是你可以自己定义的名称 -vendor/bin/start server # 启动框架 -``` - -如果是通过 **Docker 部署的环境**,则需要在先克隆脚手架后在文件夹内使用 Docker 命令下的 `composer update`。(如果主机环境有 composer 也可以使用 `composer create-project` 的方式拉取脚手架。) - -```bash -git clone https://github.com/zhamao-robot/zhamao-framework-starter.git -cd zhamao-framework-starter/ -docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update -``` - -或者在 Docker 环境下,你可以直接使用如下方法拉取和快速启动一个最标准的框架。 - -```bash -git clone https://github.com/zhamao-robot/zhamao-framework-starter.git -cd zhamao-framework-starter -./run-docker.sh # 在正式版炸毛框架 v2 发布后可用,测试版暂不放出 -``` - -!!! tip "提示" - - 如果国内 Composer 下载过慢,可以使用阿里云的 Composer 镜像加速。 - ```bash - # 仅对当前的项目使用阿里云加速 - composer config repo.packagist composer https://mirrors.aliyun.com/composer/ - # 对全局的 Composer 使用阿里云加速 - composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ - ``` - - -## 启动框架 - -本地环境启动方式: -```bash -cd zhamao-framework-starter +# 安装完成后的启动框架命令(2.5.0 版本后可省略掉 runtime/php 前缀) vendor/bin/start server + +# 扩展用法:使用静态 PHP 版本的 Composer update +runtime/composer update +# 扩展用法:使用静态 PHP 运行别的 CLI 脚本 +runtime/php path/to/your/script.php ``` -使用 Docker 启动: +> 有关静态 PHP 的多种用法(如 Composer),见 [进阶 - PHP 环境高级](/advanced/php-env) + +## 使用 Docker 部署 PHP 和框架 +你也可以使用 Docker 进行拉取 PHP 环境。 + ```bash -cd zhamao-framework-starter +# 拉取 Docker 镜像 +docker pull zmbot/swoole + +# 再通过 GitHub 或其他方式拉取框架脚手架 +git clone --depth=1 https://github.com/zhamao-robot/zhamao-framework-starter.git +cd zhamao-framework-starter/ + +# Docker 内使用 Composer 更新依赖 +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update +docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start init + +# 使用 Docker 启动框架 docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server ``` + 启动后你会看到和下方类似的初始化内容,表明启动成功了 ```verilog @@ -114,17 +52,13 @@ $ vendor/bin/start server host: 0.0.0.0 | port: 20001 log_level: 2 | version: 2.0.0 config: global.php | worker_num: 4 -working_dir: /Users/jerry/project/git-project/zhamao-framework +working_dir: /app/zhamao-framework ______ |__ / |__ __ _ _ __ ___ __ _ ___ / /| '_ \ / _` | '_ ` _ \ / _` |/ _ \ / /_| | | | (_| | | | | | | (_| | (_) | /____|_| |_|\__,_|_| |_| |_|\__,_|\___/ -[14:27:31] [I] [#0] Worker #0 启动中 -[14:27:31] [I] [#2] Worker #2 启动中 -[14:27:31] [I] [#1] Worker #1 启动中 -[14:27:31] [I] [#3] Worker #3 启动中 [14:27:31] [S] [#3] Worker #3 已启动 [14:27:31] [S] [#0] Worker #0 已启动 [14:27:31] [S] [#2] Worker #2 已启动 @@ -133,9 +67,11 @@ working_dir: /Users/jerry/project/git-project/zhamao-framework 单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问:http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。 -!!! note "安装和部署总结" +## 命令总结 - 根据上方描述,此文档中剩余提到的所有 Bash 命令,如果使用 Docker 部署环境,则需要加上 Docker 环境的指令:`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`,如执行其他 Linux 指令(以查看 PHP 版本为例):`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole php -v`。 +1. 对于框架的启动,必须 cd 到项目的跟目录,比如 `cd zhamao-app/` 进入到项目根目录。 +2. 无论何种方式启动,启动框架的命令格式都为这个格式:`{php二进制路径} vendor/bin/start server {--如果需要参数的话这样跟}` +3. 第二条的 `php 二进制路径` 指的是,比如使用第一种静态 PHP 环境,这里写 `runtime/php` 就好了,如果是安装到系统的 PHP 的话,这里为空,如果是 Docker 部署的环境,则这里填 `docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole` ## 使用 IDE 等工具开发代码 diff --git a/docs/update/v2.md b/docs/update/v2.md index 5a0ecf81..0a26a125 100644 --- a/docs/update/v2.md +++ b/docs/update/v2.md @@ -1,5 +1,68 @@ # 更新日志(v2 版本) +## v2.5.0(未发布,测试中) + +> 更新时间:未知 + +以下是版本**新增内容**: + +- 新增全新的模块系统,可打包模块(src 目录下的子目录用户逻辑代码)为 phar 格式进行分发和版本备份。 +- 全局配置文件新增 `module_loader` 项,用于配置外部模块加载的一些设置。 +- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数。 +- 新增 `module:list` 命令,用于查看未打包和已打包的模块列表。 +- 新增 `module:pack` 命令,用于打包现有 src 目录下的模块。 +- 新增打包框架功能,支持将用户的整个项目连同炸毛框架打包为一个 phar 便携运行,使用命令 `build`。 +- 新增快捷脚本 `./zhamao`,效果同 `vendor/bin/start` 或 `bin/start`。 +- 新增启动参数 `--interact`:又重新支持交互终端了,但还是有点问题,不推荐使用。 +- 新增启动参数 `--disable-safe-exit`:如果你的项目在 Ctrl+C 时总是卡住且项目内没有什么使用 LightCache 等缓存在内存的数据可开启防止关不掉框架。 +- 新增启动参数 `--preview`:只显示参数,不启动炸毛框架的服务器。 +- 新增启动参数 `--force-load-module`:强制打包状态下加载的模块(使用英文逗号分隔多个模块名称)。 +- `CoroutinePool` 协程池新增 `getRunningCoroutineCount` 方法,用于查看协程池中的协程数量。 +- `DataProvider` 新增 `getFrameworkRootDir()`、`getSourceRootDir()`,分别代表获取框架的根目录和用户源码根目录。(详见下方对目录的定义解释) +- `DataProvider` 中 `getDataFolder` 新增参数 `$second = ''`,如果给定,则自动创建子目录 `$second` 并返回。 +- `DataProvider` 新增 `scanDirFiles()` 方法,用于扫描目录,可选择是否递归、是否返回相对路径,也支持扫描 Phar 文件内的路径,非常好用。 +- `DataProvider` 新增 `isRelativePath()` 方法,检查路径是否为相对路径(根据第一个字符是否是 '/' 来判断)。 +- `ZMUtil` 新增 `getClassesPsr4()` 方法,用于根据 Psr-4 标准来获取目录下的所有类文件。 +- 新增全局错误码,可以根据错误码在文档内快速定位和解决问题。 + +以下是版本**修改内容**: + +- 启动文件 `vendor/bin/start` 修改为 shell 脚本,可自动寻找 PHP 环境。 +- 全局配置文件的 `zm_data` 根目录默认修改为 `WORKING_DIR`。 +- 全局配置文件删除 `server_event_handler_class` 项,此项废弃。 +- 修复部分 CQ 码解析过程中没有转义的问题。 +- 将 `ZMRobot` 类转移为 `OneBotV11` 类,但提供兼容。 +- 修复在守护进程模式下使用 `daemon:reload` 和 `daemon:stop` 命令可能失效的问题。 +- 修复 systemd 生成时脚本目录错误的 bug。 +- 修复 PipeMessage 等事件未捕获错误导致崩溃的问题。 +- `ZM\Http\RouteManager` 移动到 `ZM\Utils\Manager\RouteManager`,但原地址兼容。 +- 修复 `Terminal` 类使用的一些问题。 +- 对 `pcntl` 扩展改为可选依赖,当 Swoole 版本大于等于 4.6.7 时不需要安装 `pcntl` 扩展。 +- 修正启动时框架对缺省配置项的一些默认参数。 +- 注解 `@OnSetup` 和 `@SwooleHandler` 可直接使用,无需设置 `server_event_handler_class` 即可。 +- 修复框架在一些非正常终端中运行时导致错误的问题。 +- 使用 `--debug-mode` 参数时,自动开启热更新。 + +**对目录的定义解释**: + +在 2.4.4 版本之前,使用炸毛框架中,只含有两种目录,`getWorkingDir` 和 `getDataFolder`,分别代表获取工作目录和数据目录。在 2.5 版本中,又新增了 `getFrameworkRootDir` 代表获取框架的根目录,`getSourceRootDir` 代表获取源码的根目录。 + +以 Composer 运行模式举例,如果你使用 `composer create-project zhamao/framework-starter` 命令新建的框架,那么假设我们从 `/app` 目录下运行此命令,然后使用 `cd framework-starter/` 进入项目目录,此时我们使用 `vendor/bin/start server` 命令运行服务器,对应的目录为: + +- `WorkingDir`:`/app/framework-starter/` +- `SourceRootDir`:`/app/framework-starter/` +- `FrameworkRootDir`:`/app/framework-starter/vendor/zhamao/framework/` + +如果以源码模式(直接克隆 `zhamao-framework.git` 仓库),启动框架,那么使用命令 `bin/start server` 启动框架后,以上三个返回的目录则完全相同。 + +如果以 2.5 版本新的项目归档模式(build)启动框架,假设我们的项目代码打包为 `server.phar`,在 `/app/` 目录,我们使用命令 `php server.phar server` 启动炸毛框架,那么它对应的目录为: + +- `WorkingDir`:`/app/` +- `SourceRootDir`:`phar:///app/server.phar/` +- `FrameworkRootDir`:`phar:///app/server.phar/vendor/zhamao/framework/` + +如果最后一种归档方式启动的框架是从源码模式打包而来,那么 `FrameworkRootDir` 就与 `SourceRootDir` 相同。 + ## v2.4.4 (build 405) > 更新时间:2021.3.29 diff --git a/ext/go-cqhttp/go-cqhttp-down.sh b/ext/go-cqhttp/go-cqhttp-down.sh index 4559a90e..b32fb0a3 100755 --- a/ext/go-cqhttp/go-cqhttp-down.sh +++ b/ext/go-cqhttp/go-cqhttp-down.sh @@ -29,6 +29,6 @@ if [ "$ver" != "" ]; then tar -zxvf temp.tar.gz go-cqhttp rm temp.tar.gz echo "下载完成,启动命令:./go-cqhttp" - echo "首次启动后先编辑config.hjson文件!" + echo "首次启动后先编辑config文件!" fi fi diff --git a/mkdocs.yml b/mkdocs.yml index e5b007e5..22aa5672 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: 炸毛框架 v2 +site_name: 炸毛框架文档 repo_name: '炸毛框架' repo_url: 'https://github.com/zhamao-robot/zhamao-framework' @@ -32,7 +32,7 @@ markdown_extensions: linenums_style: pymdownx.inline extra: version: - method: mike + provider: mike copyright: 'Copyright © 2019 - 2021 CrazyBot Team      @@ -50,7 +50,7 @@ copyright: 'Copyright © 2019 - 2021 CrazyBot Team    &n name.textContent = attr; }) }) -
蒙ICP备18000198号-1' +
沪ICP备2021010446号-1' nav: - 指南: @@ -74,18 +74,18 @@ nav: - 框架组件: component/index.md - 上下文: component/context.md - 聊天机器人组件: - - 机器人 API: component/robot-api.md - - CQ 码(多媒体消息): component/cqcode.md - - 机器人消息处理: component/message-util.md - - Token 验证: component/access-token.md - - 图灵机器人 API: component/turing-api.md + - 机器人 API: component/bot/robot-api.md + - CQ 码(多媒体消息): component/bot/cqcode.md + - 机器人消息处理: component/bot/message-util.md + - Token 验证: component/bot/access-token.md + - 图灵机器人 API: component/bot/turing-api.md - 存储: - - LightCache 轻量缓存: component/light-cache.md - - MySQL 数据库: component/mysql.md - - Redis 数据库: component/redis.md - - ZMAtomic 原子计数器: component/atomics.md - - SpinLock 自旋锁: component/spin-lock.md - - 文件管理: component/data-provider.md + - LightCache 轻量缓存: component/store/light-cache.md + - MySQL 数据库: component/store/mysql.md + - Redis 数据库: component/store/redis.md + - ZMAtomic 原子计数器: component/store/atomics.md + - SpinLock 自旋锁: component/store/spin-lock.md + - 文件管理: component/store/data-provider.md - HTTP 服务器工具类: - HTTP 和 WebSocket 客户端: component/zmrequest.md - HTTP 路由管理: component/route-manager.md @@ -100,6 +100,7 @@ nav: - 进阶开发: advanced/index.md - 框架剖析: advanced/framework-structure.md - 框架启动模式: advanced/custom-start.md + - 手动安装环境: advanced/manually-install.md - 从 v1 升级: advanced/to-v2.md - 内部类文件手册: advanced/inside-class.md - 接入 WebSocket 客户端: advanced/connect-ws-client.md diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 78be3122..207674db 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -92,6 +92,7 @@ class Hello return TuringAPI::getTuringMsg($msg, $user_id, $api); } else { QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1); + //执行嵌套消息,递归层级+1 return true; } } diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php index 99e6f342..d4cdcb0a 100644 --- a/src/ZM/API/CQ.php +++ b/src/ZM/API/CQ.php @@ -20,7 +20,7 @@ class CQ if (is_numeric($qq) || $qq === "all") { return "[CQ:at,qq=" . $qq . "]"; } - Console::warning("传入的QQ号码($qq)错误!"); + Console::warning(zm_internal_errcode("E00035") . "传入的QQ号码($qq)错误!"); return " "; } @@ -33,7 +33,7 @@ class CQ if (is_numeric($id)) { return "[CQ:face,id=" . $id . "]"; } - Console::warning("传入的face id($id)错误!"); + Console::warning(zm_internal_errcode("E00035") . "传入的face id($id)错误!"); return " "; } @@ -46,13 +46,13 @@ class CQ * @param int $timeout * @return string */ - public static function image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1) { + public static function image($file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1) { return "[CQ:image,file=" . self::encode($file, true) . (!$cache ? ",cache=0" : "") . ($flash ? ",type=flash" : "") . (!$proxy ? ",proxy=false" : "") . - ($timeout != -1 ? (",timeout=" . intval($timeout)) : "") . + ($timeout != -1 ? (",timeout=" . $timeout) : "") . "]"; } @@ -65,13 +65,13 @@ class CQ * @param int $timeout * @return string */ - public static function record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1) { + public static function record($file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1) { return "[CQ:record,file=" . self::encode($file, true) . (!$cache ? ",cache=0" : "") . ($magic ? ",magic=1" : "") . (!$proxy ? ",proxy=false" : "") . - ($timeout != -1 ? (",timeout=" . intval($timeout)) : "") . + ($timeout != -1 ? (",timeout=" . $timeout) : "") . "]"; } @@ -83,7 +83,7 @@ class CQ * @param int $timeout * @return string */ - public static function video($file, $cache = true, $proxy = true, $timeout = -1) { + public static function video($file, bool $cache = true, bool $proxy = true, int $timeout = -1) { return "[CQ:video,file=" . self::encode($file, true) . (!$cache ? ",cache=0" : "") . @@ -123,8 +123,8 @@ class CQ * @param string $name * @return string */ - public static function poke($type, $id, $name = "") { - return "[CQ:poke,type=$type,id=$id" . ($name != "" ? (",name=".self::encode($name, true)) : "") . "]"; + public static function poke($type, $id, string $name = "") { + return "[CQ:poke,type=$type,id=$id" . ($name != "" ? (",name=" . self::encode($name, true)) : "") . "]"; } /** @@ -132,7 +132,7 @@ class CQ * @param int $ignore * @return string */ - public static function anonymous($ignore = 1) { + public static function anonymous(int $ignore = 1) { return "[CQ:anonymous" . ($ignore != 1 ? ",ignore=0" : "") . "]"; } @@ -170,12 +170,12 @@ class CQ * @param string $content * @return string */ - public static function location($lat, $lon, $title = "", $content = "") { + public static function location($lat, $lon, string $title = "", string $content = "") { return "[CQ:location" . - ",lat=".self::encode($lat, true) . - ",lon=".self::encode($lon, true). - ($title != "" ? (",title=".self::encode($title, true)) : "") . - ($content != "" ? (",content=".self::encode($content, true)) : "") . + ",lat=" . self::encode($lat, true) . + ",lon=" . self::encode($lon, true) . + ($title != "" ? (",title=" . self::encode($title, true)) : "") . + ($content != "" ? (",content=" . self::encode($content, true)) : "") . "]"; } @@ -205,7 +205,7 @@ class CQ return "[CQ:music,type=$type,id=$id_or_url]"; case "custom": if ($title === null || $audio === null) { - Console::warning("传入CQ码实例的标题和音频链接不能为空!"); + Console::warning(zm_internal_errcode("E00035") . "传入CQ码实例的标题和音频链接不能为空!"); return " "; } if ($content === null) $c = ""; @@ -217,17 +217,17 @@ class CQ ",audio=" . self::encode($audio, true) . ",title=" . self::encode($title, true) . $c . $i . "]"; default: - Console::warning("传入的music type($type)错误!"); + Console::warning(zm_internal_errcode("E00035") . "传入的music type($type)错误!"); return " "; } } public static function forward($id) { - return "[CQ:forward,id=$id]"; + return "[CQ:forward,id=".self::encode($id)."]"; } public static function node($user_id, $nickname, $content) { - return "[CQ:node,user_id=$user_id,nickname=".self::encode($nickname, true).",content=" . self::encode($content, true) . "]"; + return "[CQ:node,user_id=$user_id,nickname=" . self::encode($nickname, true) . ",content=" . self::encode($content, true) . "]"; } public static function xml($data) { @@ -297,7 +297,7 @@ class CQ public static function removeCQ($msg) { $final = ""; $last_end = 0; - foreach(self::getAllCQ($msg) as $k => $v) { + foreach (self::getAllCQ($msg) as $v) { $final .= mb_substr($msg, $last_end, $v["start"] - $last_end); $last_end = $v["end"] + 1; } @@ -319,7 +319,7 @@ class CQ $content = mb_substr($msg, $head + 4, $close + $head - mb_strlen($msg)); $exp = explode(",", $content); $cq["type"] = array_shift($exp); - foreach ($exp as $k => $v) { + foreach ($exp as $v) { $ss = explode("=", $v); $sk = array_shift($ss); $cq["params"][$sk] = self::decode(implode("=", $ss), true); @@ -349,7 +349,7 @@ class CQ $exp = explode(",", $content); $cq = []; $cq["type"] = array_shift($exp); - foreach ($exp as $k => $v) { + foreach ($exp as $v) { $ss = explode("=", $v); $sk = array_shift($ss); $cq["params"][$sk] = self::decode(implode("=", $ss), true); diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 727e88cc..10b43923 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -22,8 +22,6 @@ trait CQAPI return $this->processWebsocketAPI($connection, $reply, $function); else return $this->processHttpAPI($connection, $reply, $function); - - } public function processWebsocketAPI($connection, $reply, $function = false) { @@ -41,7 +39,7 @@ trait CQAPI } return true; } else { - Console::warning("CQAPI send failed, websocket push error."); + Console::warning(zm_internal_errcode("E00036") . "CQAPI send failed, websocket push error."); $response = [ "status" => "failed", "retcode" => -1000, @@ -69,7 +67,6 @@ trait CQAPI return false; } - /** @noinspection PhpMissingReturnTypeInspection */ public function __call($name, $arguments) { return false; } diff --git a/src/ZM/API/OneBotV11.php b/src/ZM/API/OneBotV11.php new file mode 100644 index 00000000..bd0c1acd --- /dev/null +++ b/src/ZM/API/OneBotV11.php @@ -0,0 +1,689 @@ +getOption('connect_id') == $robot_id) return new ZMRobot($v); + } + throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); + } + + /** + * @return ZMRobot + * @throws RobotNotFoundException + */ + public static function getRandom() { + $r = ManagerGM::getAllByName('qq'); + if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); + return new ZMRobot($r[array_rand($r)]); + } + + public static function getFirst() { + } + + /** + * @return ZMRobot[] + */ + public static function getAllRobot() { + $r = ManagerGM::getAllByName('qq'); + $obj = []; + foreach ($r as $v) { + $obj[] = new ZMRobot($v); + } + return $obj; + } + + public function __construct(ConnectionObject $connection) { + $this->connection = $connection; + } + + public function setCallback($callback = true) { + $this->callback = $callback; + return $this; + } + + public function setPrefix($prefix = self::API_NORMAL) { + $this->prefix = $prefix; + return $this; + } + + public function getSelfId() { + return $this->connection->getOption('connect_id'); + } + + /* 下面是 OneBot 标准的 V11 公开 API */ + + /** + * 发送私聊消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF + * @param $user_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ + public function sendPrivateMsg($user_id, $message, $auto_escape = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + /** + * 发送群消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF + * @param $group_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ + public function sendGroupMsg($group_id, $message, $auto_escape = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + /** + * 发送消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF + * @param $message_type + * @param $target_id + * @param $message + * @param bool $auto_escape + * @return array|bool|null + */ + public function sendMsg($message_type, $target_id, $message, $auto_escape = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_type' => $message_type, + ($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + /** + * 撤回消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF + * @param $message_id + * @return array|bool|null + */ + public function deleteMsg($message_id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_id' => $message_id + ] + ], $this->callback); + } + + /** + * 获取消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF + * @param $message_id + * @return array|bool|null + */ + public function getMsg($message_id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_id' => $message_id + ] + ], $this->callback); + } + + /** + * 获取合并转发消息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF + * @param $id + * @return array|bool|null + */ + public function getForwardMsg($id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'id' => $id + ] + ], $this->callback); + } + + /** + * 发送好友赞 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E + * @param $user_id + * @param int $times + * @return array|bool|null + */ + public function sendLike($user_id, $times = 1) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'times' => $times + ] + ], $this->callback); + } + + /** + * 群组踢人 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA + * @param $group_id + * @param $user_id + * @param bool $reject_add_request + * @return array|bool|null + */ + public function setGroupKick($group_id, $user_id, $reject_add_request = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'reject_add_request' => $reject_add_request + ] + ], $this->callback); + } + + /** + * 群组单人禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80 + * @param $group_id + * @param $user_id + * @param $duration + * @return array|bool|null + */ + public function setGroupBan($group_id, $user_id, $duration = 1800) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'duration' => $duration + ] + ], $this->callback); + } + + /** + * 群组匿名用户禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80 + * @param $group_id + * @param $anonymous_or_flag + * @param int $duration + * @return array|bool|null + */ + public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + (is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag, + 'duration' => $duration + ] + ], $this->callback); + } + + /** + * 群组全员禁言 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80 + * @param $group_id + * @param bool $enable + * @return array|bool|null + */ + public function setGroupWholeBan($group_id, $enable = true) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'enable' => $enable + ] + ], $this->callback); + } + + /** + * 群组设置管理员 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98 + * @param $group_id + * @param $user_id + * @param bool $enable + * @return array|bool|null + */ + public function setGroupAdmin($group_id, $user_id, $enable = true) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'enable' => $enable + ] + ], $this->callback); + } + + /** + * 群组匿名 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D + * @param $group_id + * @param bool $enable + * @return array|bool|null + */ + public function setGroupAnonymous($group_id, $enable = true) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'enable' => $enable + ] + ], $this->callback); + } + + /** + * 设置群名片(群备注) + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8 + * @param $group_id + * @param $user_id + * @param string $card + * @return array|bool|null + */ + public function setGroupCard($group_id, $user_id, $card = "") { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'card' => $card + ] + ], $this->callback); + } + + /** + * 设置群名 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D + * @param $group_id + * @param $group_name + * @return array|bool|null + */ + public function setGroupName($group_id, $group_name) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'group_name' => $group_name + ] + ], $this->callback); + } + + /** + * 退出群组 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84 + * @param $group_id + * @param bool $is_dismiss + * @return array|bool|null + */ + public function setGroupLeave($group_id, $is_dismiss = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'is_dismiss' => $is_dismiss + ] + ], $this->callback); + } + + /** + * 设置群组专属头衔 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94 + * @param $group_id + * @param $user_id + * @param string $special_title + * @param int $duration + * @return array|bool|null + */ + public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'special_title' => $special_title, + 'duration' => $duration + ] + ], $this->callback); + } + + /** + * 处理加好友请求 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82 + * @param $flag + * @param bool $approve + * @param string $remark + * @return array|bool|null + */ + public function setFriendAddRequest($flag, $approve = true, $remark = "") { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'flag' => $flag, + 'approve' => $approve, + 'remark' => $remark + ] + ], $this->callback); + } + + /** + * 处理加群请求/邀请 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7 + * @param $flag + * @param $sub_type + * @param bool $approve + * @param string $reason + * @return array|bool|null + */ + public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'flag' => $flag, + 'sub_type' => $sub_type, + 'approve' => $approve, + 'reason' => $reason + ] + ], $this->callback); + } + + /** + * 获取登录号信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF + * @return array|bool|null + */ + public function getLoginInfo() { + return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); + } + + /** + * 获取陌生人信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF + * @param $user_id + * @param bool $no_cache + * @return array|bool|null + */ + public function getStrangerInfo($user_id, $no_cache = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + /** + * 获取好友列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8 + * @return array|bool|null + */ + public function getFriendList() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取群信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF + * @param $group_id + * @param bool $no_cache + * @return array|bool|null + */ + public function getGroupInfo($group_id, $no_cache = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + /** + * 获取群列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8 + * @return array|bool|null + */ + public function getGroupList() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取群成员信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF + * @param $group_id + * @param $user_id + * @param bool $no_cache + * @return array|bool|null + */ + public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + /** + * 获取群成员列表 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8 + * @param $group_id + * @return array|bool|null + */ + public function getGroupMemberList($group_id) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id + ] + ], $this->callback); + } + + /** + * 获取群荣誉信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF + * @param $group_id + * @param $type + * @return array|bool|null + */ + public function getGroupHonorInfo($group_id, $type) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'type' => $type + ] + ], $this->callback); + } + + /** + * 获取 CSRF Token + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token + * @return array|bool|null + */ + public function getCsrfToken() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取 QQ 相关接口凭证 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81 + * @param string $domain + * @return array|bool|null + */ + public function getCredentials($domain = "") { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'domain' => $domain + ] + ], $this->callback); + } + + /** + * 获取语音 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3 + * @param $file + * @param $out_format + * @return array|bool|null + */ + public function getRecord($file, $out_format) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'file' => $file, + 'out_format' => $out_format + ] + ], $this->callback); + } + + /** + * 获取图片 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87 + * @param $file + * @return array|bool|null + */ + public function getImage($file) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'file' => $file + ] + ], $this->callback); + } + + /** + * 检查是否可以发送图片 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87 + * @return array|bool|null + */ + public function canSendImage() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 检查是否可以发送语音 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3 + * @return array|bool|null + */ + public function canSendRecord() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取运行状态 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81 + * @return array|bool|null + */ + public function getStatus() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 获取版本信息 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF + * @return array|bool|null + */ + public function getVersionInfo() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + /** + * 重启 OneBot 实现 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0 + * @param int $delay + * @return array|bool|null + */ + public function setRestart($delay = 0) { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'delay' => $delay + ] + ], $this->callback); + } + + /** + * 清理缓存 + * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98 + * @return array|bool|null + */ + public function cleanCache() { + return $this->processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function callExtendedAPI($action, $params = []) { + return $this->processAPI($this->connection, [ + 'action' => $action, + 'params' => $params + ], $this->callback); + } + + private function getActionName(string $method) { + $prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : '')); + $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method)); + return $func_name . $prefix; + } +} \ No newline at end of file diff --git a/src/ZM/API/TuringAPI.php b/src/ZM/API/TuringAPI.php index ab5de163..5b851386 100644 --- a/src/ZM/API/TuringAPI.php +++ b/src/ZM/API/TuringAPI.php @@ -54,14 +54,14 @@ class TuringAPI if ($api_return["intent"]["code"] == 4003) { return "哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗"; } - Console::error("图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status); + Console::error(zm_internal_errcode("E00038") . "图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status); //echo json_encode($r, 128|256); return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?"; } $result = $api_return["results"]; //Console::info(Console::setColor(json_encode($result, 128 | 256), "green")); $final = ""; - foreach ($result as $k => $v) { + foreach ($result as $v) { switch ($v["resultType"]) { case "url": $final .= "\n" . $v["values"]["url"]; diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php index 8bab3f41..fb304856 100644 --- a/src/ZM/API/ZMRobot.php +++ b/src/ZM/API/ZMRobot.php @@ -5,687 +5,13 @@ namespace ZM\API; -use ZM\ConnectionManager\ConnectionObject; -use ZM\ConnectionManager\ManagerGM; -use ZM\Exception\RobotNotFoundException; - /** * Class ZMRobot * @package ZM\Utils * @since 1.2 * @version V11 */ -class ZMRobot +class ZMRobot extends OneBotV11 { - use CQAPI; - const API_ASYNC = 1; - const API_NORMAL = 0; - const API_RATE_LIMITED = 2; - - /** @var ConnectionObject|null */ - private $connection; - - private $callback = true; - private $prefix = 0; - - /** - * @param $robot_id - * @return ZMRobot - * @throws RobotNotFoundException - */ - public static function get($robot_id) { - $r = ManagerGM::getAllByName('qq'); - foreach ($r as $v) { - if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v); - } - throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); - } - - /** - * @return ZMRobot - * @throws RobotNotFoundException - */ - public static function getRandom() { - $r = ManagerGM::getAllByName('qq'); - if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); - return new ZMRobot($r[array_rand($r)]); - } - - public static function getFirst() { - } - - /** - * @return ZMRobot[] - */ - public static function getAllRobot() { - $r = ManagerGM::getAllByName('qq'); - $obj = []; - foreach ($r as $v) { - $obj[] = new ZMRobot($v); - } - return $obj; - } - - public function __construct(ConnectionObject $connection) { - $this->connection = $connection; - } - - public function setCallback($callback = true) { - $this->callback = $callback; - return $this; - } - - public function setPrefix($prefix = self::API_NORMAL) { - $this->prefix = $prefix; - return $this; - } - - public function getSelfId() { - return $this->connection->getOption('connect_id'); - } - - /* 下面是 OneBot 标准的 V11 公开 API */ - - /** - * 发送私聊消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF - * @param $user_id - * @param $message - * @param bool $auto_escape - * @return array|bool|null - */ - public function sendPrivateMsg($user_id, $message, $auto_escape = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'message' => $message, - 'auto_escape' => $auto_escape - ] - ], $this->callback); - } - - /** - * 发送群消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF - * @param $group_id - * @param $message - * @param bool $auto_escape - * @return array|bool|null - */ - public function sendGroupMsg($group_id, $message, $auto_escape = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'message' => $message, - 'auto_escape' => $auto_escape - ] - ], $this->callback); - } - - /** - * 发送消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF - * @param $message_type - * @param $target_id - * @param $message - * @param bool $auto_escape - * @return array|bool|null - */ - public function sendMsg($message_type, $target_id, $message, $auto_escape = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'message_type' => $message_type, - ($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id, - 'message' => $message, - 'auto_escape' => $auto_escape - ] - ], $this->callback); - } - - /** - * 撤回消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF - * @param $message_id - * @return array|bool|null - */ - public function deleteMsg($message_id) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'message_id' => $message_id - ] - ], $this->callback); - } - - /** - * 获取消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF - * @param $message_id - * @return array|bool|null - */ - public function getMsg($message_id) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'message_id' => $message_id - ] - ], $this->callback); - } - - /** - * 获取合并转发消息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF - * @param $id - * @return array|bool|null - */ - public function getForwardMsg($id) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'id' => $id - ] - ], $this->callback); - } - - /** - * 发送好友赞 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E - * @param $user_id - * @param int $times - * @return array|bool|null - */ - public function sendLike($user_id, $times = 1) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'times' => $times - ] - ], $this->callback); - } - - /** - * 群组踢人 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA - * @param $group_id - * @param $user_id - * @param bool $reject_add_request - * @return array|bool|null - */ - public function setGroupKick($group_id, $user_id, $reject_add_request = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'reject_add_request' => $reject_add_request - ] - ], $this->callback); - } - - /** - * 群组单人禁言 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80 - * @param $group_id - * @param $user_id - * @param $duration - * @return array|bool|null - */ - public function setGroupBan($group_id, $user_id, $duration = 1800) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'duration' => $duration - ] - ], $this->callback); - } - - /** - * 群组匿名用户禁言 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80 - * @param $group_id - * @param $anonymous_or_flag - * @param int $duration - * @return array|bool|null - */ - public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - (is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag, - 'duration' => $duration - ] - ], $this->callback); - } - - /** - * 群组全员禁言 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80 - * @param $group_id - * @param bool $enable - * @return array|bool|null - */ - public function setGroupWholeBan($group_id, $enable = true) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'enable' => $enable - ] - ], $this->callback); - } - - /** - * 群组设置管理员 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98 - * @param $group_id - * @param $user_id - * @param bool $enable - * @return array|bool|null - */ - public function setGroupAdmin($group_id, $user_id, $enable = true) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'enable' => $enable - ] - ], $this->callback); - } - - /** - * 群组匿名 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D - * @param $group_id - * @param bool $enable - * @return array|bool|null - */ - public function setGroupAnonymous($group_id, $enable = true) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'enable' => $enable - ] - ], $this->callback); - } - - /** - * 设置群名片(群备注) - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8 - * @param $group_id - * @param $user_id - * @param string $card - * @return array|bool|null - */ - public function setGroupCard($group_id, $user_id, $card = "") { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'card' => $card - ] - ], $this->callback); - } - - /** - * 设置群名 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D - * @param $group_id - * @param $group_name - * @return array|bool|null - */ - public function setGroupName($group_id, $group_name) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'group_name' => $group_name - ] - ], $this->callback); - } - - /** - * 退出群组 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84 - * @param $group_id - * @param bool $is_dismiss - * @return array|bool|null - */ - public function setGroupLeave($group_id, $is_dismiss = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'is_dismiss' => $is_dismiss - ] - ], $this->callback); - } - - /** - * 设置群组专属头衔 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94 - * @param $group_id - * @param $user_id - * @param string $special_title - * @param int $duration - * @return array|bool|null - */ - public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'special_title' => $special_title, - 'duration' => $duration - ] - ], $this->callback); - } - - /** - * 处理加好友请求 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82 - * @param $flag - * @param bool $approve - * @param string $remark - * @return array|bool|null - */ - public function setFriendAddRequest($flag, $approve = true, $remark = "") { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'flag' => $flag, - 'approve' => $approve, - 'remark' => $remark - ] - ], $this->callback); - } - - /** - * 处理加群请求/邀请 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7 - * @param $flag - * @param $sub_type - * @param bool $approve - * @param string $reason - * @return array|bool|null - */ - public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'flag' => $flag, - 'sub_type' => $sub_type, - 'approve' => $approve, - 'reason' => $reason - ] - ], $this->callback); - } - - /** - * 获取登录号信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF - * @return array|bool|null - */ - public function getLoginInfo() { - return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); - } - - /** - * 获取陌生人信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF - * @param $user_id - * @param bool $no_cache - * @return array|bool|null - */ - public function getStrangerInfo($user_id, $no_cache = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'user_id' => $user_id, - 'no_cache' => $no_cache - ] - ], $this->callback); - } - - /** - * 获取好友列表 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8 - * @return array|bool|null - */ - public function getFriendList() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 获取群信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF - * @param $group_id - * @param bool $no_cache - * @return array|bool|null - */ - public function getGroupInfo($group_id, $no_cache = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'no_cache' => $no_cache - ] - ], $this->callback); - } - - /** - * 获取群列表 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8 - * @return array|bool|null - */ - public function getGroupList() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 获取群成员信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF - * @param $group_id - * @param $user_id - * @param bool $no_cache - * @return array|bool|null - */ - public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'user_id' => $user_id, - 'no_cache' => $no_cache - ] - ], $this->callback); - } - - /** - * 获取群成员列表 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8 - * @param $group_id - * @return array|bool|null - */ - public function getGroupMemberList($group_id) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id - ] - ], $this->callback); - } - - /** - * 获取群荣誉信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF - * @param $group_id - * @param $type - * @return array|bool|null - */ - public function getGroupHonorInfo($group_id, $type) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'group_id' => $group_id, - 'type' => $type - ] - ], $this->callback); - } - - /** - * 获取 CSRF Token - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token - * @return array|bool|null - */ - public function getCsrfToken() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 获取 QQ 相关接口凭证 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81 - * @param string $domain - * @return array|bool|null - */ - public function getCredentials($domain = "") { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'domain' => $domain - ] - ], $this->callback); - } - - /** - * 获取语音 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3 - * @param $file - * @param $out_format - * @return array|bool|null - */ - public function getRecord($file, $out_format) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'file' => $file, - 'out_format' => $out_format - ] - ], $this->callback); - } - - /** - * 获取图片 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87 - * @param $file - * @return array|bool|null - */ - public function getImage($file) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'file' => $file - ] - ], $this->callback); - } - - /** - * 检查是否可以发送图片 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87 - * @return array|bool|null - */ - public function canSendImage() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 检查是否可以发送语音 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3 - * @return array|bool|null - */ - public function canSendRecord() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 获取运行状态 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81 - * @return array|bool|null - */ - public function getStatus() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 获取版本信息 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF - * @return array|bool|null - */ - public function getVersionInfo() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - /** - * 重启 OneBot 实现 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0 - * @param int $delay - * @return array|bool|null - */ - public function setRestart($delay = 0) { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__), - 'params' => [ - 'delay' => $delay - ] - ], $this->callback); - } - - /** - * 清理缓存 - * @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98 - * @return array|bool|null - */ - public function cleanCache() { - return $this->processAPI($this->connection, [ - 'action' => $this->getActionName(__FUNCTION__) - ], $this->callback); - } - - public function callExtendedAPI($action, $params = []) { - return $this->processAPI($this->connection, [ - 'action' => $action, - 'params' => $params - ], $this->callback); - } - - private function getActionName(string $method) { - $prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : '')); - $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method)); - return $func_name . $prefix; - } } diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 6ee85b33..005cd2ba 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -17,7 +17,8 @@ use ZM\Annotation\Http\MiddlewareClass; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Module\Closed; -use ZM\Http\RouteManager; +use ZM\Utils\Manager\RouteManager; +use ZM\Utils\ZMUtil; class AnnotationParser { @@ -53,7 +54,7 @@ class AnnotationParser public function registerMods() { foreach ($this->path_list as $path) { Console::debug("parsing annotation in " . $path[0]); - $all_class = getAllClasses($path[0], $path[1]); + $all_class = ZMUtil::getClassesPsr4($path[0], $path[1]); $this->reader = new AnnotationReader(); foreach ($all_class as $v) { Console::debug("正在检索 " . $v); @@ -88,7 +89,7 @@ class AnnotationParser } - foreach ($this->annotation_map[$v]["class_annotations"] as $ks => $vs) { + foreach ($this->annotation_map[$v]["class_annotations"] as $vs) { $vs->class = $v; //预处理1:将适用于每一个函数的注解到类注解重新注解到每个函数下面 @@ -132,12 +133,12 @@ class AnnotationParser public function generateAnnotationEvents() { $o = []; - foreach ($this->annotation_map as $module => $obj) { + foreach ($this->annotation_map as $obj) { foreach (($obj["class_annotations"] ?? []) as $class_annotation) { if ($class_annotation instanceof ErgodicAnnotation) continue; else $o[get_class($class_annotation)][] = $class_annotation; } - foreach (($obj["methods_annotations"] ?? []) as $method_name => $methods_annotations) { + foreach (($obj["methods_annotations"] ?? []) as $methods_annotations) { foreach ($methods_annotations as $annotation) { $o[get_class($annotation)][] = $annotation; } diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php index c3b34f69..31169623 100644 --- a/src/ZM/Command/BuildCommand.php +++ b/src/ZM/Command/BuildCommand.php @@ -9,6 +9,7 @@ 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 { @@ -21,28 +22,28 @@ class BuildCommand extends Command protected function configure() { $this->setDescription("Build an \".phar\" file | 将项目构建一个phar包"); - $this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。"); + $this->setHelp("此功能将会把整个项目打包为phar"); $this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录"); // ... } protected function execute(InputInterface $input, OutputInterface $output): int { $this->output = $output; - $target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/'); + $target_dir = $input->getOption("target") ?? (WORKING_DIR); if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir); if ($target_dir === false) { - $output->writeln(TermColor::color8(31) . "Error: No such file or directory (" . __DIR__ . '/../../../resources/' . ")" . TermColor::RESET); + $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory (" . $target_dir . ")" . TermColor::RESET); return 1; } - $output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"])); + $output->writeln("Target: " . $target_dir); if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/"; if (ini_get('phar.readonly') == 1) { - $output->writeln(TermColor::color8(31) . "You need to set \"phar.readonly\" to \"Off\"!"); + $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00040") . "You need to set \"phar.readonly\" to \"Off\"!"); $output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing"); return 1; } if (!is_dir($target_dir)) { - $output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET); + $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory ($target_dir)" . TermColor::RESET); return 1; } $filename = "server.phar"; @@ -55,19 +56,43 @@ class BuildCommand extends Command @unlink($target_dir . $filename); $phar = new Phar($target_dir . $filename); $phar->startBuffering(); - $src = realpath(__DIR__ . '/../../zhamao-framework/'); - $hello = file_get_contents($src . '/src/Module/Example/Hello.php'); - $middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php'); - unlink($src . '/src/Module/Example/Hello.php'); - unlink($src . '/src/Module/Middleware/TimerMiddleware.php'); - $phar->buildFromDirectory($src); - $phar->addFromString('tmp/Hello.php.bak', $hello); - $phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware); - //$phar->compressFiles(Phar::GZ); - $phar->setStub($phar->createDefaultStub('phar-starter.php')); + + $allow_dir = ["bin", "config", "resources", "src", "vendor", "composer.json", "README.md", "zhamao"]; + + $archive_dir = DataProvider::getSourceRootDir(); + $scan = scandir($archive_dir); + if ($scan[0] == ".") { + unset($scan[0], $scan[1]); + } + foreach ($scan as $v) { + if (in_array($v, $allow_dir)) { + if (is_dir($archive_dir . "/" . $v)) { + $this->addDirectory($phar, $archive_dir . "/" . $v, $v); + } elseif (is_file($archive_dir . "/" . $v)) { + $phar->addFile($archive_dir . "/" . $v, $v); + } + } + } + + $phar->setStub( + "#!/usr/bin/env php\n" . + $phar->createDefaultStub(LOAD_MODE == 0 ? "src/entry.php" : "vendor/zhamao/framework/src/entry.php") + ); $phar->stopBuffering(); - file_put_contents($src . '/src/Module/Example/Hello.php', $hello); - file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware); $this->output->writeln("Successfully built. Location: " . $target_dir . "$filename"); } + + private function addDirectory(Phar $phar, $dir, $local_dir) { + $o = scandir($dir); + if ($o[0] == ".") { + unset($o[0], $o[1]); + } + foreach ($o as $v) { + if (is_dir($dir . "/" . $v)) { + $this->addDirectory($phar, $dir . "/" . $v, $local_dir . "/" . $v); + } elseif (is_file($dir . "/" . $v)) { + $phar->addFile($dir . "/" . $v, $local_dir . "/" . $v); + } + } + } } diff --git a/src/ZM/Command/DaemonCommand.php b/src/ZM/Command/Daemon/DaemonCommand.php similarity index 87% rename from src/ZM/Command/DaemonCommand.php rename to src/ZM/Command/Daemon/DaemonCommand.php index ceffceb4..4b18750e 100644 --- a/src/ZM/Command/DaemonCommand.php +++ b/src/ZM/Command/Daemon/DaemonCommand.php @@ -1,7 +1,7 @@ writeln("没有检测到正在运行的守护进程!"); + $output->writeln("没有检测到正在运行的守护进程或框架进程!"); die(); } $file = json_decode(file_get_contents($pid_path), true); if ($file === null || posix_getsid(intval($file["pid"])) === false) { - $output->writeln("未检测到正在运行的守护进程!"); + $output->writeln("未检测到正在运行的守护进程或框架进程!"); unlink($pid_path); die(); } diff --git a/src/ZM/Command/DaemonReloadCommand.php b/src/ZM/Command/Daemon/DaemonReloadCommand.php similarity index 95% rename from src/ZM/Command/DaemonReloadCommand.php rename to src/ZM/Command/Daemon/DaemonReloadCommand.php index 0a5f86f7..7ff98c5a 100644 --- a/src/ZM/Command/DaemonReloadCommand.php +++ b/src/ZM/Command/Daemon/DaemonReloadCommand.php @@ -1,7 +1,7 @@ [ "Module\\" => "src/Module", @@ -69,7 +69,7 @@ class InitCommand extends Command foreach ($autoload["psr-4"] as $k => $v) { if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v; } - foreach ($autoload["files"] as $k => $v) { + foreach ($autoload["files"] as $v) { if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v; } } @@ -78,7 +78,7 @@ class InitCommand extends Command exec("composer dump-autoload"); echo PHP_EOL; } else { - echo("Error occurred. Please check your updates.\n"); + echo(zm_internal_errcode("E00041") . "Error occurred. Please check your updates.\n"); return 1; } $output->writeln("Done!"); @@ -99,7 +99,7 @@ class InitCommand extends Command } } } - $output->writeln("initialization must be started with composer-project mode!"); + $output->writeln(zm_internal_errcode("E00042") . "initialization must be started with composer-project mode!"); return 1; } diff --git a/src/ZM/Command/Module/ModuleListCommand.php b/src/ZM/Command/Module/ModuleListCommand.php new file mode 100644 index 00000000..0c984d1c --- /dev/null +++ b/src/ZM/Command/Module/ModuleListCommand.php @@ -0,0 +1,75 @@ +setDescription("Build an \".phar\" file | 将项目构建一个phar包"); + $this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。"); + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); + ZMConfig::setEnv($args["env"] ?? ""); + if (ZMConfig::get("global") === false) { + die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + } + + //定义常量 + include_once DataProvider::getFrameworkRootDir() . "/src/ZM/global_defines.php"; + + Console::init( + ZMConfig::get("global", "info_level") ?? 2, + null, + $args["log-theme"] ?? "default", + ($o = ZMConfig::get("console_color")) === false ? [] : $o + ); + + $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; + date_default_timezone_set($timezone); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $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); + } + $list = ModuleManager::getPackedModules(); + foreach ($list as $v) { + echo "[" . Console::setColor($v["name"], "gold") . "]" . PHP_EOL; + $out_list = ["类型" => "archive(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("没有发现已编写打包配置文件(zm.json)的模块和已打包且装载的模块!", "yellow") . PHP_EOL; + } + return 0; + } + + private function printList($list) { + foreach ($list as $k => $v) { + echo "\t" . $k . ": " . Console::setColor($v, "yellow") . PHP_EOL; + } + } +} \ No newline at end of file diff --git a/src/ZM/Command/Module/ModulePackCommand.php b/src/ZM/Command/Module/ModulePackCommand.php new file mode 100644 index 00000000..03c712c6 --- /dev/null +++ b/src/ZM/Command/Module/ModulePackCommand.php @@ -0,0 +1,59 @@ +addArgument("module-name", InputArgument::REQUIRED); + $this->setDescription("Build an \".phar\" file | 将项目构建一个phar包"); + $this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。"); + $this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录"); + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); + ZMConfig::setEnv($args["env"] ?? ""); + if (ZMConfig::get("global") === false) { + die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + } + + //定义常量 + include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php"; + + Console::init( + ZMConfig::get("global", "info_level") ?? 2, + null, + $args["log-theme"] ?? "default", + ($o = ZMConfig::get("console_color")) === false ? [] : $o + ); + + $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; + date_default_timezone_set($timezone); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $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")]); + if ($result) Console::success("打包完成!"); + else Console::error("打包失败!"); + return 0; + } +} \ No newline at end of file diff --git a/src/ZM/Command/Module/ModuleUnpackCommand.php b/src/ZM/Command/Module/ModuleUnpackCommand.php new file mode 100644 index 00000000..1cdcf659 --- /dev/null +++ b/src/ZM/Command/Module/ModuleUnpackCommand.php @@ -0,0 +1,57 @@ +addArgument("module-name", InputArgument::REQUIRED); + $this->setDescription("Unpack a phar module into src directory"); + $this->setHelp("此功能将phar格式的模块包解包到src目录下。"); + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); + ZMConfig::setEnv($args["env"] ?? ""); + if (ZMConfig::get("global") === false) { + die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + } + + //定义常量 + include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php"; + + Console::init( + ZMConfig::get("global", "info_level") ?? 2, + null, + $args["log-theme"] ?? "default", + ($o = ZMConfig::get("console_color")) === false ? [] : $o + ); + + $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; + date_default_timezone_set($timezone); + // ... + } + + protected function execute(InputInterface $input, OutputInterface $output): int { + $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")]); + if ($result) Console::success("解压完成!"); + else Console::error("解压失败!"); + return 0; + } +} \ No newline at end of file diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php index fe8b1bec..cb4c6f38 100644 --- a/src/ZM/Command/PureHttpCommand.php +++ b/src/ZM/Command/PureHttpCommand.php @@ -41,7 +41,7 @@ class PureHttpCommand extends Command $output->writeln("Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory."); return self::FAILURE; } - ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); $global = ZMConfig::get("global"); $host = $input->getOption("host") ?? $global["host"]; $port = $input->getOption("port") ?? $global["port"]; diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php index 6a07cb6e..fd32afcf 100644 --- a/src/ZM/Command/RunServerCommand.php +++ b/src/ZM/Command/RunServerCommand.php @@ -23,6 +23,7 @@ class RunServerCommand extends Command new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"), new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"), new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"), + new InputOption("interact", null, null, "打开终端输入"), new InputOption("remote-terminal", null, null, "启用远程终端,配置使用global.php中的"), new InputOption("disable-coroutine", null, null, "关闭协程Hook"), new InputOption("daemon", null, null, "以守护进程的方式运行框架"), @@ -32,7 +33,8 @@ class RunServerCommand extends Command new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"), new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"), new InputOption("disable-safe-exit", null, null, "关闭安全退出(关闭后按CtrlC时直接杀死进程)"), - new InputOption("preview", null, null, "只显示参数,不启动服务器") + new InputOption("preview", null, null, "只显示参数,不启动服务器"), + new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)") ]); $this->setDescription("Run zhamao-framework | 启动框架"); $this->setHelp("直接运行可以启动"); diff --git a/src/ZM/Command/SystemdCommand.php b/src/ZM/Command/SystemdCommand.php index a03917f8..6e567c7d 100644 --- a/src/ZM/Command/SystemdCommand.php +++ b/src/ZM/Command/SystemdCommand.php @@ -29,11 +29,8 @@ class SystemdCommand extends Command $s .= "\nUser=" . exec("whoami"); $s .= "\nGroup=" . exec("groups | awk '{print $1}'"); $s .= "\nWorkingDirectory=" . getcwd(); - if (LOAD_MODE == 1) { - $s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server"; - } else { - $s .= "\nExecStart=" . getcwd() . "/bin/start server"; - } + 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(getcwd() . "/resources/zhamao.service", $s); diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 61b5c4b3..a9c12007 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -5,22 +5,28 @@ namespace ZM; use Exception; +use Phar; +use ZM\Command\BuildCommand; use ZM\Command\CheckConfigCommand; -use ZM\Command\DaemonReloadCommand; -use ZM\Command\DaemonStatusCommand; -use ZM\Command\DaemonStopCommand; +use ZM\Command\Daemon\DaemonReloadCommand; +use ZM\Command\Daemon\DaemonStatusCommand; +use ZM\Command\Daemon\DaemonStopCommand; 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\RunServerCommand; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use ZM\Command\SystemdCommand; +use ZM\Console\Console; class ConsoleApplication extends Application { - const VERSION_ID = 407; - const VERSION = "2.5.0"; + const VERSION_ID = 408; + const VERSION = "2.5.0-b1"; public function __construct(string $name = 'UNKNOWN') { define("ZM_VERSION_ID", self::VERSION_ID); @@ -32,10 +38,18 @@ class ConsoleApplication extends Application $this->selfCheck(); define("WORKING_DIR", getcwd()); - define("LOAD_MODE", is_dir(WORKING_DIR . "/src/ZM") ? 0 : 1); - define("FRAMEWORK_ROOT_DIR", realpath(__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__ . "/../../")); + } if (LOAD_MODE == 0) { - $composer = json_decode(file_get_contents(WORKING_DIR . "/composer.json"), true); + $composer = json_decode(file_get_contents(SOURCE_ROOT_DIR . "/composer.json"), true); if (!isset($composer["autoload"]["psr-4"]["Module\\"])) { echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] "; $r = strtolower(trim(fgets(STDIN))); @@ -47,7 +61,7 @@ class ConsoleApplication extends Application echo "成功添加!请运行 composer dump-autoload !\n"; exit(0); } else { - echo "添加失败!请按任意键继续!"; + echo zm_internal_errcode("E00006") . "添加失败!请按任意键继续!"; fgets(STDIN); exit(1); } @@ -62,13 +76,19 @@ class ConsoleApplication extends Application new DaemonReloadCommand(), new DaemonStopCommand(), new RunServerCommand(), //运行主服务的指令控制器 - new InitCommand(), //初始化用的,用于项目初始化和phar初始化 new PureHttpCommand(), //纯HTTP服务器指令 new SystemdCommand() ]); 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); } @@ -84,14 +104,20 @@ class ConsoleApplication extends Application try { return parent::run($input, $output); } catch (Exception $e) { - die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); + die(zm_internal_errcode("E00005") . "{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); } } - private function selfCheck(): bool { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19\n"); - if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die("You must install swoole version >= 4.5.0 !"); - if (version_compare(PHP_VERSION, "7.2") == -1) die("PHP >= 7.2 required."); - return true; + /** + * 启动炸毛前要做的环境检查项目 + */ + private function selfCheck(): void { + if (!extension_loaded("swoole")) die(zm_internal_errcode("E00001") . "Can not find swoole extension.\n"); + if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die(zm_internal_errcode("E00002") . "You must install swoole version >= 4.5.0 !"); + if (version_compare(PHP_VERSION, "7.2") == -1) die(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 扩展!"); + die(); + } } } diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 1fe5bf7a..151b7314 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -4,8 +4,8 @@ namespace ZM\Context; -use Co; use Exception; +use Swoole\Coroutine; use Swoole\Http\Request; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; @@ -104,8 +104,7 @@ class Context implements ContextInterface * only can used by cq->message event function * @param $msg * @param bool $yield - * @return mixed - * @noinspection PhpMissingReturnTypeInspection + * @return array|bool */ public function reply($msg, $yield = false) { $data = $this->getData(); @@ -136,7 +135,7 @@ class Context implements ContextInterface /** * @param $msg * @param false $yield - * @return mixed|void + * @return void * @throws InterruptException */ public function finalReply($msg, $yield = false) { @@ -170,42 +169,6 @@ class Context implements ContextInterface throw new WaitTimeoutException($this, $timeout_prompt); } return $r["message"]; - /* - $cid = Co::getuid(); - $api_id = ZMAtomic::get("wait_msg_id")->add(1); - $hang = [ - "coroutine" => $cid, - "user_id" => $this->getData()["user_id"], - "message" => $this->getData()["message"], - "self_id" => $this->getData()["self_id"], - "message_type" => $this->getData()["message_type"], - "result" => null - ]; - if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { - $hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"]; - } - SpinLock::lock("wait_api"); - $hw = LightCacheInside::get("wait_api", "wait_api") ?? []; - $hw[$api_id] = $hang; - LightCacheInside::set("wait_api", "wait_api", $hw); - SpinLock::unlock("wait_api"); - $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { - $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null; - if (is_array($r)) { - Co::resume($r["coroutine"]); - } - }); - - Co::suspend(); - SpinLock::lock("wait_api"); - $hw = LightCacheInside::get("wait_api", "wait_api") ?? []; - $sess = $hw[$api_id]; - unset($hw[$api_id]); - LightCacheInside::set("wait_api", "wait_api", $hw); - $result = $sess["result"]; - if (isset($id)) swoole_timer_clear($id); - if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); - return $result;*/ } /** @@ -269,7 +232,7 @@ class Context implements ContextInterface /** @noinspection PhpMissingReturnTypeInspection */ public function cloneFromParent() { - set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); + set_coroutine_params(self::$context[Coroutine::getPcid()] ?? self::$context[$this->cid]); return context(); } diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php index e25ea2f4..aa8e71c0 100644 --- a/src/ZM/Event/EventManager.php +++ b/src/ZM/Event/EventManager.php @@ -54,9 +54,9 @@ class EventManager try { $dispatcher->dispatchEvent($vss, null); } catch (Exception $e) { - Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); + Console::error(zm_internal_errcode("E00034") . "Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); } catch (Error $e) { - Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage()); + 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!"); } diff --git a/src/ZM/Event/SwooleEvent/OnClose.php b/src/ZM/Event/SwooleEvent/OnClose.php index 1d5d902e..f94a47ac 100644 --- a/src/ZM/Event/SwooleEvent/OnClose.php +++ b/src/ZM/Event/SwooleEvent/OnClose.php @@ -4,9 +4,9 @@ namespace ZM\Event\SwooleEvent; -use Co; use Error; use Exception; +use Swoole\Coroutine; use ZM\Annotation\Swoole\OnCloseEvent; use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\SwooleHandler; @@ -27,7 +27,7 @@ class OnClose implements SwooleEvent { /** @noinspection PhpUnreachableStatementInspection */ public function onCall($server, $fd) { - unset(Context::$context[Co::getCid()]); + unset(Context::$context[Coroutine::getCid()]); Console::debug("Calling Swoole \"close\" event from fd=" . $fd); $conn = ManagerGM::get($fd); if ($conn === null) return; @@ -61,11 +61,11 @@ class OnClose implements SwooleEvent $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg); + Console::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("Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg); + Console::error(zm_internal_errcode("E00016") . "Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg); Console::trace(); } ManagerGM::popConnect($fd); diff --git a/src/ZM/Event/SwooleEvent/OnManagerStart.php b/src/ZM/Event/SwooleEvent/OnManagerStart.php index b65cad5a..6367e738 100644 --- a/src/ZM/Event/SwooleEvent/OnManagerStart.php +++ b/src/ZM/Event/SwooleEvent/OnManagerStart.php @@ -11,6 +11,7 @@ use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\Console; use ZM\Event\SwooleEvent; use ZM\Framework; +use ZM\Utils\SignalListener; /** * Class OnManagerStart @@ -21,9 +22,7 @@ class OnManagerStart implements SwooleEvent { public function onCall(Server $server) { if (!Framework::$argv["disable-safe-exit"]) { - pcntl_signal(SIGINT, function () { - Console::verbose("Interrupted in manager!"); - }); + SignalListener::signalManager(); } Console::verbose("进程 Manager 已启动"); } diff --git a/src/ZM/Event/SwooleEvent/OnMessage.php b/src/ZM/Event/SwooleEvent/OnMessage.php index b1f5a465..fcb7666d 100644 --- a/src/ZM/Event/SwooleEvent/OnMessage.php +++ b/src/ZM/Event/SwooleEvent/OnMessage.php @@ -56,11 +56,11 @@ class OnMessage implements SwooleEvent //Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::error(zm_internal_errcode("E00017") . "Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); Console::trace(); } catch (Error $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::error(zm_internal_errcode("E00017") . "Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg); Console::trace(); } diff --git a/src/ZM/Event/SwooleEvent/OnOpen.php b/src/ZM/Event/SwooleEvent/OnOpen.php index db41f2e1..d2779d27 100644 --- a/src/ZM/Event/SwooleEvent/OnOpen.php +++ b/src/ZM/Event/SwooleEvent/OnOpen.php @@ -5,9 +5,9 @@ namespace ZM\Event\SwooleEvent; use Closure; -use Co; use Error; use Exception; +use Swoole\Coroutine; use Swoole\Http\Request; use ZM\Annotation\Swoole\OnOpenEvent; use ZM\Annotation\Swoole\OnSwooleEvent; @@ -30,20 +30,20 @@ class OnOpen implements SwooleEvent /** @noinspection PhpUnreachableStatementInspection */ public function onCall($server, Request $request) { Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); - unset(Context::$context[Co::getCid()]); + 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); - Console::warning("Unauthorized access_token: " . $access_token); + Console::warning(zm_internal_errcode("E00018") . "Unauthorized access_token: " . $access_token); return; } } elseif (is_string($token)) { if ($access_token !== $token && $token !== "") { $server->close($request->fd); - Console::warning("Unauthorized access_token: " . $access_token); + Console::warning(zm_internal_errcode("E00019") . "Unauthorized access_token: " . $access_token); return; } } @@ -81,11 +81,11 @@ class OnOpen implements SwooleEvent $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::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("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::error(zm_internal_errcode("E00020") . "Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); Console::trace(); } } diff --git a/src/ZM/Event/SwooleEvent/OnPipeMessage.php b/src/ZM/Event/SwooleEvent/OnPipeMessage.php index e2adf0cf..2a5f846a 100644 --- a/src/ZM/Event/SwooleEvent/OnPipeMessage.php +++ b/src/ZM/Event/SwooleEvent/OnPipeMessage.php @@ -4,10 +4,13 @@ namespace ZM\Event\SwooleEvent; +use Error; +use Exception; use Swoole\Server; use ZM\Annotation\Swoole\SwooleHandler; +use ZM\Console\Console; use ZM\Event\SwooleEvent; -use ZM\Utils\ProcessManager; +use ZM\Utils\Manager\ProcessManager; /** * Class OnPipeMessage @@ -18,6 +21,16 @@ class OnPipeMessage implements SwooleEvent { public function onCall(Server $server, $src_worker_id, $data) { $data = json_decode($data, true); - ProcessManager::workerAction($src_worker_id, $data); + try { + ProcessManager::workerAction($src_worker_id, $data); + } catch (Exception $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error(zm_internal_errcode("E00021") . "Uncaught exception " . get_class($e) . " when calling \"pipeMessage\": " . $error_msg); + Console::trace(); + } catch (Error $e) { + $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; + Console::error(zm_internal_errcode("E00021") . "Uncaught " . get_class($e) . " when calling \"pipeMessage\": " . $error_msg); + Console::trace(); + } } } \ No newline at end of file diff --git a/src/ZM/Event/SwooleEvent/OnRequest.php b/src/ZM/Event/SwooleEvent/OnRequest.php index bcf2a02c..8869cf95 100644 --- a/src/ZM/Event/SwooleEvent/OnRequest.php +++ b/src/ZM/Event/SwooleEvent/OnRequest.php @@ -4,9 +4,9 @@ namespace ZM\Event\SwooleEvent; -use Co; use Error; use Exception; +use Swoole\Coroutine; use Swoole\Http\Request; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Swoole\OnRequestEvent; @@ -33,7 +33,7 @@ class OnRequest implements SwooleEvent foreach (ZMConfig::get("global")["http_header"] as $k => $v) { $response->setHeader($k, $v); } - unset(Context::$context[Co::getCid()]); + unset(Context::$context[Coroutine::getCid()]); Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd); set_coroutine_params(["request" => $request, "response" => $response]); @@ -86,11 +86,11 @@ class OnRequest implements SwooleEvent ); if (!$response->isEnd()) { if (ZMConfig::get("global", "debug_mode")) - $response->end("Internal server exception: " . $e->getMessage()); + $response->end(zm_internal_errcode("E00023") . "Internal server exception: " . $e->getMessage()); else - $response->end("Internal server error."); + $response->end(zm_internal_errcode("E00023") . "Internal server error."); } - Console::error("Internal server exception (500), caused by " . get_class($e) . ": " . $e->getMessage()); + 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); @@ -100,11 +100,11 @@ class OnRequest implements SwooleEvent if (!$response->isEnd()) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; if (ZMConfig::get("global", "debug_mode")) - $response->end("Internal server error: " . $error_msg); + $response->end(zm_internal_errcode("E00023") . "Internal server error: " . $error_msg); else - $response->end("Internal server error."); + $response->end(zm_internal_errcode("E00023") . "Internal server error."); } - Console::error("Internal server error (500), caused by " . get_class($e) . ": " . $e->getMessage()); + Console::error(zm_internal_errcode("E00023") . "Internal server error (500), caused by " . get_class($e) . ": " . $e->getMessage()); Console::log($e->getTraceAsString(), "gray"); } } diff --git a/src/ZM/Event/SwooleEvent/OnStart.php b/src/ZM/Event/SwooleEvent/OnStart.php index f3830139..64ef20ce 100644 --- a/src/ZM/Event/SwooleEvent/OnStart.php +++ b/src/ZM/Event/SwooleEvent/OnStart.php @@ -4,15 +4,18 @@ namespace ZM\Event\SwooleEvent; +use Error; +use Exception; use Swoole\Event; -use Swoole\Process; use Swoole\Server; use ZM\Annotation\Swoole\SwooleHandler; -use ZM\Config\ZMConfig; use ZM\Console\Console; use ZM\Event\SwooleEvent; use ZM\Framework; +use ZM\Store\ZMBuf; use ZM\Utils\DataProvider; +use ZM\Utils\SignalListener; +use ZM\Utils\Terminal; use ZM\Utils\ZMUtil; /** @@ -23,48 +26,49 @@ use ZM\Utils\ZMUtil; class OnStart implements SwooleEvent { public function onCall(Server $server) { - $r = null; if (!Framework::$argv["disable-safe-exit"]) { - Process::signal(SIGINT, function () use ($r, $server) { - if (zm_atomic("_int_is_reload")->get() === 1) { - zm_atomic("_int_is_reload")->set(0); - $server->reload(); - } else { - echo "\r"; - Console::warning("Server interrupted(SIGINT) on Master."); - if ((Framework::$server->inotify ?? null) !== null) - /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); - Process::kill($server->master_pid, SIGTERM); - } - }); - } - if (Framework::$argv["daemon"]) { - $daemon_data = json_encode([ - "pid" => $server->master_pid, - "stdout" => ZMConfig::get("global")["swoole"]["log_file"] - ], 128 | 256); - file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data); + SignalListener::signalMaster($server); } if (Framework::$argv["watch"]) { if (extension_loaded('inotify')) { Console::info("Enabled File watcher, framework will reload automatically."); /** @noinspection PhpUndefinedFieldInspection */ Framework::$server->inotify = $fd = inotify_init(); - $this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd); + $this->addWatcher(DataProvider::getSourceRootDir() . "/src", $fd); Event::add($fd, function () use ($fd) { $r = inotify_read($fd); - Console::verbose("File updated: ".$r[0]["name"]); + Console::verbose("File updated: " . $r[0]["name"]); ZMUtil::reload(); }); } else { - Console::warning("You have not loaded \"inotify\" extension, please install first."); + Console::warning(zm_internal_errcode("E00024") . "You have not loaded \"inotify\" extension, please install first."); } } + if (Framework::$argv["interact"]) { + ZMBuf::$terminal = $r = STDIN; + Event::add($r, function () use ($r) { + $fget = fgets($r); + if ($fget === false) { + Event::del($r); + return; + } + $var = trim($fget); + 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() . ")"); + } + }); + } } private function addWatcher($maindir, $fd) { $dir = scandir($maindir); - unset($dir[0], $dir[1]); + if ($dir[0] == ".") { + unset($dir[0], $dir[1]); + } foreach ($dir as $subdir) { if (is_dir($maindir . "/" . $subdir)) { Console::debug("添加监听目录:" . $maindir . "/" . $subdir); diff --git a/src/ZM/Event/SwooleEvent/OnTask.php b/src/ZM/Event/SwooleEvent/OnTask.php index cd299130..1c19e9e1 100644 --- a/src/ZM/Event/SwooleEvent/OnTask.php +++ b/src/ZM/Event/SwooleEvent/OnTask.php @@ -65,11 +65,11 @@ class OnTask implements SwooleEvent $dispatcher->dispatchEvents(); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"task\": " . $error_msg); + 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("Uncaught " . get_class($e) . " when calling \"task\": " . $error_msg); + Console::error(zm_internal_errcode("E00026") . "Uncaught " . get_class($e) . " when calling \"task\": " . $error_msg); Console::trace(); } } diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php index dc375960..59642000 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStart.php @@ -12,7 +12,6 @@ use Swoole\Database\PDOConfig; use Swoole\Database\PDOPool; use Swoole\Process; use Swoole\Server; -use Swoole\Timer; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Swoole\OnStart; use ZM\Annotation\Swoole\OnSwooleEvent; @@ -26,13 +25,14 @@ use ZM\Event\EventDispatcher; use ZM\Event\EventManager; use ZM\Event\SwooleEvent; use ZM\Exception\DbException; +use ZM\Exception\ZMException; use ZM\Framework; use ZM\Module\QQBot; use ZM\Store\LightCacheInside; use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\Redis\ZMRedisPool; use ZM\Utils\DataProvider; -use ZM\Utils\ProcessManager; +use ZM\Utils\SignalListener; /** * Class OnWorkerStart @@ -43,18 +43,10 @@ class OnWorkerStart implements SwooleEvent { public function onCall(Server $server, $worker_id) { if (!Framework::$argv["disable-safe-exit"]) { - Process::signal(SIGINT, function () use ($worker_id, $server) { - - }); + SignalListener::signalWorker($server, $worker_id); } unset(Context::$context[Coroutine::getCid()]); if ($server->taskworker === false) { - if (!Framework::$argv["disable-safe-exit"]) { - Process::signal(SIGUSR1, function () use ($worker_id) { - Timer::clearAll(); - ProcessManager::resumeAllWorkerCoroutines(); - }); - } zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid); if (LightCacheInside::get("wait_api", "wait_api") !== null) { LightCacheInside::unset("wait_api", "wait_api"); @@ -63,7 +55,8 @@ class OnWorkerStart implements SwooleEvent register_shutdown_function(function () use ($server) { $error = error_get_last(); if (($error["type"] ?? -1) != 0) { - Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); + Console::error(zm_internal_errcode("E00027") . "Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); + zm_dump($error); } //DataProvider::saveBuffer(); /** @var Server $server */ @@ -71,7 +64,7 @@ class OnWorkerStart implements SwooleEvent else server()->shutdown(); }); - Console::verbose("Worker #{$server->worker_id} 启动中"); + Console::verbose("Worker #{$server->worker_id} starting"); Framework::$server = $server; //ZMBuf::resetCache(); //清空变量缓存 //ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 @@ -90,12 +83,12 @@ class OnWorkerStart implements SwooleEvent phpinfo(); //这个phpinfo是有用的,不能删除 $str = ob_get_clean(); $str = explode("\n", $str); - foreach ($str as $k => $v) { + 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("未安装 mysqlnd php-mysql扩展。"); + throw new DbException(zm_internal_errcode("E00028") . "未安装 mysqlnd php-mysql扩展。"); } } $sql = ZMConfig::get("global", "sql_config"); @@ -115,7 +108,7 @@ class OnWorkerStart implements SwooleEvent // 开箱即用的Redis $redis = ZMConfig::get("global", "redis_config"); if ($redis !== null && $redis["host"] != "") { - if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n"); + if (!extension_loaded("redis")) Console::error(zm_internal_errcode("E00029") . "Can not find redis extension.\n"); else ZMRedisPool::init($redis); } @@ -136,11 +129,11 @@ class OnWorkerStart implements SwooleEvent Console::success("Worker #" . $worker_id . " started"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); - Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); + Console::error(zm_internal_errcode("E00030") . $e->getMessage() . "\n" . $e->getTraceAsString()); Process::kill($server->master_pid, SIGTERM); return; } catch (Error $e) { - Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); + 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); @@ -153,11 +146,11 @@ class OnWorkerStart implements SwooleEvent Console::success("TaskWorker #" . $server->worker_id . " started"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); - Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); + Console::error(zm_internal_errcode("E00030") . $e->getMessage() . "\n" . $e->getTraceAsString()); Process::kill($server->master_pid, SIGTERM); return; } catch (Error $e) { - Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); + 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); @@ -189,15 +182,13 @@ class OnWorkerStart implements SwooleEvent //加载各个模块的注解类,以及反射 Console::debug("检索Module中"); $parser = new AnnotationParser(); - $path = DataProvider::getWorkingDir() . "/src/"; - $dir = scandir($path); - unset($dir[0], $dir[1]); - $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); - foreach ($dir as $v) { - if (is_dir($path . "/" . $v) && isset($composer["autoload"]["psr-4"][$v . "\\"]) && !in_array($composer["autoload"]["psr-4"][$v . "\\"], $composer["extra"]["exclude_annotate"] ?? [])) { - if (\server()->worker_id == 0) - Console::verbose("Add " . $v . " to register path"); - $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v); + $composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true); + foreach ($composer["autoload"]["psr-4"] as $k => $v) { + if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) { + if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue; + if (trim($k, "\\") == "ZM") continue; + if (\server()->worker_id == 0) Console::verbose("Add " . $v . ":$k to register path"); + $parser->addRegisterPath(DataProvider::getSourceRootDir() . "/" . $v . "/", trim($k, "\\")); } } $parser->registerMods(); @@ -207,7 +198,7 @@ class OnWorkerStart implements SwooleEvent Console::debug("加载自定义上下文中..."); $context_class = ZMConfig::get("global", "context_class"); if (!is_a($context_class, ContextInterface::class, true)) { - throw new Exception("Context class must implemented from ContextInterface!"); + throw new ZMException(zm_internal_errcode("E00032") ."Context class must implemented from ContextInterface!"); } //加载插件 diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php index 007d9ffe..59d3b54f 100644 --- a/src/ZM/Exception/DbException.php +++ b/src/ZM/Exception/DbException.php @@ -4,7 +4,11 @@ namespace ZM\Exception; +use Throwable; + class DbException extends ZMException { - + public function __construct($message = "", $code = 0, Throwable $previous = null) { + parent::__construct(zm_internal_errcode("E00043") . $message, $code, $previous); + } } diff --git a/src/ZM/Exception/ExceptionHandler.php b/src/ZM/Exception/ExceptionHandler.php new file mode 100644 index 00000000..137bfc7c --- /dev/null +++ b/src/ZM/Exception/ExceptionHandler.php @@ -0,0 +1,15 @@ +getTtyWidth(); self::$argv = $args; - - ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); ZMConfig::setEnv($args["env"] ?? ""); if (ZMConfig::get("global") === false) { - die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + die (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"); } //定义常量 @@ -80,9 +78,10 @@ class Framework ] ]); } catch (ConnectionManager\TableException $e) { - die($e->getMessage()); + die(zm_internal_errcode("E00008") . $e->getMessage()); } try { + Console::init( ZMConfig::get("global", "info_level") ?? 2, self::$server, @@ -97,13 +96,18 @@ class Framework $this->server_set["log_level"] = SWOOLE_LOG_DEBUG; $add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false; + $this->loadServerEvents(); + $this->parseCliArgs(self::$argv, $add_port); + if (!isset($this->server_set["max_wait_time"])) { $this->server_set["max_wait_time"] = 5; } $worker = $this->server_set["worker_num"] ?? swoole_cpu_num(); define("ZM_WORKER_NUM", $worker); + ZMAtomic::init(); + // 打印初始信息 $out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port"); if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)"; @@ -116,6 +120,9 @@ class Framework if (isset($this->server_set["task_worker_num"])) { $out["task_worker"] = $this->server_set["task_worker_num"]; } + if (!isset($this->server_set["pid_file"])) { + $this->server_set["pid_file"] = ZMConfig::get("crash_dir") . ".zm.pid"; + } if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") { $conf = ZMConfig::get("global", "sql_config"); $out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"]; @@ -141,6 +148,8 @@ class Framework if ($args["preview"]) { exit(); } + + self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); if ($add_port) { @@ -196,11 +205,11 @@ class Framework Terminal::executeCommand(trim($data)); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::error(zm_internal_errcode("E00009") . "Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); Console::trace(); } catch (Error $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); + Console::error(zm_internal_errcode("E00009") . "Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); Console::trace(); } @@ -224,11 +233,11 @@ class Framework // 注册 Swoole Server 的事件 $this->registerServerEvents(); $r = ZMConfig::get("global", "light_cache") ?? [ - "size" => 1024, - "max_strlen" => 8192, - "hash_conflict_proportion" => 0.6, - "persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), - "auto_save_interval" => 900 + 'size' => 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(); @@ -269,15 +278,15 @@ class Framework }, E_ALL | E_STRICT); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); - Console::error($e->getMessage()); + Console::error(zm_internal_errcode("E00010") . $e->getMessage()); Console::debug($e); die; } } private static function printMotd($tty_width) { - if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { - $motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); + 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"); } @@ -290,10 +299,27 @@ class Framework } public function start() { - self::$loaded_files = get_included_files(); - self::$server->start(); - zm_atomic("server_is_stopped")->set(1); - Console::log("zhamao-framework is stopped."); + try { + self::$loaded_files = get_included_files(); + self::$server->start(); + zm_atomic("server_is_stopped")->set(1); + Console::log("zhamao-framework is stopped."); + } catch (Throwable $e) { + die(zm_internal_errcode("E00011") . "Framework has an uncaught " . get_class($e) . ": " . $e->getMessage() . PHP_EOL); + } + } + + private function loadServerEvents() { + $r = exec(PHP_BINARY . " " . DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php", $output, $result_code); + if ($result_code !== 0) { + Console::error("Parsing code error!"); + exit(1); + } + $json = json_decode($r, true); + if (!is_array($json)) { + Console::warning(zm_internal_errcode("E00012") . "Parsing @SwooleHandler and @OnSetup error!"); + } + $this->setup_events = $json; } /** @@ -303,18 +329,13 @@ class Framework */ private function registerServerEvents() { $reader = new AnnotationReader(); - global $master_events; - $master_events = [ - "setup" => [], - "event" => [] - ]; - $all = getAllClasses(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent"); + $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) { // 类名形式的注解 - $master_events["event"][]=[ + $this->setup_events["event"][] = [ "class" => $v, "method" => "onCall", "event" => $anno_class->event @@ -322,66 +343,14 @@ class Framework } } - $base_path = DataProvider::getWorkingDir(); - $scan_paths = []; - $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); - foreach (($composer["autoload"]["psr-4"] ?? []) as $k => $v) { - if (is_dir($base_path."/".$v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) { - $scan_paths[trim($k, "\\")]=realpath($base_path."/".$v); - } - } - $all_event_class = []; - foreach($scan_paths as $namespace => $autoload_path) { - $all_event_class = array_merge($all_event_class, getAllClasses($autoload_path."/", $namespace)); - } - $process = new Process(function(Process $process) use ($all_event_class) { - $reader = new AnnotationReader(); - $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() - ]; - } - } - } - } - $sock = $process->exportSocket(); - $sock->send(json_encode(["setup" => $setup_list, "event" => $event_list])); - }); - $process->start(); - go(function() use ($process) { - $socket = $process->exportSocket(); - global $master_events; - $obj = json_decode($socket->recv(), true); - if ($obj["setup"] != []) $master_events["setup"] = array_merge($master_events["setup"], $obj["setup"]); - if ($obj["event"] != []) $master_events["event"] = array_merge($master_events["event"], $obj["event"]); - }); - Process::wait(true); - Event::wait(); - - foreach($master_events["setup"] as $k => $v) { + foreach (($this->setup_events["setup"] ?? []) as $v) { $c = ZMUtil::getModInstance($v["class"]); $method = $v["method"]; $c->$method(); } - foreach ($master_events["event"] as $k => $v) { - self::$server->on($v["event"], function (...$param) use ($k, $v) { + foreach ($this->setup_events["event"] as $v) { + self::$server->on($v["event"], function (...$param) use ($v) { ZMUtil::getModInstance($v["class"])->{$v["method"]}(...$param); }); } @@ -403,7 +372,7 @@ class Framework if (intval($y) >= 1 && intval($y) <= 1024) { $this->server_set["worker_num"] = intval($y); } else { - Console::warning("Invalid worker num! Turn to default value (" . ($this->server_set["worker_num"] ?? swoole_cpu_num()) . ")"); + Console::warning(zm_internal_errcode("E00013") . "Invalid worker num! Turn to default value (" . ($this->server_set["worker_num"] ?? swoole_cpu_num()) . ")"); } break; case 'task-worker-num': @@ -411,7 +380,7 @@ class Framework $this->server_set["task_worker_num"] = intval($y); $this->server_set["task_enable_coroutine"] = true; } else { - Console::warning("Invalid worker num! Turn to default value (0)"); + Console::warning(zm_internal_errcode("E00013") . "Invalid worker num! Turn to default value (0)"); } break; case 'disable-coroutine': @@ -421,7 +390,7 @@ class Framework $coroutine_mode = false; $terminal_id = null; self::$argv["watch"] = true; - echo ("* You are in debug mode, do not use in production!\n"); + echo("* You are in debug mode, do not use in production!\n"); break; case 'daemon': $this->server_set["daemonize"] = 1; @@ -463,7 +432,8 @@ class Framework } } } - if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); + $global_hook = ZMConfig::get("global", 'runtime')['swoole_coroutine_hook_flags'] ?? SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL); + if ($coroutine_mode && $global_hook === false) Runtime::enableCoroutine(true, $global_hook); else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL); } @@ -554,7 +524,9 @@ class Framework } public static function getTtyWidth(): string { - return explode(" ", trim(exec("stty size")))[1]; + $size = exec("stty size"); + if (empty($size)) return 65; + return explode(" ", trim($size))[1]; } public static function loadFrameworkState() { diff --git a/src/ZM/Http/RouteManager.php b/src/ZM/Http/RouteManager.php index bbe7be16..bd729069 100644 --- a/src/ZM/Http/RouteManager.php +++ b/src/ZM/Http/RouteManager.php @@ -3,58 +3,12 @@ namespace ZM\Http; - -use Symfony\Component\Routing\Route; -use Symfony\Component\Routing\RouteCollection; -use ZM\Annotation\Http\Controller; -use ZM\Annotation\Http\RequestMapping; -use ZM\Console\Console; -use ZM\Store\LightCacheInside; - -class RouteManager +/** + * 为了兼容,豁出去了(2.5),此兼容类 + * Class RouteManager + * @package ZM\Http + * @deprecated + */ +class RouteManager extends \ZM\Utils\Manager\RouteManager { - /** @var null|RouteCollection */ - public static $routes = null; - - public static function importRouteByAnnotation(RequestMapping $vss, $method, $class, $methods_annotations) { - if (self::$routes === null) self::$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; - Console::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}"; - Console::debug("添加静态文件路由:" . $route_name); - $route = new Route($route_name, ['_class' => RouteManager::class, '_method' => "onStaticRoute"]); - //echo $path.PHP_EOL; - LightCacheInside::set("static_route", $route->getPath(), $path); - - self::$routes->add(md5($route_name), $route); - } - - public function onStaticRoute($params) { - $route_path = self::$routes->get($params["_route"])->getPath(); - if(($path = LightCacheInside::get("static_route", $route_path)) === null) { - ctx()->getResponse()->endWithStatus(404); - return false; - } - unset($params["_route"]); - $obj = array_shift($params); - return new StaticFileHandler($obj, $path); - } } \ No newline at end of file diff --git a/src/ZM/Module/ModulePacker.php b/src/ZM/Module/ModulePacker.php new file mode 100644 index 00000000..c4af6df8 --- /dev/null +++ b/src/ZM/Module/ModulePacker.php @@ -0,0 +1,206 @@ +module = $module; + } + + /** + * 设置输出文件夹 + * @param $path + */ + public function setOutputPath($path) { + $this->output_path = $path; + } + + /** + * 设置是否覆盖 + * @param bool $override + */ + public function setOverride(bool $override = true) { + $this->override = $override; + } + + /** + * 获取模块名字 + * @return mixed + */ + public function getName() { + return $this->module['name']; + } + + /** + * 获取打包的文件名绝对路径 + * @return string + */ + 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)) { + Console::info('Overwriting ' . $this->filename); + unlink($this->filename); + } + } + + $this->phar = new Phar($this->filename); + $this->phar->startBuffering(); + Console::info('模块输出文件:' . $this->filename); + + $this->addFiles(); //添加文件 + $this->addLightCacheStore(); //保存light-cache-store指定的项 + $this->addModuleConfig(); //生成module-config.json + $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 { + return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, true); + } + + 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 (strcmp($path, $v) === 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) { + Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:`$v` 不存在或值为null,无法为其保存。'); + } else { + $store[$v] = $r; + Console::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'], + 'autoload-psr-4' => $this->generatePharAutoload(), + 'unpack' => [ + 'composer-autoload-items' => $this->getComposerAutoloadItems(), + 'global-config-override' => !empty($this->module['global-config-override'] ?? []) ? $this->module['global-config-override'] : false + ], + 'allow-hotload' => empty($this->module['global-config-override'] ?? []) && !isset($this->module['depends']) + ]; + $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')); + } +} \ No newline at end of file diff --git a/src/ZM/Module/ModuleUnpacker.php b/src/ZM/Module/ModuleUnpacker.php new file mode 100644 index 00000000..3e628b4c --- /dev/null +++ b/src/ZM/Module/ModuleUnpacker.php @@ -0,0 +1,23 @@ +module = $module; + } + + /** + * 解包模块 + * @return array + */ + public function unpack(): array { + // TODO: 解包模块到src + return $this->module; + } +} \ No newline at end of file diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index b146de16..59bf5be0 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -11,7 +11,7 @@ use ZM\Console\Console; use ZM\Event\EventDispatcher; use ZM\Exception\ZMException; use ZM\Framework; -use ZM\Utils\ProcessManager; +use ZM\Utils\Manager\ProcessManager; class LightCache { @@ -26,7 +26,6 @@ class LightCache * @param $config * @return bool|mixed * @throws Exception - * @noinspection PhpMissingReturnTypeInspection */ public static function init($config) { self::$config = $config; @@ -44,17 +43,17 @@ class LightCache $write = self::set($k, $v); Console::verbose("Writing LightCache: " . $k); if ($write === false) { - self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; + self::$last_error = zm_internal_errcode("E00051") . '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; return false; } } } } if ($result === false) { - self::$last_error = '系统内存不足,申请失败'; + self::$last_error = zm_internal_errcode("E00050") . '系统内存不足,申请失败'; } else { $obj = Framework::loadFrameworkState(); - foreach(($obj["expiring_light_cache"] ?? []) as $k => $v) { + foreach (($obj["expiring_light_cache"] ?? []) as $k => $v) { $value = $v["value"]; if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); @@ -87,7 +86,7 @@ class LightCache * @throws ZMException */ public static function get(string $key) { - if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache"); self::checkExpire($key); $r = self::$kv_table->get($key); return $r === false ? null : self::parseGet($r); @@ -99,7 +98,7 @@ class LightCache * @throws ZMException */ public static function getExpire(string $key) { - if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache"); self::checkExpire($key); $r = self::$kv_table->get($key, "expire"); return $r === false ? null : $r - time(); @@ -112,7 +111,7 @@ class LightCache * @since 2.4.3 */ public static function getExpireTS(string $key) { - if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache"); self::checkExpire($key); $r = self::$kv_table->get($key, "expire"); return $r === false ? null : $r; @@ -124,10 +123,9 @@ class LightCache * @param int $expire * @return mixed * @throws ZMException - * @noinspection PhpMissingReturnTypeInspection */ public static function set(string $key, $value, int $expire = -1) { - if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache"); if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); if (strlen($value) >= self::$config["max_strlen"]) return false; @@ -140,7 +138,7 @@ class LightCache $data_type = "bool"; $value = json_encode($value); } else { - throw new ZMException("Only can set string, array and int"); + throw new ZMException(zm_internal_errcode("E00049") . "Only can set string, array and int"); } try { return self::$kv_table->set($key, [ @@ -158,10 +156,9 @@ class LightCache * @param $value * @return bool|mixed * @throws ZMException - * @noinspection PhpMissingReturnTypeInspection */ public static function update(string $key, $value) { - if (self::$kv_table === null) throw new ZMException("not initialized LightCache."); + if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache."); if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); if (strlen($value) >= self::$config["max_strlen"]) return false; @@ -171,7 +168,7 @@ class LightCache } elseif (is_int($value)) { $data_type = "int"; } else { - throw new ZMException("Only can set string, array and int"); + throw new ZMException(zm_internal_errcode("E00048") . "Only can set string, array and int"); } try { if (self::$kv_table->get($key) === false) return false; diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php index 92c3ab38..4febdba1 100644 --- a/src/ZM/Store/LightCacheInside.php +++ b/src/ZM/Store/LightCacheInside.php @@ -67,6 +67,6 @@ class LightCacheInside 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 ZMException("内存不足,创建静态表失败!"); + if ($r === false) throw new ZMException(zm_internal_errcode("E00050") . "内存不足,创建静态表失败!"); } } diff --git a/src/ZM/Store/Redis/ZMRedisPool.php b/src/ZM/Store/Redis/ZMRedisPool.php index 590cc08b..7179d2b0 100644 --- a/src/ZM/Store/Redis/ZMRedisPool.php +++ b/src/ZM/Store/Redis/ZMRedisPool.php @@ -30,7 +30,7 @@ class ZMRedisPool var_dump($r); } } catch (RedisException $e) { - Console::error("Redis init failed! " . $e->getMessage()); + Console::error(zm_internal_errcode("E00047") . "Redis init failed! " . $e->getMessage()); self::$pool = null; } } diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php index c1c8bed3..7ded8dbb 100644 --- a/src/ZM/Store/ZMAtomic.php +++ b/src/ZM/Store/ZMAtomic.php @@ -38,6 +38,7 @@ class ZMAtomic for ($i = 0; $i < 10; ++$i) { self::$atomics["_tmp_" . $i] = new Atomic(0); } + self::$atomics["ss"] = new Atomic(1); } diff --git a/src/ZM/Utils/CoMessage.php b/src/ZM/Utils/CoMessage.php index 05c22f50..cda82734 100644 --- a/src/ZM/Utils/CoMessage.php +++ b/src/ZM/Utils/CoMessage.php @@ -4,10 +4,11 @@ namespace ZM\Utils; -use Co; +use Swoole\Coroutine; use ZM\Store\LightCacheInside; use ZM\Store\Lock\SpinLock; use ZM\Store\ZMAtomic; +use ZM\Utils\Manager\ProcessManager; class CoMessage { @@ -16,10 +17,9 @@ class CoMessage * @param array $compare * @param int $timeout * @return mixed - * @noinspection PhpMissingReturnTypeInspection */ public static function yieldByWS(array $hang, array $compare, $timeout = 600) { - $cid = Co::getuid(); + $cid = Coroutine::getuid(); $api_id = ZMAtomic::get("wait_msg_id")->add(1); $hang["compare"] = $compare; $hang["coroutine"] = $cid; @@ -33,10 +33,10 @@ class CoMessage $id = swoole_timer_after($timeout * 1000, function () use ($api_id) { $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null; if (is_array($r)) { - Co::resume($r["coroutine"]); + Coroutine::resume($r["coroutine"]); } }); - Co::suspend(); + Coroutine::suspend(); SpinLock::lock("wait_api"); $sess = LightCacheInside::get("wait_api", "wait_api"); $result = $sess[$api_id]["result"] ?? null; @@ -70,7 +70,7 @@ class CoMessage if ($all[$last]["worker_id"] != server()->worker_id) { ProcessManager::sendActionToWorker($all[$last]["worker_id"], "resume_ws_message", $all[$last]); } else { - Co::resume($all[$last]["coroutine"]); + Coroutine::resume($all[$last]["coroutine"]); } return true; } else { diff --git a/src/ZM/Utils/CoroutinePool.php b/src/ZM/Utils/CoroutinePool.php index 042500b8..1abe6835 100644 --- a/src/ZM/Utils/CoroutinePool.php +++ b/src/ZM/Utils/CoroutinePool.php @@ -38,6 +38,10 @@ class CoroutinePool 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]); diff --git a/src/ZM/Utils/DataProvider.php b/src/ZM/Utils/DataProvider.php index df320dd4..729e467f 100644 --- a/src/ZM/Utils/DataProvider.php +++ b/src/ZM/Utils/DataProvider.php @@ -11,44 +11,139 @@ class DataProvider { public static $buffer_list = []; + /** + * 返回资源目录 + * @return string + */ public static function getResourceFolder(): string { return self::getWorkingDir() . '/resources/'; } + /** + * 返回工作目录,不带最右边文件夹的斜杠(/) + * @return false|string + */ public static function getWorkingDir() { return WORKING_DIR; } + /** + * 获取框架所在根目录 + * @return false|string + */ + public static function getFrameworkRootDir() { + return FRAMEWORK_ROOT_DIR; + } + + /** + * 获取源码根目录,除Phar模式外均与工作目录相同 + * @return false|string + */ + public static function getSourceRootDir() { + return defined("SOURCE_ROOT_DIR") ? SOURCE_ROOT_DIR : WORKING_DIR; + } + + /** + * 获取框架反代链接 + * @return array|false|mixed|null + */ public static function getFrameworkLink() { return ZMConfig::get("global", "http_reverse_link"); } - public static function getDataFolder() { + /** + * 获取zm_data数据目录,如果二级目录不为空,则自动创建目录并返回 + * @param string $second + * @return array|false|mixed|string|null + */ + public static function getDataFolder(string $second = '') { + if ($second !== '') { + @mkdir(ZM_DATA . $second); + if (!is_dir(ZM_DATA . $second)) return false; + return realpath(ZM_DATA . $second) . "/"; + } return ZM_DATA; } + /** + * 将变量保存在zm_data下的数据目录,传入数组 + * @param $filename + * @param $file_array + * @return false|int + */ public static function saveToJson($filename, $file_array) { $path = ZMConfig::get("global", "config_dir"); $r = explode("/", $filename); - if(count($r) == 2) { - $path = $path . $r[0]."/"; - if(!is_dir($path)) mkdir($path); + if (count($r) == 2) { + $path = $path . $r[0] . "/"; + if (!is_dir($path)) mkdir($path); $name = $r[1]; } elseif (count($r) != 1) { - Console::warning("存储失败,文件名只能有一级目录"); + Console::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)); + return file_put_contents($path . $name . ".json", json_encode($file_array, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } + /** + * 从json加载变量到内存 + * @param $filename + * @return mixed|null + */ public static function loadFromJson($filename) { $path = ZMConfig::get("global", "config_dir"); - if(file_exists($path.$filename.".json")) { - return json_decode(file_get_contents($path.$filename.".json"), true); + if (file_exists($path . $filename . ".json")) { + return json_decode(file_get_contents($path . $filename . ".json"), true); } else { return null; } } + + /** + * 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表 + * @param $dir + * @param bool $recursive + * @param bool|string $relative + * @return array|false + * @since 2.5 + */ + public static function scanDirFiles($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 { + Console::warning(zm_internal_errcode("E00058") . "Relative path is not generated: wrong base directory ($relative)"); + return false; + } + } + } + return $list; + } + + /** + * 检查路径是否为相对路径(根据第一个字符是否为"/"来判断) + * @param $path + * @return bool + * @since 2.5 + */ + public static function isRelativePath($path) { + return strlen($path) > 0 && $path[0] === '/'; + } } diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php index ccb3c497..14e77520 100644 --- a/src/ZM/Utils/HttpUtil.php +++ b/src/ZM/Utils/HttpUtil.php @@ -4,7 +4,7 @@ namespace ZM\Utils; -use Co; +use Swoole\Coroutine; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Matcher\UrlMatcher; @@ -13,7 +13,7 @@ use Symfony\Component\Routing\RouteCollection; use ZM\Config\ZMConfig; use ZM\Console\Console; use ZM\Http\Response; -use ZM\Http\RouteManager; +use ZM\Utils\Manager\RouteManager; class HttpUtil { @@ -51,7 +51,7 @@ class HttpUtil public static function getHttpCodePage(int $http_code) { if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) { - return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); + return Coroutine::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); } else return null; } diff --git a/src/ZM/Utils/Manager/ModuleManager.php b/src/ZM/Utils/Manager/ModuleManager.php new file mode 100644 index 00000000..397db4bd --- /dev/null +++ b/src/ZM/Utils/Manager/ModuleManager.php @@ -0,0 +1,115 @@ +setOutputPath(DataProvider::getDataFolder() . "output"); + $packer->setOverride(); + $packer->pack(); + return true; + } catch (ModulePackException $e) { + Console::error($e->getMessage()); + return false; + } + } + + /** + * 解包模块 TODO + * @param $module + * @return array|false + */ + public static function unpackModule($module) { + try { + $packer = new ModuleUnpacker($module); + return $packer->unpack(); + } catch (ModulePackException $e) { + Console::error($e->getMessage()); + return false; + } + } +} \ No newline at end of file diff --git a/src/ZM/Utils/ProcessManager.php b/src/ZM/Utils/Manager/ProcessManager.php similarity index 93% rename from src/ZM/Utils/ProcessManager.php rename to src/ZM/Utils/Manager/ProcessManager.php index e53f386e..66dbd15e 100644 --- a/src/ZM/Utils/ProcessManager.php +++ b/src/ZM/Utils/Manager/ProcessManager.php @@ -1,10 +1,10 @@ $action, "data" => $data]; if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) { - Console::warning("Cannot send worker action from master or manager process!"); + Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!"); return; } if (server()->worker_id == $worker_id) { @@ -114,9 +114,9 @@ class ProcessManager Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!"); return; } - foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) { + foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) { if (isset($v["coroutine"], $v["worker_id"])) { - if (server()->worker_id == $v["worker_id"]) Co::resume($v["coroutine"]); + if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]); } } } diff --git a/src/ZM/Utils/Manager/RouteManager.php b/src/ZM/Utils/Manager/RouteManager.php new file mode 100644 index 00000000..ed97cf26 --- /dev/null +++ b/src/ZM/Utils/Manager/RouteManager.php @@ -0,0 +1,67 @@ +prefix; + break; + } + } + $tail = trim($vss->route, "/"); + $route_name = $prefix . ($tail === "" ? "" : "/") . $tail; + Console::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}"; + Console::debug("添加静态文件路由:" . $route_name); + $route = new Route($route_name, ['_class' => RouteManager::class, '_method' => "onStaticRoute"]); + //echo $path.PHP_EOL; + LightCacheInside::set("static_route", $route->getPath(), $path); + + self::$routes->add(md5($route_name), $route); + } + + public function onStaticRoute($params) { + $route_path = self::$routes->get($params["_route"])->getPath(); + if(($path = LightCacheInside::get("static_route", $route_path)) === null) { + ctx()->getResponse()->endWithStatus(404); + return false; + } + unset($params["_route"]); + $obj = array_shift($params); + return new StaticFileHandler($obj, $path); + } +} \ No newline at end of file diff --git a/src/ZM/Utils/TaskManager.php b/src/ZM/Utils/Manager/TaskManager.php similarity index 64% rename from src/ZM/Utils/TaskManager.php rename to src/ZM/Utils/Manager/TaskManager.php index 1942ca80..802aa7ce 100644 --- a/src/ZM/Utils/TaskManager.php +++ b/src/ZM/Utils/Manager/TaskManager.php @@ -1,7 +1,7 @@ setting["task_worker_num"])) { - Console::warning("未开启 TaskWorker 进程,请先修改 global 配置文件启用!"); + Console::warning(zm_internal_errcode("E00056") . "未开启 TaskWorker 进程,请先修改 global 配置文件启用!"); return false; } $r = server()->taskwait(["task" => $task_name, "params" => $params], $timeout); diff --git a/src/ZM/Utils/MessageUtil.php b/src/ZM/Utils/MessageUtil.php index 7fc501c4..7dae8473 100644 --- a/src/ZM/Utils/MessageUtil.php +++ b/src/ZM/Utils/MessageUtil.php @@ -11,6 +11,7 @@ use ZM\Console\Console; use ZM\Entity\MatchResult; use ZM\Event\EventManager; use ZM\Requests\ZMRequest; +use ZM\Utils\Manager\ProcessManager; class MessageUtil { @@ -25,7 +26,7 @@ class MessageUtil if (!is_dir($path)) mkdir($path); $path = realpath($path); if ($path === false) { - Console::warning("指定的路径错误不存在!"); + Console::warning(zm_internal_errcode("E00059") . "指定的路径错误不存在!"); return false; } $files = []; @@ -34,7 +35,7 @@ class MessageUtil if ($v->type == "image") { $result = ZMRequest::downloadFile($v->params["url"], $path . "/" . $v->params["file"]); if ($result === false) { - Console::warning("图片 " . $v->params["url"] . " 下载失败!"); + Console::warning(zm_internal_errcode("E00060") . "图片 " . $v->params["url"] . " 下载失败!"); return false; } $files[] = $path . "/" . $v->params["file"]; @@ -112,7 +113,7 @@ class MessageUtil $ls = EventManager::$events[CQCommand::class] ?? []; $word = self::splitCommand($msg); $matched = new MatchResult(); - foreach ($ls as $k => $v) { + foreach ($ls as $v) { if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue; elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) && ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) && diff --git a/src/ZM/Utils/SignalListener.php b/src/ZM/Utils/SignalListener.php new file mode 100644 index 00000000..290ee3f7 --- /dev/null +++ b/src/ZM/Utils/SignalListener.php @@ -0,0 +1,75 @@ +get() === 1) { + zm_atomic("_int_is_reload")->set(0); + $server->reload(); + } else { + echo "\r"; + Console::warning("Server interrupted(SIGINT) on Master."); + if ((Framework::$server->inotify ?? null) !== null) + /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); + if (ZMBuf::$terminal !== null) + Event::del(ZMBuf::$terminal); + Process::kill($server->master_pid, SIGTERM); + } + }); + } + + /** + * 监听Manager进程的Ctrl+C + */ + public static function signalManager() { + $func = function () { + Console::verbose("Interrupted in manager!"); + }; + 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 Server $server + * @param $worker_id + */ + public static function signalWorker(Server $server, $worker_id) { + Process::signal(SIGINT, function () use ($worker_id, $server) { + // do nothing + }); + if ($server->taskworker === false) { + Process::signal(SIGUSR1, function () use ($worker_id) { + Timer::clearAll(); + ProcessManager::resumeAllWorkerCoroutines(); + }); + } + } +} \ No newline at end of file diff --git a/src/ZM/Utils/Terminal.php b/src/ZM/Utils/Terminal.php index fee5714d..bdd5f4b5 100644 --- a/src/ZM/Utils/Terminal.php +++ b/src/ZM/Utils/Terminal.php @@ -33,13 +33,13 @@ class Terminal $dispatcher = new EventDispatcher(TerminalCommand::class); $dispatcher->setRuleFunction(function ($v) use ($it) { /** @var TerminalCommand $v */ - return $v->command == $it[0] || $v->alias == $it[0]; + return !empty($it) && ($v->command == $it[0] || $v->alias == $it[0]); }); $dispatcher->setReturnFunction(function () { EventDispatcher::interrupt('none'); }); $dispatcher->dispatchEvents($it); - if ($dispatcher->store !== 'none') { + if ($dispatcher->store !== 'none' && $cmd !== "") { Console::info("Command not found: " . $cmd); return true; } @@ -55,7 +55,7 @@ class Terminal Console::$type($log_msg); $r = ob_get_clean(); $all = ManagerGM::getAllByName("terminal"); - foreach ($all as $k => $v) { + foreach ($all as $v) { server()->send($v->getFd(), "\r" . $r); server()->send($v->getFd(), ">>> "); } @@ -72,7 +72,7 @@ class Terminal $class = new Terminal(); $reader = new AnnotationReader(); $reflection = new ReflectionClass($class); - foreach ($reflection->getMethods() as $k => $v) { + foreach ($reflection->getMethods() as $v) { $r = $reader->getMethodAnnotation($v, TerminalCommand::class); if ($r !== null) { Console::debug("adding command " . $r->command); @@ -176,7 +176,7 @@ class Terminal * @TerminalCommand(command="stop",description="停止框架") */ public function stop() { - Process::kill(server()->master_pid, SIGTERM); + posix_kill(server()->master_pid, SIGTERM); } /** diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index df28c0ca..c7cf97e0 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -18,13 +18,13 @@ class ZMUtil * @throws Exception */ public static function stop() { - if (SpinLock::tryLock("_stop_signal") === false) return; - Console::warning(Console::setColor("Stopping server...", "red")); + if (SpinLock::tryLock('_stop_signal') === false) return; + Console::warning(Console::setColor('Stopping server...', 'red')); if (Console::getLevel() >= 4) Console::trace(); - ZMAtomic::get("stop_signal")->set(1); + ZMAtomic::get('stop_signal')->set(1); for ($i = 0; $i < ZM_WORKER_NUM; ++$i) { - if (Process::kill(zm_atomic("_#worker_" . $i)->get(), 0)) - Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1); + if (Process::kill(zm_atomic('_#worker_' . $i)->get(), 0)) + Process::kill(zm_atomic('_#worker_' . $i)->get(), SIGUSR1); } server()->shutdown(); } @@ -38,7 +38,7 @@ class ZMUtil public static function getModInstance($class) { if (!isset(ZMBuf::$instance[$class])) { - //Console::debug("Class instance $class not exist, so I created it."); + //Console::debug('Class instance $class not exist, so I created it.'); return ZMBuf::$instance[$class] = new $class(); } else { return ZMBuf::$instance[$class]; @@ -52,11 +52,44 @@ class ZMUtil public static function getReloadableFiles() { return array_map( function ($x) { - return str_replace(DataProvider::getWorkingDir() . "/", "", $x); + return str_replace(DataProvider::getSourceRootDir() . '/', '', $x); }, array_diff( get_included_files(), Framework::$loaded_files ) ); } + + /** + * 使用Psr-4标准获取目录下的所有类 + * @param $dir + * @param $base_namespace + * @param null|mixed $rule + * @param bool $return_kv + * @return String[] + */ + public static function getClassesPsr4($dir, $base_namespace, $rule = null, $return_kv = false) { + // 预先读取下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') { + if ($rule === null) { //规则未设置回调时候,使用默认的识别过滤规则 + if (substr(file_get_contents($dir . '/' . $v), 6, 6) == '#plain') continue; + elseif (mb_substr($v, 0, 7) == 'global_' || mb_substr($v, 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 ($return_kv) $classes[$class_name] = $v; + else $classes[] = $class_name; + } + } + return $classes; + } } diff --git a/src/ZM/global_defines.php b/src/ZM/global_defines.php index 745c3510..b46424d0 100644 --- a/src/ZM/global_defines.php +++ b/src/ZM/global_defines.php @@ -5,7 +5,7 @@ use ZM\Utils\DataProvider; define("ZM_START_TIME", microtime(true)); define("ZM_DATA", ZMConfig::get("global", "zm_data")); -define("APP_VERSION", LOAD_MODE == 1 ? (json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true)["version"] ?? "unknown") : "unknown"); +define("APP_VERSION", LOAD_MODE == 1 ? (json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true)["version"] ?? "unknown") : "unknown"); define("CRASH_DIR", ZMConfig::get("global", "crash_dir")); define("MAIN_WORKER", ZMConfig::get("global", "worker_cache")["worker"] ?? 0); if (!is_dir(ZM_DATA)) @mkdir(ZM_DATA); diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index ae1e54ee..59828904 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -1,10 +1,10 @@ $v) { + foreach ($msgs as $v) { if (trim($v) == "") continue; $ls[] = trim($v); } return $ls; } +/** @noinspection PhpUnused */ function unicode_decode($str) { return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); }, $str); } -/** - * 获取模块文件夹下的每个类文件的类名称 - * @param $dir - * @param $indoor_name - * @return array - */ -function getAllClasses($dir, $indoor_name): array { - if (!is_dir($dir)) return []; - $list = scandir($dir); - $classes = []; - if ($list[0] == '.') unset($list[0], $list[1]); - foreach ($list as $v) { - //echo "Finding " . $dir . $v . PHP_EOL; - //echo "At " . $indoor_name . PHP_EOL; - if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v)); - elseif (mb_substr($v, -4) == ".php") { - if (substr(file_get_contents($dir . $v), 6, 6) == "#plain") continue; - $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); - foreach ($composer["autoload"]["files"] as $fi) { - if (realpath(DataProvider::getWorkingDir() . "/" . $fi) == realpath($dir . $v)) { - continue 2; - } - } - if ($v == "global_function.php") continue; - $class_name = $indoor_name . "\\" . mb_substr($v, 0, -4); - $classes [] = $class_name; - } - } - return $classes; -} - function matchPattern($pattern, $context): bool { if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "") return true; @@ -124,7 +94,7 @@ function split_explode($del, $str, $divide_en = false): array { $str = implode($del, $str); //echo $str."\n"; $ls = []; - foreach (explode($del, $str) as $k => $v) { + foreach (explode($del, $str) as $v) { if (trim($v) == "") continue; $ls[] = $v; } @@ -178,8 +148,8 @@ function getAnnotations(): array { $s = debug_backtrace()[1]; //echo json_encode($s, 128|256); $list = []; - foreach (EventManager::$events as $event => $v) { - foreach ($v as $ks => $vs) { + foreach (EventManager::$events as $v) { + foreach ($v as $vs) { //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL; if ($vs->class == $s["class"] && $vs->method == $s["function"]) { $list[get_class($vs)][] = $vs; @@ -191,7 +161,7 @@ function getAnnotations(): array { function set_coroutine_params($array) { $cid = Co::getCid(); - if ($cid == -1) die("Cannot set coroutine params at none coroutine mode."); + if ($cid == -1) die(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], $array); else Context::$context[$cid] = $array; foreach (Context::$context as $c => $v) { @@ -261,11 +231,11 @@ function call_with_catch($callable) { $callable(); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; - Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::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("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg); + Console::error(zm_internal_errcode("E00033") . "Uncaught " . get_class($e) . ": " . $error_msg); Console::trace(); } } @@ -298,15 +268,15 @@ function server(): ?Server { } /** - * @return ZMRobot + * @return OneBotV11 * @throws RobotNotFoundException * @throws ZMException */ -function bot(): ZMRobot { +function bot() { if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) { - return ZMRobot::getRandom(); + return OneBotV11::getRandom(); } elseif ($conn != -1) { - if (($obj = ManagerGM::get($conn)) !== null) return new ZMRobot($obj); + if (($obj = ManagerGM::get($conn)) !== null) return new OneBotV11($obj); else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!"); } else { throw new RobotNotFoundException("没有任何机器人连接到框架!"); @@ -376,7 +346,11 @@ function zm_error($obj) { Console::error($obj); } function zm_config($name, $key = null) { return ZMConfig::get($name, $key); } function quick_reply_closure($reply) { - return function() use ($reply){ + return function () use ($reply) { return $reply; }; } + +function zm_internal_errcode($code): string { + return "[ErrCode:$code] "; +} diff --git a/src/ZM/script_phar_stub.php b/src/ZM/script_phar_stub.php new file mode 100644 index 00000000..9b1ec8c9 --- /dev/null +++ b/src/ZM/script_phar_stub.php @@ -0,0 +1,11 @@ + false]; \ No newline at end of file diff --git a/src/ZM/script_setup_loader.php b/src/ZM/script_setup_loader.php new file mode 100644 index 00000000..274fc9aa --- /dev/null +++ b/src/ZM/script_setup_loader.php @@ -0,0 +1,59 @@ +initEnv(); + + $base_path = DataProvider::getSourceRootDir(); + $scan_paths = []; + $composer = json_decode(file_get_contents($base_path . "/composer.json"), true); + foreach (($composer["autoload"]["psr-4"] ?? []) as $k => $v) { + if (is_dir($base_path . "/" . $v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) { + $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 AnnotationReader(); + $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/test/CoroutinePoolTest.php b/test/CoroutinePoolTest.php deleted file mode 100644 index cefced7b..00000000 --- a/test/CoroutinePoolTest.php +++ /dev/null @@ -1,28 +0,0 @@ -assertTrue(true); - Console::init(4); - CoroutinePool::setSize("default", 2); - CoroutinePool::defaultSize(50); - for ($i = 0; $i < 59; ++$i) { - CoroutinePool::go(function () use ($i) { - //Console::debug("第 $i 个马上进入睡眠..."); - ZMRequest::get("http://localhost:9002/test/ping"); - Console::verbose(strval($i)); - }); - } - } - - public function testA() { - $this->assertTrue(true); - } -} diff --git a/test/LightCacheTest.php b/test/LightCacheTest.php deleted file mode 100644 index 59dff6f3..00000000 --- a/test/LightCacheTest.php +++ /dev/null @@ -1,26 +0,0 @@ - 2, - "max_strlen" => 4096, - "hash_conflict_proportion" => 0, - "persistence_path" => "../composer.json" - ]); - //LightCache::set("bool", true); - $this->assertEquals(true, LightCache::set("2048", 123, 3)); - $this->assertArrayHasKey("2048", LightCache::getAll()); - sleep(3); - $this->assertArrayNotHasKey("2048", LightCache::getAll()); - $this->assertEquals("Apache-2.0", LightCache::get("license")); - $this->assertEquals("zhamao/framework", LightCache::get("name")); - //$this->assertTrue(LightCache::set("storage", "asdasd", -2)); - //LightCache::savePersistence(); - } -} diff --git a/test/ZM/Utils/DataProviderTest.php b/test/ZM/Utils/DataProviderTest.php new file mode 100644 index 00000000..773db1cc --- /dev/null +++ b/test/ZM/Utils/DataProviderTest.php @@ -0,0 +1,29 @@ +assertContains("Example/Hello.php", DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/Module', true, true)); + } + + public function testGetDataFolder() { + DataProvider::getDataFolder("testFolder"); + $this->assertDirectoryExists(DataProvider::getWorkingDir() . "/zm_data/testFolder"); + rmdir(DataProvider::getWorkingDir() . "/zm_data/testFolder"); + } +} \ No newline at end of file diff --git a/test/ZM/Utils/Manager/ModuleManagerTest.php b/test/ZM/Utils/Manager/ModuleManagerTest.php new file mode 100644 index 00000000..e128b453 --- /dev/null +++ b/test/ZM/Utils/Manager/ModuleManagerTest.php @@ -0,0 +1,50 @@ + "示例模块2" + ])); + + ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config'); + ZMConfig::setEnv($args["env"] ?? ""); + if (ZMConfig::get("global") === false) { + die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); + } + + //定义常量 + include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php"; + + Console::init( + ZMConfig::get("global", "info_level") ?? 2, + null, + $args["log-theme"] ?? "default", + ($o = ZMConfig::get("console_color")) === false ? [] : $o + ); + + $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; + date_default_timezone_set($timezone); + } + + public function tearDown(): void { + unlink(DataProvider::getSourceRootDir()."/src/Module/zm.json"); + } + + public function testGetConfiguredModules() { + zm_dump(ModuleManager::getConfiguredModules()); + $this->assertArrayHasKey("示例模块", ModuleManager::getConfiguredModules()); + } + + public function testPackModule() { + $list = ModuleManager::getConfiguredModules(); + $this->assertTrue(ModuleManager::packModule(current($list))); + } +} diff --git a/test/ZM/Utils/ZMUtilTest.php b/test/ZM/Utils/ZMUtilTest.php new file mode 100644 index 00000000..f1acd36a --- /dev/null +++ b/test/ZM/Utils/ZMUtilTest.php @@ -0,0 +1,19 @@ +assertContains(Hello::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/Module", "Module")); + $this->assertContains(TimerMiddleware::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/Module", "Module")); + $this->assertContains(Framework::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/ZM", "ZM")); + } +} \ No newline at end of file diff --git a/test/ZMTest/Testing/ModuleTest.php b/test/ZMTest/Testing/ModuleTest.php index a42644f6..082650e7 100644 --- a/test/ZMTest/Testing/ModuleTest.php +++ b/test/ZMTest/Testing/ModuleTest.php @@ -7,6 +7,7 @@ namespace ZMTest\Testing; use Module\Example\Hello; use PHPUnit\Framework\TestCase; use ZM\Config\ZMConfig; +use ZM\ConsoleApplication; use ZM\Utils\ZMUtil; class ModuleTest extends TestCase @@ -14,6 +15,7 @@ class ModuleTest extends TestCase protected function setUp(): void { ZMConfig::setDirectory(realpath(__DIR__."/../Mock")); set_coroutine_params([]); + (new ConsoleApplication('zhamao-test'))->initEnv(); require_once __DIR__ . '/../../../src/ZM/global_defines.php'; } diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 00000000..136d2f1a --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,13 @@ +