diff --git a/.gitignore b/.gitignore
index 8823c742..c44fa39b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,7 @@ zm.json
/zm_data/
composer.lock
/resources/server.phar
+/distribute/
+/bin/.phpunit.result.cache
+/resources/zhamao.service
+.phpunit.result.cache
diff --git a/Dockerfile b/Dockerfile
index 8130fa82..0225d218 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,29 +1,3 @@
-FROM ubuntu:18.04
-WORKDIR /app/
-RUN echo "Asia/Shanghai" > /etc/timezone
-ENV LANG C.UTF_8
-ENV LC_ALL C.UTF-8
-ENV LANGUAGE C.UTF-8
+FROM zmbot/swoole:latest
-RUN apt-get update && apt-get install -y software-properties-common tzdata
-RUN dpkg-reconfigure -f noninteractive tzdata
-VOLUME ["/app/zhamao-framework/"]
-RUN add-apt-repository ppa:ondrej/php && \
- apt-get update && \
- apt-get install php php-dev php-mbstring gcc make openssl \
- php-mbstring php-json php-curl php-mysql -y && \
- apt-get install wget composer -y && \
- wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \
- tar -zxvf v4.5.0.tar.gz && \
- cd swoole-src-4.5.0/ && \
- phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \
- (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}'))
-
-
-ADD . /app/zhamao-framework
-ADD . /app/zhamao-framework-bak
-#RUN cd /app/zhamao-framework && composer update && composer clearcache
-#RUN mv zhamao-framework-master zhamao-framework
-WORKDIR /app/zhamao-framework
-
-CMD ["/bin/bash", "-i", "/app/zhamao-framework-bak/.entry.sh"]
+# TODO: auto-setup entrypoint
diff --git a/README.md b/README.md
index f6eb04fd..555caf4e 100644
--- a/README.md
+++ b/README.md
@@ -7,78 +7,65 @@
[](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
[](https://packagist.org/packages/zhamao/framework)
[]()
-[]()
[](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
[](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
-
+
## 开发者注意
-**v2.0 版本已经开始公测了,但是文档还在光速编写中,可以现行进行测试!**
+**开发者 QQ 群:670821194**
-**炸毛框架目前经过实验可以直接在 PHP8 环境上运行,但是细节部分未经充分测试,如果在 PHP8 环境下运行出现问题,请及时提出 Issue,谢谢!**
+**当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!**
-**由于 CQHTTP 不再提供维护,转为 [OneBot 标准](https://github.com/howmanybots/onebot)(原 CQHTTP 插件衍生而来的机器人 HTTP 接口标准),本框架也将在未来改为兼容此标准。**
-
-**以上涉及的变更将在下一个大版本 (v2.0.0) 更新,请关注 2.0-dev 分支 和 Project 模块!**
-
-**v2.0版本即将到来,请持续关注 [新文档](https://docs-v2.zhamao.xin/) 进度和 Project 模块展示的测试进度!**
+**2.0 版本如果有问题请第一时间加群反馈!**
## 简介
-zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 标准,它会对微信公众号等终端收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。
+炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
-除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。
+框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
+
+```php
+/**
+ * @CQCommand("你好")
+ */
+public function hello() {
+ ctx()->reply("你好,我是炸毛!"); // 简单的命令式回复
+}
+/**
+ * @RequestMapping("/index")
+ */
+public function index() {
+ return "
';
+ let dialogs = i.innerHTML.split("\n");
+ for(let j of dialogs) {
+ if(j === '') continue;
+ if(j.substr(0, 2) === ') ') {
+ final += '
\n' +
+ '
' + j.substr(2) + '
\n' +
+ '

\n' +
+ '
';
+ } else if (j.substr(0, 2) === '( ') {
+ final += '
\n' +
+ '

\n' +
+ '
' + j.substr(2) + '
\n' +
+ '
';
+ }
+ }
+ i.innerHTML = final;
+ }
+}, 500);
diff --git a/docs/update/v1.md b/docs/update/v1.md
new file mode 100644
index 00000000..89d53221
--- /dev/null
+++ b/docs/update/v1.md
@@ -0,0 +1,220 @@
+# 更新日志(v1 版本)
+
+## v1.6.5
+
+> 更新时间:2020.12.9
+
+- 修复:版本号显示
+- 优化:依赖问题,减少对 PHP 扩展的依赖,转变为可选
+
+## v1.6.4
+
+> 更新时间:2020.12.9
+
+- 修复:composer require模式下自动加载的问题
+- 优化:减少不是必需的依赖问题
+
+## v1.6.3
+
+> 更新时间:2020.11.15
+
+- 修复:Response 对象使用 redirect 造成的递归报错
+- 修复:`document_index` 配置项在 `/` 路径下无法使用的 bug
+
+## v1.6.2
+
+> 更新时间:2020.7.27
+
+- 修复:不写配置 `server_event_handler_class` 项无法启动的 bug
+
+## v1.6.1
+
+> 更新时间:2020.7.26
+
+- 新增:`ZMRequest::downloadFile($url, $dst)` 方法,可直接将文件下载到本地
+
+## v1.6
+
+> 更新时间:2020.7.14
+
+- 新增:现在可以对类修饰自定义的注解了
+- 修复:数据库操作 where 对象时产生的歧义
+- 新增:支持自定义任何 Swoole Server 事件的注解绑定,详见文档
+- 修复:多个中间件注解对类只生效最后一个的 bug
+
+❗ 下面是框架升级需要手动进行的变更:
+
+- 新版本由于引进了自定义 Swoole Server 事件的机制,对 global.php 全局配置文件有了变动,需要添加以下内容才能正常启动(旧版本升级新版本用户,新用户无需操作):
+
+```php
+/** 注册 Swoole Server 事件注解的类列表 */
+$config['server_event_handler_class'] = [
+ \Framework\ServerEventHandler::class, //默认不可删除,否则会不能使用框架
+];
+```
+
+## v1.5.8
+
+> 更新时间:2020.6.26
+
+- 新增:`@CQCommand` 注解的 fullMatch 参数(全量正则表达式匹配)
+
+## v1.5.7
+
+> 更新时间:2020.6.20
+
+- 新增:ZM_BREAKPOINT 的短名称:BP
+- 优化:终端连接器自动重连
+- 修复:语法错误时防止循环报错
+
+## v1.5.6
+
+> 更新时间:2020.6.15
+
+- 新增:`@CQCommand` 注解支持 `message_type`,`user_id`,`group_id`,`discuss_id` 限定条件
+- 新增:PDO 数据库支持自定义 fetch_mode,可在 `global.php` 中的 `sql_config["sql_default_fetch_mode"]` 字段设置,也可以调用时 `DB::rawQuery("语句", [], PDO::FETCH_ASSOC);` 第三个参数可选
+- 🔴 废弃:`ModBase` 基类,基类继承机制将在 1.6 版本起完全删除
+
+## v1.5.5
+
+> 更新时间:2020.6.13
+
+- 修复:`@SwooleEventAt("close")` 下不能使用 `ctx()->getConnection()` 获取链接对象的 bug
+- 新增:init 命令,可在 `composer require zhamao/framework` 后使用 `vendor/bin/start init` 初始化项目目录结构和配置文件
+- 更新:默认模块新增机器人断开连接的回调事件
+
+## v1.5.4
+
+> 更新时间:2020.6.13
+
+- 新增:`@CQCommand` 下支持 alias 参数
+- 更新:将 autoload 变为 composer autoload(需要重新 composer update)
+
+## v1.5.3
+
+> 更新时间:2020.6.10
+
+- 修复:在 Linux 系统下 Terminal 无法正常使用的 bug
+
+## v1.5.2
+
+> 更新时间:2020.6.8
+
+- 新增:`ZM_VERSION` 常量,对应为当前框架版本
+- 修复:部分链接不带 `/` 会导致 ZMRequest 模块报错的 bug
+
+## v1.5.1
+
+> 更新时间:2020.6.5
+
+- 新增:ZMRequest::request() 自定义构建 HTTP 请求方法
+- 修复:一个不会导致崩溃的 warning 提示
+
+## v1.5
+
+> 更新时间:2020.6.5
+
+- 重要变更:支持从 composer 使用框架
+- 新增:数据库 Select 选择器支持 `count()` 方法
+- 修复:ZMRequest 中 https 和端口的指定顺序问题
+- 新增:ZMWebSocket 创建 WS 链接的轻量级客户端
+- 修复:数据库异常的捕获更改为 PDOException
+
+## v1.4
+
+> 更新时间:2020.5.23
+
+- 新增:自定义 motd
+- 新增:debug_mode 下断点调试功能
+- 新增:`@OnSave` 注解,储存自动保存的变量时事件激活
+- 新增:Swoole 版本检测
+- 新增:全局函数,以 `zm_` 开头的,详情见文档
+- 新增:`@LoadBuffer` 注解,只加载内存不自动保存的变量
+- 新增:局部静态文件服务
+- 新增:mysqlnd 扩展状态检测
+- 更新:将终端输入更换为多进程
+- 更新:将数据库连接池变更为 Swoole 官方的连接池,需要 Swoole 版本 >= 4.4.13
+- 更新:提升注解绑定的事件函数的执行效率
+- 修复:上下文 `getConnection()` 的 fd 无法获取的 bug
+- 修复:MySQL 长链接 gone away 自动重连的问题
+- 修复:MySQL 查询构造器无 WHERE 语句时会造成的 bug
+- 修复:调整各项资源初始化前后顺序
+
+不可逆修改:你需要重新执行一次 `composer update` 或重新拉取一次 Docker Image,因为 composer 依赖发生了变化。
+
+## v1.3.1
+
+> 更新时间:2020.5.10
+
+- 修复:DataProvider 下 setJsonData 新建文件夹的问题
+- 优化:默认 / 页面显示 `Hello Zhamao!` 文字
+- 优化:Exception 和 Fatal error 报错机制的改进
+- 修复:计时器没有上下文环境,发不了 API 的 bug
+
+❗ 下面是框架升级需要手动进行的变更:
+
+- 更改 MySQL 客户端为原生 PDO mysqlnd,如果之前使用 Docker 启动,则需使用新的 Dockerfile 构建。如果安装在本机,需安装 php-mysql 扩展。本次更新不影响框架内的 API,不需要更改任何代码。
+
+## v1.3.0
+
+> 更新时间:2020.5.8
+
+- 新增:上下文,具体更新都写到了文档里了!
+- 修复:ZMRobot 的 `setPrefix()` 的严重错误
+- 优化:优化部分代码
+- 改动:现在你可以和任意事件的注解使用任意中间件啦,而且还支持多中间件
+- 新增:CQHTTP + 酷Q + 炸毛框架 的 Dockerfile
+- 新增注解:`@CQAPISend`,`@CQAPIResponse`,是 API 调用后触发的事件,具体见文档说明
+
+## v1.2.1
+
+> 更新时间:2020.5.2
+
+- 新增:phar 启动模式构建脚本,你可以直接拉取 phar 运行框架了!
+- 优化:优化部分代码
+
+## v1.2
+
+> 更新时间:2020.4.29
+
+- 新增:systemd 生成脚本、一键 daemonize 守护进程方式常驻后台
+- 新增:示例模块的注释
+- 重构:Console 模块,现在有准确的控制台输出分级功能了
+- 新增:`@OnTick` 注解,用于绑定定时器(毫秒级)
+- 新增:`ZMRobot` 类,比调用 `CQAPI` 类发送 API 更方便,同时兼容最新版本的 `CQHTTP` 插件
+- 优化:使用键盘中断 `Ctrl+C`,不会丢失未保存的缓存数据了
+- 优化:完善上下文对象的方法
+- 新增:终端命令:`logtest`,测试输出的 log 类型
+
+:exclamation:下面是框架模块开发中需要注意的或有不兼容的修改内容:
+
+- 修改:`global.php` 中原来的 `info_level` 默认数值需要改为 `2`,保证终端输出和原来一致
+
+## v1.1.2
+
+> 更新时间:2020.4.26
+
+- 新增:静态文件服务器
+- 修复:`/` 路径的 Mapping 无法正常绑定的 bug
+
+## v1.1.1
+
+> 更新时间:2020.4.26
+
+- 新增:中间件对类的修饰
+- 新增:上下文对象对 IDE 的支持
+- 修复:数据库插入查询的愚蠢错误
+- 修复:数据库查询的 `value()` 不支持指定参数的 bug
+
+## v1.1.0
+
+> 更新时间:2020.3.29
+
+- 新增:中间件 `@Middleware` 功能
+- 修复:Websocket 链接关闭后未自动删除连接对象的bug
+
+## v1.0.0
+
+> 更新时间:2020.3.19
+
+正式版发布。
\ No newline at end of file
diff --git a/docs/update/v2.md b/docs/update/v2.md
new file mode 100644
index 00000000..dfe073bc
--- /dev/null
+++ b/docs/update/v2.md
@@ -0,0 +1,3 @@
+# 更新日志(v2 版本)
+
+> 暂未发布正式版。
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 00000000..d367b3ee
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,74 @@
+site_name: 炸毛框架 v2
+
+repo_name: '炸毛框架'
+repo_url: 'https://github.com/zhamao-robot/zhamao-framework'
+edit_uri: 'blob/2.0-dev/docs/'
+
+theme:
+ name: material
+ logo: assets/logos.png
+ favicon: assets/favicon.png
+ language: zh
+ features:
+ - navigation.tabs
+extra_javascript:
+ - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
+ - javascripts/config.js
+extra_css:
+ - assets/css/extra.css
+ - https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
+markdown_extensions:
+ - admonition
+ - pymdownx.tabbed
+ - pymdownx.superfences
+ - pymdownx.inlinehilite
+ - pymdownx.snippets
+ - abbr
+ - pymdownx.highlight:
+ linenums: true
+ linenums_style: pymdownx.inline
+extra:
+ version:
+ method: mike
+
+copyright: 'Copyright © 2019 - 2020 CrazyBot Team
+
+
+
+
+
蒙ICP备18000198号-1'
+
+nav:
+ - 指南:
+ - 介绍: index.md
+ - 安装框架: guide/安装.md
+ - 快速上手(机器人篇): guide/快速上手-机器人.md
+ - 快速上手(HTTP篇): guide/快速上手-http.md
+ - 选择聊天机器人实例: guide/OneBot实例.md
+ - 基本配置: guide/基本配置.md
+ - 编写模块: guide/编写模块.md
+ - 注册事件响应: guide/注册事件响应.md
+ - 事件和注解:
+ - 事件和注解: event/index.md
+ - 框架组件:
+ - 框架组件: component/index.md
+ - 进阶开发:
+ - 进阶开发: advanced/index.md
+ - 从 v1 升级: advanced/to-v2.md
+ - FAQ:
+ - FAQ: FAQ.md
+ - 更新日志:
+ - 更新日志(v2): update/v2.md
+ - 更新日志(v1): update/v1.md
+ -
炸毛框架 v1: https://docs-v1.zhamao.xin/
diff --git a/phar-starter.php b/phar-starter.php
deleted file mode 100644
index ba2fc9a0..00000000
--- a/phar-starter.php
+++ /dev/null
@@ -1,85 +0,0 @@
- 30000,
-]);
-
-date_default_timezone_set("Asia/Shanghai");
-
-define('WORKING_DIR', __DIR__);
-define('FRAMEWORK_DIR', __DIR__);
-define('LOAD_MODE', 2);
-
-$s = new FrameworkLoader($argv);
-
-function loadPhp($dir) {
- $dirs = scandir($dir);
- foreach ($dirs as $v) {
- $path = $dir . '/' . $v;
- if (is_dir($path)) {
- loadPhp($path);
- } else {
- if (pathinfo($dir . '/' . $v)['extension'] == 'php') {
- if(pathinfo($dir . '/' . $v)['basename'] == 'terminal_listener.php') continue;
- //echo 'loading '.$path.PHP_EOL;
- require_once $path;
- }
- }
- }
-}
-
-function testEnvironment() {
- $current_dir = realpath('.');
- @mkdir($current_dir . '/config/');
- if (!is_file($current_dir . '/config/global.php')) {
- echo "Exporting default global config...\n";
- $global = file_get_contents(__DIR__ . '/config/global.php');
- $global = str_replace("WORKING_DIR", 'realpath(__DIR__ . "/../")', $global);
- file_put_contents($current_dir . '/config/global.php', $global);
- }
- if (!is_file($current_dir . '/config/file_header.json')) {
- echo "Exporting default file_header config...\n";
- $global = file_get_contents(__DIR__ . '/config/file_header.json');
- file_put_contents($current_dir . '/config/file_header.json', $global);
- }
- if (!is_dir($current_dir . '/resources')) mkdir($current_dir . '/resources');
- if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src');
- if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src');
- if (!is_dir($current_dir . '/src/Module')) {
- mkdir($current_dir . '/src/Module');
- mkdir($current_dir . '/src/Module/Example');
- file_put_contents($current_dir . '/src/Module/Example/Hello.php', file_get_contents(__DIR__ . '/tmp/Hello.php.bak'));
- mkdir($current_dir . '/src/Module/Middleware');
- file_put_contents($current_dir . '/src/Module/Middleware/TimerMiddleware.php', file_get_contents(__DIR__ . '/tmp/TimerMiddleware.php.bak'));
- }
- if (!is_dir($current_dir . '/src/Custom')) {
- mkdir($current_dir . '/src/Custom');
- mkdir($current_dir . '/src/Custom/Annotation');
- mkdir($current_dir . '/src/Custom/Connection');
- file_put_contents($current_dir . '/src/Custom/global_function.php', "
+
+
+
+
Example page
+
+
+
+
+
diff --git a/src/Custom/Annotation/Example.php b/src/Custom/Annotation/Example.php
index 8d41fd51..4c255730 100644
--- a/src/Custom/Annotation/Example.php
+++ b/src/Custom/Annotation/Example.php
@@ -4,6 +4,7 @@
namespace Custom\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
+use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\CustomAnnotation;
/**
@@ -12,8 +13,8 @@ use ZM\Annotation\Interfaces\CustomAnnotation;
* @Target("ALL")
* @package Custom\Annotation
*/
-class Example implements CustomAnnotation
+class Example extends AnnotationBase implements CustomAnnotation
{
/** @var string */
- public $str;
-}
\ No newline at end of file
+ public $str = '';
+}
diff --git a/src/Custom/Command/CustomCommand.php b/src/Custom/Command/CustomCommand.php
new file mode 100644
index 00000000..9fe702f4
--- /dev/null
+++ b/src/Custom/Command/CustomCommand.php
@@ -0,0 +1,31 @@
+setDescription("custom description | 自定义命令的描述字段");
+ $this->addOption("failure", null, null, "以错误码为1返回结果");
+ // ...
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ if ($input->getOption("failure")) {
+ $output->writeln("
Hello error! I am wrong message.");
+ return Command::FAILURE;
+ } else {
+ $output->writeln("
Hello world! I am successful message.");
+ return Command::SUCCESS;
+ }
+ }
+}
diff --git a/src/Custom/Connection/CustomConnection.php b/src/Custom/Connection/CustomConnection.php
deleted file mode 100644
index 87b806cd..00000000
--- a/src/Custom/Connection/CustomConnection.php
+++ /dev/null
@@ -1,14 +0,0 @@
-get(), [1, 2])) {
- $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
- $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
- }
- if (!is_string($obj)) {
- if (isset($trace)) {
- var_dump($obj);
- return;
- } else $obj = "{Object}";
- }
- echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n");
- }
-
- static function warning($obj, $head = null) {
- if ($head === null) $head = date("[H:i:s]") . " [W] ";
- if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
- $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
- $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
- }
- if (ZMBuf::$atomics["info_level"]->get() >= 1) {
- if (!is_string($obj)) {
- if (isset($trace)) {
- var_dump($obj);
- return;
- } else $obj = "{Object}";
- }
- echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "blue" : "yellow") . "\n");
- }
- }
-
- static function info($obj, $head = null) {
- if ($head === null) $head = date("[H:i:s] ") . "[I] ";
- if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
- $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
- $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
- }
- if (ZMBuf::$atomics["info_level"]->get() >= 2) {
- if (!is_string($obj)) {
- if (isset($trace)) {
- var_dump($obj);
- return;
- } else $obj = "{Object}";
- }
- echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "black" : "lightblue") . "\n");
- }
- }
-
- static function success($obj, $head = null) {
- if ($head === null) $head = date("[H:i:s] ") . "[S] ";
- if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
- $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
- $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
- }
- if (ZMBuf::$atomics["info_level"]->get() >= 2) {
- if (!is_string($obj)) {
- if (isset($trace)) {
- var_dump($obj);
- return;
- } else $obj = "{Object}";
- }
- echo(self::setColor($head . ($trace ?? "") . $obj, "green") . "\n");
- }
- }
-
- static function verbose($obj, $head = null) {
- if ($head === null) $head = date("[H:i:s] ") . "[V] ";
- if (ZMBuf::$atomics["info_level"]->get() >= 3) {
- if (!is_string($obj)) {
- if (isset($trace)) {
- var_dump($obj);
- return;
- } else $obj = "{Object}";
- }
- echo(self::setColor($head . ($trace ?? "") . $obj, "blue") . "\n");
- }
- }
-
- static function debug($msg) {
- if (ZMBuf::$atomics["info_level"]->get() >= 4) Console::log(date("[H:i:s] ") . "[D] " . $msg, 'gray');
- }
-
- static function log($obj, $color = "") {
- if (!is_string($obj)) var_dump($obj);
- else echo(self::setColor($obj, $color) . "\n");
- }
-
- static function stackTrace() {
- $log = "Stack trace:\n";
- $trace = debug_backtrace();
- //array_shift($trace);
- foreach ($trace as $i => $t) {
- if (!isset($t['file'])) {
- $t['file'] = 'unknown';
- }
- if (!isset($t['line'])) {
- $t['line'] = 0;
- }
- if (!isset($t['function'])) {
- $t['function'] = 'unknown';
- }
- $log .= "#$i {$t['file']}({$t['line']}): ";
- if (isset($t['object']) and is_object($t['object'])) {
- $log .= get_class($t['object']) . '->';
- }
- $log .= "{$t['function']}()\n";
- }
- $log = Console::setColor($log, "gray");
- echo $log;
- }
-
- static function listenConsole() {
- if (in_array('--disable-console-input', FrameworkLoader::$argv) || in_array('--debug-mode', FrameworkLoader::$argv)) {
- self::info("ConsoleCommand disabled.");
- return;
- }
- global $terminal_id;
- global $port;
- $port = ZMBuf::globals("port");
- $vss = new SwooleEventAt();
- $vss->type = "open";
- $vss->level = 256;
- $vss->rule = "connectType:terminal";
- $terminal_id = call_user_func(function () {
- try {
- $data = random_bytes(16);
- } catch (Exception $e) {
- return "";
- }
- $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
- $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
- return strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)));
- });
- $vss->callback = function(?WSConnection $conn) use ($terminal_id){
- $req = ctx()->getRequest();
- if($conn->getType() != "terminal") return false;
- Console::debug("Terminal fd: ".$conn->fd);
- if(($req->header["x-terminal-id"] ?? "") != $terminal_id) {
- $conn->close();
- return false;
- }
- return false;
- };
- ZMBuf::$events[SwooleEventAt::class][] = $vss;
- $vss2 = new SwooleEventAt();
- $vss2->type = "message";
- $vss2->rule = "connectType:terminal";
- $vss2->callback = function(?WSConnection $conn){
- if ($conn === null) return false;
- if($conn->getType() != "terminal") return false;
- $cmd = ctx()->getFrame()->data;
- self::executeCommand($cmd);
- return false;
- };
- ZMBuf::$events[SwooleEventAt::class][] = $vss2;
- go(function () {
- global $terminal_id, $port;
- $descriptorspec = array(
- 0 => STDIN,
- 1 => STDOUT,
- 2 => STDERR
- );
- self::$console_proc = proc_open('php -r \'$terminal_id = "'.$terminal_id.'";$port = '.$port.';require "'.__DIR__.'/terminal_listener.php";\'', $descriptorspec, $pipes);
- });
- }
-
- /**
- * @param string $cmd
- * @return bool
- */
- private static function executeCommand(string $cmd) {
- $it = explodeMsg($cmd);
- switch ($it[0] ?? '') {
- case 'logtest':
- Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)");
- Console::error("This is error msg. (0)");
- Console::warning("This is warning msg. (1)");
- Console::info("This is info msg. (2)");
- Console::success("This is success msg. (2)");
- Console::verbose("This is verbose msg. (3)");
- Console::debug("This is debug msg. (4)");
- return true;
- case 'call':
- $class_name = $it[1];
- $function_name = $it[2];
- $class = new $class_name([]);
- call_user_func_array([$class, $function_name], []);
- return true;
- case 'bc':
- $code = base64_decode($it[1] ?? '', true);
- try {
- eval($code);
- } catch (Exception $e) {
- }
- return true;
- case 'echo':
- Console::info($it[1]);
- return true;
- case 'color':
- Console::log($it[2], $it[1]);
- return true;
- case 'stop':
- ZMUtil::stop();
- return false;
- case 'reload':
- case 'r':
- ZMUtil::reload();
- return false;
- case 'save':
- $origin = ZMBuf::$atomics["info_level"]->get();
- //ZMBuf::$atomics["info_level"]->set(3);
- DataProvider::saveBuffer();
- //ZMBuf::$atomics["info_level"]->set($origin);
- return true;
- case '':
- return true;
- default:
- Console::info("Command not found: " . $cmd);
- return true;
- }
- }
-
- public static function withSleep(string $string, int $int) {
- self::info($string);
- sleep($int);
- }
-}
diff --git a/src/Framework/DataProvider.php b/src/Framework/DataProvider.php
deleted file mode 100644
index 13de875e..00000000
--- a/src/Framework/DataProvider.php
+++ /dev/null
@@ -1,78 +0,0 @@
-get() >= 3)
- echo $head;
- foreach (self::$buffer_list as $k => $v) {
- Console::debug("Saving " . $k . " to " . $v);
- self::setJsonData($v, ZMBuf::get($k));
- }
- foreach (ZMBuf::$events[OnSave::class] ?? [] as $v) {
- $c = $v->class;
- $method = $v->method;
- $class = new $c();
- Console::debug("Calling @OnSave: $c -> $method");
- $class->$method();
- }
- if (ZMBuf::$atomics["info_level"]->get() >= 3)
- echo Console::setColor("saved", "blue") . PHP_EOL;
- }
-
- public static function getFrameworkLink() {
- return ZMBuf::globals("http_reverse_link");
- }
-
- public static function getJsonData(string $string) {
- if (!file_exists(self::getDataConfig() . $string)) return [];
- return json_decode(file_get_contents(self::getDataConfig() . $string), true);
- }
-
- public static function setJsonData($filename, array $args) {
- $pathinfo = pathinfo($filename);
- if (!is_dir(self::getDataConfig() . $pathinfo["dirname"])) {
- Console::debug("Making Directory: " . self::getDataConfig() . $pathinfo["dirname"]);
- mkdir(self::getDataConfig() . $pathinfo["dirname"]);
- }
- $r = file_put_contents(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING));
- if ($r === false) {
- Console::warning("无法保存文件: " . $filename);
- }
- }
-
- public static function getDataFolder() {
- return ZM_DATA;
- }
-}
diff --git a/src/Framework/FrameworkLoader.php b/src/Framework/FrameworkLoader.php
deleted file mode 100644
index fec6a6f6..00000000
--- a/src/Framework/FrameworkLoader.php
+++ /dev/null
@@ -1,213 +0,0 @@
-requireGlobalFunctions();
- if (LOAD_MODE == 0) define("WORKING_DIR", getcwd());
- elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
- elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
- //$this->registerAutoloader('classLoader');
- require_once "DataProvider.php";
- if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
- /** @noinspection PhpIncludeInspection */
- require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
- }
- if (LOAD_MODE == 0) {
- echo "* This is repository mode.\n";
- $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
- if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
- echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] ";
- $r = strtolower(trim(fgets(STDIN)));
- if ($r === "" || $r === "y") {
- $composer["autoload"]["psr-4"]["Module\\"] = "src/Module";
- $composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom";
- $r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
- if ($r !== false) {
- echo "成功添加!请重新进行 composer update !\n";
- exit(1);
- } else {
- echo "添加失败!请按任意键继续!";
- fgets(STDIN);
- exit(1);
- }
- } else {
- exit(1);
- }
- }
- }
- if (LOAD_MODE == 2) {
- require_once FRAMEWORK_DIR . "/vendor/autoload.php";
- spl_autoload_register('phar_classloader');
- }
-
-
- self::$settings = new GlobalConfig();
- if (self::$settings->get("debug_mode") === true) {
- $args[] = "--debug-mode";
- $args[] = "--disable-console-input";
- }
- self::$argv = $args;
- if (!in_array("--debug-mode", self::$argv)) {
- Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
- }
- self::$settings = new GlobalConfig();
- ZMBuf::$globals = self::$settings;
- if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file");
- $this->defineProperties();
-
- //start swoole Framework
- $this->selfCheck();
- try {
- $this->server = new Server(self::$settings->get("host"), self::$settings->get("port"));
- $settings = self::$settings->get("swoole");
- if (in_array("--daemon", $args)) {
- $settings["daemonize"] = 1;
- Console::log("已启用守护进程,输出重定向到 " . $settings["log_file"]);
- self::$argv[] = "--disable-console-input";
- }
- $this->server->set($settings);
- $all_event_class = self::$settings->get("server_event_handler_class") ?? [];
- if (!in_array(ServerEventHandler::class, $all_event_class)) {
- $all_event_class[] = ServerEventHandler::class;
- }
- $event_list = [];
- foreach ($all_event_class as $v) {
- $reader = new AnnotationReader();
- $reflection_class = new ReflectionClass($v);
- $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
- foreach ($methods as $vs) {
- $method_annotations = $reader->getMethodAnnotations($vs);
- if ($method_annotations != []) {
- $annotation = $method_annotations[0];
- if ($annotation instanceof OnEvent) {
- $annotation->class = $v;
- $annotation->method = $vs->getName();
- $event_list[strtolower($annotation->event)] = $annotation;
- }
- }
- }
- }
- foreach ($event_list as $k => $v) {
- $this->server->on($k, function (...$param) use ($v) {
- $c = $v->class;
- //echo $c.PHP_EOL;
- $c = new $c();
- call_user_func_array([$c, $v->method], $param);
- });
- }
-
- ZMBuf::initAtomic();
- if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
- if (in_array("--log-error", $args)) ZMBuf::$atomics["info_level"]->set(0);
- if (in_array("--log-warning", $args)) ZMBuf::$atomics["info_level"]->set(1);
- if (in_array("--log-info", $args)) ZMBuf::$atomics["info_level"]->set(2);
- if (in_array("--log-verbose", $args)) ZMBuf::$atomics["info_level"]->set(3);
- if (in_array("--log-debug", $args)) ZMBuf::$atomics["info_level"]->set(4);
- Console::log(
- "host: " . self::$settings->get("host") .
- ", port: " . self::$settings->get("port") .
- ", log_level: " . ZMBuf::$atomics["info_level"]->get() .
- ", version: " . ZM_VERSION .
- "\nworking_dir: " . DataProvider::getWorkingDir()
- );
- global $motd;
- if (!file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
- echo $motd;
- } else {
- echo file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
- }
- if (in_array("--debug-mode", self::$argv))
- Console::warning("You are in debug mode, do not use in production!");
- $this->server->start();
- } catch (Exception $e) {
- Console::error("Framework初始化出现错误,请检查!");
- Console::error($e->getMessage());
- die;
- }
- }
-
- private function requireGlobalFunctions()
- {
- require_once __DIR__ . '/global_functions.php';
- }
-
- private function defineProperties()
- {
- define("ZM_START_TIME", microtime(true));
- define("ZM_DATA", self::$settings->get("zm_data"));
- define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown");
- define("CONFIG_DIR", self::$settings->get("config_dir"));
- define("CRASH_DIR", self::$settings->get("crash_dir"));
- @mkdir(ZM_DATA);
- @mkdir(CONFIG_DIR);
- @mkdir(CRASH_DIR);
- define("ZM_MATCH_ALL", 0);
- define("ZM_MATCH_FIRST", 1);
- define("ZM_MATCH_NUMBER", 2);
- define("ZM_MATCH_SECOND", 3);
- define("ZM_BREAKPOINT", 'if(in_array("--debug-mode", \Framework\FrameworkLoader::$argv)) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));');
- define("BP", ZM_BREAKPOINT);
- define("ZM_DEFAULT_FETCH_MODE", self::$settings->get("sql_config")["sql_default_fetch_mode"] ?? 4);
- }
-
- private function selfCheck()
- {
- if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
- if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !");
- //if (!extension_loaded("gd")) die("Can not find gd extension.\n");
- //if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
- if (!function_exists("ctype_alpha")) die("Can not find ctype extension.\n");
- if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n");
- if (substr(PHP_VERSION, 0, 1) < "7") die("PHP >=7 required.\n");
- //if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
- //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
- //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
- return true;
- }
-}
-
-global $motd;
-$motd = <<
success = true;
- $this->config = $config;
- }
-
- public function get($key) {
- $r = $this->config[$key] ?? null;
- if ($r === null) return null;
- return $r;
- }
-
- public function getAll() {
- return $this->config;
- }
-}
diff --git a/src/Framework/RemoteShell.php b/src/Framework/RemoteShell.php
deleted file mode 100644
index 0e718824..00000000
--- a/src/Framework/RemoteShell.php
+++ /dev/null
@@ -1,264 +0,0 @@
-listen($host, $port, SWOOLE_SOCK_TCP);
- if (!$port) {
- throw new Exception("listen fail.");
- }
- $port->set(array(
- "open_eof_split" => true,
- 'package_eof' => "\r\n",
- ));
- $port->on("Connect", array(__CLASS__, 'onConnect'));
- $port->on("Close", array(__CLASS__, 'onClose'));
- $port->on("Receive", array(__CLASS__, 'onReceive'));
- if (method_exists($serv, 'getCallback')) {
- self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage');
- }
- $serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage'));
- self::$serv = $serv;
- }
-
- static function onConnect($serv, $fd, $reactor_id) {
- self::$contexts[$fd]['worker_id'] = $serv->worker_id;
- self::output($fd, implode("\r\n", self::$menu));
- }
-
- static function output($fd, $msg) {
- if (!isset(self::$contexts[$fd]['worker_id'])) {
- $msg .= "\r\nworker#" . self::$serv->worker_id . "$ ";
- } else {
- $msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ ";
- }
- self::$serv->send($fd, $msg);
- }
-
- static function onClose($serv, $fd, $reactor_id) {
- unset(self::$contexts[$fd]);
- }
-
- static function onPipeMessage($serv, $src_worker_id, $message) {
- //不是 debug 消息
- if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) {
- if (self::$oriPipeMessageCallback == null) {
- trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING);
- return true;
- }
- return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message);
- } else {
- $request = unserialize(substr($message, strlen(self::STX)));
- self::call($request['fd'], $request['func'], $request['args']);
- }
- return true ;
- }
-
- static protected function call($fd, $func, $args) {
- ob_start();
- call_user_func_array($func, $args);
- self::output($fd, ob_get_clean());
- }
-
- static protected function exec($fd, $func, $args) {
- //不在当前Worker进程
- if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) {
- self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']);
- } else {
- self::call($fd, $func, $args);
- }
- }
-
- static function getCoros() {
- var_export(iterator_to_array(Coroutine::listCoroutines()));
- }
-
- static function getBackTrace($_cid) {
- $info = Co::getBackTrace($_cid);
- if (!$info) {
- echo "coroutine $_cid not found.";
- } else {
- echo get_debug_print_backtrace($info);
- }
- }
-
- static function printVariant($var) {
- $var = ltrim($var, '$ ');
- var_dump($var);
- var_dump($$var);
- }
-
- static function evalCode($code) {
- eval($code . ';');
- }
-
- /**
- * @param $serv server
- * @param $fd
- * @param $reactor_id
- * @param $data
- */
- static function onReceive($serv, $fd, $reactor_id, $data) {
- $args = explode(" ", $data, 2);
- $cmd = trim($args[0]);
- unset($args[0]);
- switch ($cmd) {
- case 'w':
- case 'worker':
- if (!isset($args[1])) {
- self::output($fd, "invalid command.");
- break;
- }
- $dstWorkerId = intval($args[1]);
- self::$contexts[$fd]['worker_id'] = $dstWorkerId;
- self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]");
- break;
- case 'e':
- case 'exec':
- if (!isset($args[1])) {
- self::output($fd, "invalid command.");
- break;
- }
- $var = trim($args[1]);
- self::exec($fd, 'self::evalCode', [$var]);
- break;
- case 'p':
- case 'print':
- $var = trim($args[1]);
- self::exec($fd, 'self::printVariant', [$var]);
- break;
- case 'h':
- case 'help':
- self::output($fd, implode("\r\n", self::$menu));
- break;
- case 's':
- case 'stats':
- $stats = $serv->stats();
- self::output($fd, var_export($stats, true));
- break;
- case 'c':
- case 'coros':
- self::exec($fd, 'self::getCoros', []);
- break;
- /**
- * 查看协程堆栈
- */
- case 'bt':
- case 'b':
- case 'backtrace':
- if (empty($args[1])) {
- self::output($fd, "invalid command [" . trim($args[1]) . "].");
- break;
- }
- $_cid = intval($args[1]);
- self::exec($fd, 'self::getBackTrace', [$_cid]);
- break;
- case 'i':
- case 'info':
- if (empty($args[1])) {
- self::output($fd, "invalid command [" . trim($args[1]) . "].");
- break;
- }
- $_fd = intval($args[1]);
- $info = $serv->getClientInfo($_fd);
- if (!$info) {
- self::output($fd, "connection $_fd not found.");
- } else {
- self::output($fd, var_export($info, true));
- }
- break;
- case 'l':
- case 'list':
- $tmp = array();
- foreach ($serv->connections as $fd) {
- $tmp[] = $fd;
- if (count($tmp) > self::PAGESIZE) {
- self::output($fd, json_encode($tmp));
- $tmp = array();
- }
- }
- if (count($tmp) > 0) {
- self::output($fd, json_encode($tmp));
- }
- break;
- case 'q':
- case 'quit':
- $serv->close($fd);
- break;
- default:
- self::output($fd, "unknow command[$cmd]");
- break;
- }
- }
-}
-
-function get_debug_print_backtrace($traces) {
- $ret = array();
- foreach ($traces as $i => $call) {
- $object = '';
- if (isset($call['class'])) {
- $object = $call['class'] . $call['type'];
- if (is_array($call['args'])) {
- foreach ($call['args'] as &$arg) {
- get_arg($arg);
- }
- }
- }
- $ret[] = '#' . str_pad($i, 3, ' ')
- . $object . $call['function'] . '(' . implode(', ', $call['args'])
- . ') called at [' . $call['file'] . ':' . $call['line'] . ']';
- }
- return implode("\n", $ret);
-}
-
-function get_arg(&$arg) {
- if (is_object($arg)) {
- $arr = (array)$arg;
- $args = array();
- foreach ($arr as $key => $value) {
- if (strpos($key, chr(0)) !== false) {
- $key = ''; // Private variable found
- }
- $args[] = '[' . $key . '] => ' . get_arg($value);
- }
- $arg = get_class($arg) . ' Object (' . implode(',', $args) . ')';
- }
-}
\ No newline at end of file
diff --git a/src/Framework/ServerEventHandler.php b/src/Framework/ServerEventHandler.php
deleted file mode 100644
index b4cd762b..00000000
--- a/src/Framework/ServerEventHandler.php
+++ /dev/null
@@ -1,84 +0,0 @@
-taskworker === false) {
- FrameworkLoader::$run_time = microtime(true);
- EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
- } else {
- ob_start();
- AnnotationParser::registerMods();
- //加载Custom目录下的自定义的内部类
- ConnectionManager::registerCustomClass();
- ob_get_clean();
- }
- }
-
- /**
- * @OnEvent("message")
- * @param $server
- * @param Frame $frame
- * @throws AnnotationException
- */
- public function onMessage($server, Frame $frame) {
- Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd);
- EventHandler::callSwooleEvent("message", $server, $frame);
- }
-
- /**
- * @OnEvent("request")
- * @param $request
- * @param $response
- * @throws AnnotationException
- */
- public function onRequest($request, $response) {
- $response = new Response($response);
- Console::debug("Receiving Http request event, cid=" . Co::getCid());
- EventHandler::callSwooleEvent("request", $request, $response);
- }
-
- /**
- * @OnEvent("open")
- * @param $server
- * @param Request $request
- * @throws AnnotationException
- */
- public function onOpen($server, Request $request) {
- Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
- EventHandler::callSwooleEvent("open", $server, $request);
- }
-
- /**
- * @OnEvent("close")
- * @param $server
- * @param $fd
- * @throws AnnotationException
- */
- public function onClose($server, $fd) {
- Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
- EventHandler::callSwooleEvent("close", $server, $fd);
- }
-}
diff --git a/src/Framework/ZMBuf.php b/src/Framework/ZMBuf.php
deleted file mode 100755
index 238c0d3a..00000000
--- a/src/Framework/ZMBuf.php
+++ /dev/null
@@ -1,121 +0,0 @@
-get($key);
- }
-
- static function config($config_name) {
- return self::$config[$config_name] ?? null;
- }
-
- public static function resetCache() {
- self::$cache = [];
- self::$connect = [];
- self::$time_nlp = null;
- self::$instance = [];
- }
-
- /**
- * 初始化atomic计数器
- */
- public static function initAtomic() {
- foreach (ZMBuf::globals("init_atomics") as $k => $v) {
- self::$atomics[$k] = new Atomic($v);
- }
- }
-}
diff --git a/src/Framework/terminal_listener.php b/src/Framework/terminal_listener.php
deleted file mode 100644
index 24f72ac8..00000000
--- a/src/Framework/terminal_listener.php
+++ /dev/null
@@ -1,33 +0,0 @@
-set(['websocket_mask' => true]);
- $client->setHeaders(["x-terminal-id" => $terminal_id, 'x-pid' => posix_getppid()]);
- $ret = $client->upgrade("/?type=terminal");
- if ($ret) {
- while (true) {
- $line = fgets(STDIN);
- if ($line !== false) {
- $r = $client->push(trim($line));
- if (trim($line) == "reload" || trim($line) == "r" || trim($line) == "stop") {
- break;
- }
- if($r === false) {
- echo "Unable to connect framework terminal, connection closed. Trying to reconnect after 5s.\n";
- sleep(5);
- goto hello;
- }
- } else {
- break;
- }
- }
- } else {
- echo "Unable to connect framework terminal. port: $port\n";
- }
-});
-
diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php
index b87b350c..858ffe88 100644
--- a/src/Module/Example/Hello.php
+++ b/src/Module/Example/Hello.php
@@ -1,44 +1,66 @@
getQQ() . " 已连接!");
+ public function testCase() {
+ $a = new ZMRedis();
+ $redis = $a->get();
+ $r1 = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想设置的操作[r/w]");
+ switch ($r1) {
+ case "r":
+ $k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想读取的键名");
+ $result = $redis->get($k);
+ ctx()->reply("结果:" . $result);
+ break;
+ case "w":
+ $k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的键名");
+ $v = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的字符串");
+ $result = $redis->set($k, $v);
+ ctx()->reply("结果:" . ($result ? "成功" : "失败"));
+ break;
+ }
}
/**
- * 在机器人连接后向终端输出信息
- * @SwooleEventAt("close",rule="connectType:qq")
+ * 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ
+ * @CQCommand(".reload",user_id=627577391)
*/
- public function onDisconnect() {
- $conn = ctx()->getConnection();
- Console::info("机器人 " . $conn->getQQ() . " 已断开连接!");
+ public function reload() {
+ ctx()->reply("重启中...");
+ ZMUtil::reload();
}
/**
- * 向机器人发送"你好",即可回复这句话
+ * @CQCommand("我是谁")
+ */
+ public function whoami() {
+ $user = ctx()->getRobot()->getLoginInfo();
+ return "你是" . $user["data"]["nickname"] . ",QQ号是" . $user["data"]["user_id"];
+ }
+
+ /**
+ * 向机器人发送"你好啊",也可回复这句话
* @CQCommand(match="你好",alias={"你好啊","你是谁"})
*/
public function hello() {
@@ -46,27 +68,22 @@ class Hello
}
/**
- * @CQCommand(".reload")
- */
- public function reload() {
- context()->reply("reloading...");
- ZMUtil::reload();
- }
-
- /**
+ * 一个简单随机数的功能demo
+ * 问法1:随机数 1 20
+ * 问法2:从1到20的随机数
* @CQCommand("随机数")
- * @CQCommand(regexMatch="*从*到*的随机数")
- * @param $arg
+ * @CQCommand(pattern="*从*到*的随机数")
+ * @return string
*/
- public function randNum($arg) {
+ public function randNum() {
// 获取第一个数字类型的参数
- $num1 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第一个数字");
+ $num1 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第一个数字");
// 获取第二个数字类型的参数
- $num2 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第二个数字");
+ $num2 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第二个数字");
$a = min(intval($num1), intval($num2));
$b = max(intval($num1), intval($num2));
// 回复用户结果
- context()->reply("随机数是:".mt_rand($a, $b));
+ return "随机数是:" . mt_rand($a, $b);
}
/**
@@ -87,13 +104,48 @@ class Hello
return "Hello Zhamao!";
}
+ /**
+ * 使用自定义参数的路由参数
+ * @RequestMapping("/whoami/{name}")
+ * @param $param
+ * @return string
+ */
+ public function paramGet($param) {
+ return "Your name: {$param["name"]}";
+ }
+
+ /**
+ * 在机器人连接后向终端输出信息
+ * @OnSwooleEvent("open",rule="connectIsQQ()")
+ * @param $conn
+ */
+ public function onConnect(ConnectionObject $conn) {
+ Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
+ }
+
+ /**
+ * 在机器人断开连接后向终端输出信息
+ * @OnSwooleEvent("close",rule="connectIsQQ()")
+ * @param ConnectionObject $conn
+ */
+ public function onDisconnect(ConnectionObject $conn) {
+ Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!");
+ }
+
+ /**
+ * 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
+ * @OnSwooleEvent("request",rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
+ */
+ public function onRequest() {
+ EventDispatcher::interrupt();
+ }
/**
* 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改
- * @SwooleEventAt(type="open",rule="connectType:unknown")
+ * @OnSwooleEvent(type="open",rule="connectIsDefault()")
*/
public function closeUnknownConn() {
Console::info("Unknown connection , I will close it.");
- context()->getConnection()->close();
+ server()->close(ctx()->getConnection()->getFd());
}
}
diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php
index e78e8b67..d90d2570 100644
--- a/src/Module/Middleware/TimerMiddleware.php
+++ b/src/Module/Middleware/TimerMiddleware.php
@@ -2,24 +2,25 @@
namespace Module\Middleware;
-use Framework\Console;
-use ZM\Annotation\Http\After;
-use ZM\Annotation\Http\Before;
+use ZM\Annotation\Http\HandleAfter;
+use ZM\Annotation\Http\HandleBefore;
+use ZM\Annotation\Http\HandleException;
use ZM\Annotation\Http\MiddlewareClass;
+use ZM\Console\Console;
use ZM\Http\MiddlewareInterface;
/**
- * Class AuthMiddleware
+ * Class TimerMiddleware
* 示例中间件:用于统计路由函数运行时间用的
* @package Module\Middleware
- * @MiddlewareClass()
+ * @MiddlewareClass("timer")
*/
class TimerMiddleware implements MiddlewareInterface
{
private $starttime;
/**
- * @Before()
+ * @HandleBefore()
* @return bool
*/
public function onBefore() {
@@ -28,11 +29,16 @@ class TimerMiddleware implements MiddlewareInterface
}
/**
- * @After()
+ * @HandleAfter()
*/
public function onAfter() {
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
}
- public function getName() { return "timer"; }
+ /**
+ * @HandleException(\Exception::class)
+ */
+ public function onException() {
+ Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
+ }
}
diff --git a/src/Scheduler/MessageEvent.php b/src/Scheduler/MessageEvent.php
deleted file mode 100644
index b3e67f6e..00000000
--- a/src/Scheduler/MessageEvent.php
+++ /dev/null
@@ -1,29 +0,0 @@
-client = $client;
- $this->frame = $frame;
- }
-
- public function onActivate() {
- //TODO: 写Scheduler计时器内的处理逻辑
- }
-}
\ No newline at end of file
diff --git a/src/Scheduler/Scheduler.php b/src/Scheduler/Scheduler.php
deleted file mode 100644
index 3d8a1deb..00000000
--- a/src/Scheduler/Scheduler.php
+++ /dev/null
@@ -1,140 +0,0 @@
-initProcess();
- elseif ($method == self::REMOTE) $this->initRemote();
- }
-
- private function initProcess() { //TODO: 完成Process模式的代码
- $m_pid = posix_getpid();
- $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
- $this->pid = $this->process->start();
- while (1) {
- $ret = Process::wait();
- if ($ret) {
- $this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
- $this->pid = $this->process->start();
- echo "Reboot done.\n";
- }
- }
- }
-
- private function initRemote() {
- define('WORKING_DIR', __DIR__ . '../..');
- $this->requireGlobalFunctions();
- $this->registerAutoloader('classLoader');
- $this->settings = new GlobalConfig();
- if (!$this->settings->success) die("Failed to load global config. Please check config/global.php file");
- $this->defineProperties();
-
- //start swoole Framework
- $this->selfCheck();
- try {
- $host = $this->settings->get("scheduler")["host"];
- $port = $this->settings->get("scheduler")["port"];
- $token = $this->settings->get("scheduler")["token"];
- $this->client = new Client($host, $port);
- $path = "/" . ($token != "" ? ("?token=" . urlencode($token)) : "");
- while (true) {
- if ($this->client->upgrade($path)) {
- while (true) {
- $recv = $this->client->recv();
- if ($recv instanceof Frame) {
- (new MessageEvent($this->client, $recv))->onActivate();
- } else {
- break;
- }
- }
- } else {
- Console::warning("无法连接Framework,将在5秒后重连...");
- Coroutine::sleep(5);
- }
- }
- } catch (Exception $e) {
- Console::error($e);
- }
- }
-
- private function requireGlobalFunctions() {
- /** @noinspection PhpIncludeInspection */
- require WORKING_DIR . '/src/Framework/global_functions.php';
- }
-
- private function registerAutoloader(string $string) {
- if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !");
- }
-
- private function defineProperties() {
- define("ZM_START_TIME", microtime(true));
- define("ZM_DATA", $this->settings->get("zm_data"));
- //define("CONFIG_DIR", $this->settings->get("config_dir"));
- define("CRASH_DIR", $this->settings->get("crash_dir"));
- }
-
- private function selfCheck() {
- if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
- if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
- if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n");
- if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n");
- //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
- if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
- return true;
- }
-
- private static function onWork(Process $worker, $m_pid) {
- swoole_set_process_name('php-scheduler');
- for ($j = 0; $j < 16000; $j++) {
- self::checkMpid($worker, $m_pid);
- echo "msg: {$j}\n";
- sleep(1);
- }
- }
-
- private static function checkMpid(Process $worker, $m_pid) {
- if (!Process::kill($m_pid, 0)) {
- $worker->exit(); //主进程死了我也死
- // 这句提示,实际是看不到的.需要写到日志中
- echo "Master process exited, I [{$worker['pid']}] also quit\n";
- }
- }
-}
\ No newline at end of file
diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php
index d8e172d4..61191446 100644
--- a/src/ZM/API/CQ.php
+++ b/src/ZM/API/CQ.php
@@ -4,8 +4,7 @@
namespace ZM\API;
-use Framework\Console;
-use ZM\Utils\ZMUtil;
+use ZM\Console\Console;
class CQ
{
diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php
index b715a595..b4b35baa 100644
--- a/src/ZM/API/CQAPI.php
+++ b/src/ZM/API/CQAPI.php
@@ -3,239 +3,52 @@
namespace ZM\API;
-
use Co;
-use Framework\Console;
-use Framework\ZMBuf;
-use ZM\Connection\ConnectionManager;
-use ZM\Connection\CQConnection;
-use ZM\Event\EventHandler;
-use ZM\Utils\ZMRobot;
+use ZM\ConnectionManager\ConnectionObject;
+use ZM\Console\Console;
+use ZM\Store\LightCacheInside;
+use ZM\Store\Lock\SpinLock;
+use ZM\Store\ZMAtomic;
-/**
- * @method static send_private_msg($self_id, $params, $function = null)
- * @method static send_group_msg($self_id, $params, $function = null)
- * @method static send_discuss_msg($self_id, $params, $function = null)
- * @method static send_msg($self_id, $params, $function = null)
- * @method static delete_msg($self_id, $params, $function = null)
- * @method static send_like($self_id, $params, $function = null)
- * @method static set_group_kick($self_id, $params, $function = null)
- * @method static set_group_ban($self_id, $params, $function = null)
- * @method static set_group_anonymous_ban($self_id, $params, $function = null)
- * @method static set_group_whole_ban($self_id, $params, $function = null)
- * @method static set_group_admin($self_id, $params, $function = null)
- * @method static set_group_anonymous($self_id, $params, $function = null)
- * @method static set_group_card($self_id, $params, $function = null)
- * @method static set_group_leave($self_id, $params, $function = null)
- * @method static set_group_special_title($self_id, $params, $function = null)
- * @method static set_discuss_leave($self_id, $params, $function = null)
- * @method static set_friend_add_request($self_id, $params, $function = null)
- * @method static set_group_add_request($self_id, $params, $function = null)
- * @method static get_login_info($self_id, $params, $function = null)
- * @method static get_stranger_info($self_id, $params, $function = null)
- * @method static get_group_list($self_id, $params, $function = null)
- * @method static get_group_member_info($self_id, $params, $function = null)
- * @method static get_group_member_list($self_id, $params, $function = null)
- * @method static get_cookies($self_id, $params, $function = null)
- * @method static get_csrf_token($self_id, $params, $function = null)
- * @method static get_credentials($self_id, $params, $function = null)
- * @method static get_record($self_id, $params, $function = null)
- * @method static get_status($self_id, $params, $function = null)
- * @method static get_version_info($self_id, $params, $function = null)
- * @method static set_restart($self_id, $params, $function = null)
- * @method static set_restart_plugin($self_id, $params, $function = null)
- * @method static clean_data_dir($self_id, $params, $function = null)
- * @method static clean_plugin_log($self_id, $params, $function = null)
- * @method static _get_friend_list($self_id, $params, $function = null)
- * @method static _get_group_info($self_id, $params, $function = null)
- * @method static _get_vip_info($self_id, $params, $function = null)
- * @method static send_private_msg_async($self_id, $params, $function = null)
- * @method static send_group_msg_async($self_id, $params, $function = null)
- * @method static send_discuss_msg_async($self_id, $params, $function = null)
- * @method static send_msg_async($self_id, $params, $function = null)
- * @method static delete_msg_async($self_id, $params, $function = null)
- * @method static set_group_kick_async($self_id, $params, $function = null)
- * @method static set_group_ban_async($self_id, $params, $function = null)
- * @method static set_group_anonymous_ban_async($self_id, $params, $function = null)
- * @method static set_group_whole_ban_async($self_id, $params, $function = null)
- * @method static set_group_admin_async($self_id, $params, $function = null)
- * @method static set_group_anonymous_async($self_id, $params, $function = null)
- * @method static set_group_card_async($self_id, $params, $function = null)
- * @method static set_group_leave_async($self_id, $params, $function = null)
- * @method static set_group_special_title_async($self_id, $params, $function = null)
- * @method static set_discuss_leave_async($self_id, $params, $function = null)
- * @method static set_friend_add_request_async($self_id, $params, $function = null)
- * @method static set_group_add_request_async($self_id, $params, $function = null)
- */
-class CQAPI
+trait CQAPI
{
- public static function quick_reply(CQConnection $conn, $data, $msg, $yield = null) {
- switch ($data["message_type"]) {
- case "group":
- return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg);
- case "private":
- return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg);
- case "discuss":
- return (new ZMRobot($conn))->setCallback($yield)->sendDiscussMsg($data["discuss_id"], $msg);
- }
- return null;
- }
-
/**
- * @param $name
- * @param $arg
- * @return bool
- * @deprecated
- */
- public static function __callStatic($name, $arg) {
- trigger_error("This dynamic CQAPI calling method will be removed after 2.0 version.", E_USER_DEPRECATED);
- $all = self::getSupportedAPIs();
- $find = null;
- if (in_array($name, $all)) $find = $name;
- else {
- foreach ($all as $v) {
- if (strtolower($name) == strtolower(str_replace("_", "", $v))) {
- $find = $v;
- break;
- }
- }
- }
- if ($find === null) {
- Console::warning("Unknown API " . $name);
- return false;
- }
- $reply = ["action" => $find];
- if (!is_array($arg[1])) {
- Console::warning("Error when parsing params. Please make sure your params is an array.");
- return false;
- }
- if ($arg[1] != []) {
- $reply["params"] = $arg[1];
- }
- if (!($arg[0] instanceof CQConnection)) {
- $robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]);
- if ($robot == []) {
- Console::warning("发送错误,机器人连接不存在!");
- return false;
- }
- $arg[0] = $robot[0];
- }
- return self::processAPI($arg[0], $reply, $arg[2] ?? null);
- }
-
- /********************** non-API Part **********************/
-
- private static function getSupportedAPIs() {
- return [
- "send_private_msg",
- "send_group_msg",
- "send_discuss_msg",
- "send_msg",
- "delete_msg",
- "send_like",
- "set_group_kick",
- "set_group_ban",
- "set_group_anonymous_ban",
- "set_group_whole_ban",
- "set_group_admin",
- "set_group_anonymous",
- "set_group_card",
- "set_group_leave",
- "set_group_special_title",
- "set_discuss_leave",
- "set_friend_add_request",
- "set_group_add_request",
- "get_login_info",
- "get_stranger_info",
- "get_group_list",
- "get_group_member_info",
- "get_group_member_list",
- "get_cookies",
- "get_csrf_token",
- "get_credentials",
- "get_record",
- "get_status",
- "get_version_info",
- "set_restart",
- "set_restart_plugin",
- "clean_data_dir",
- "clean_plugin_log",
- "_get_friend_list",
- "_get_group_info",
- "_get_vip_info",
- //异步API
- "send_private_msg_async",
- "send_group_msg_async",
- "send_discuss_msg_async",
- "send_msg_async",
- "delete_msg_async",
- "set_group_kick_async",
- "set_group_ban_async",
- "set_group_anonymous_ban_async",
- "set_group_whole_ban_async",
- "set_group_admin_async",
- "set_group_anonymous_async",
- "set_group_card_async",
- "set_group_leave_async",
- "set_group_special_title_async",
- "set_discuss_leave_async",
- "set_friend_add_request_async",
- "set_group_add_request_async"
- ];
- }
-
- public static function getLoggedAPIs() {
- return [
- "send_private_msg",
- "send_group_msg",
- "send_discuss_msg",
- "send_msg",
- "send_private_msg_async",
- "send_group_msg_async",
- "send_discuss_msg_async",
- "send_msg_async"
- ];
- }
-
- /**
- * @param CQConnection $connection
+ * @param ConnectionObject $connection
* @param $reply
* @param |null $function
* @return bool|array
*/
- public static function processAPI($connection, $reply, $function = null) {
- $api_id = ZMBuf::$atomics["wait_msg_id"]->get();
+ private function processAPI($connection, $reply, $function = null) {
+ if ($connection->getOption("type") === CONN_WEBSOCKET)
+ return $this->processWebsocketAPI($connection, $reply, $function);
+ else
+ return $this->processHttpAPI($connection, $reply, $function);
+
+
+ }
+
+ public function processWebsocketAPI($connection, $reply, $function = false) {
+ $api_id = ZMAtomic::get("wait_msg_id")->add(1);
$reply["echo"] = $api_id;
- ZMBuf::$atomics["wait_msg_id"]->add(1);
- EventHandler::callCQAPISend($reply, $connection);
- if (is_callable($function)) {
- ZMBuf::appendKey("sent_api", $api_id, [
- "data" => $reply,
- "time" => microtime(true),
- "func" => $function,
- "self_id" => $connection->getQQ()
- ]);
- } elseif ($function === true) {
- ZMBuf::appendKey("sent_api", $api_id, [
- "data" => $reply,
- "time" => microtime(true),
- "coroutine" => Co::getuid(),
- "self_id" => $connection->getQQ()
- ]);
- } else {
- ZMBuf::appendKey("sent_api", $api_id, [
- "data" => $reply,
- "time" => microtime(true),
- "self_id" => $connection->getQQ()
- ]);
- }
- if ($connection->push(json_encode($reply))) {
- //Console::msg($reply, $connection->getQQ());
- ZMBuf::$atomics["out_count"]->add(1);
+ SpinLock::lock("wait_api");
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ $r[$api_id] = [
+ "data" => $reply,
+ "time" => microtime(true),
+ "self_id" => $connection->getOption("connect_id")
+ ];
+ if ($function === true) $r[$api_id]["coroutine"] = Co::getuid();
+ LightCacheInside::set("wait_api", "wait_api", $r);
+ SpinLock::unlock("wait_api");
+ if (server()->push($connection->getFd(), json_encode($reply))) {
if ($function === true) {
Co::suspend();
- $data = ZMBuf::get("sent_api")[$api_id];
- ZMBuf::unsetByValue("sent_api", $reply["echo"]);
+ SpinLock::lock("wait_api");
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ $data = $r[$api_id];
+ unset($r[$api_id]);
+ LightCacheInside::set("wait_api", "wait_api", $r);
+ SpinLock::unlock("wait_api");
return isset($data['result']) ? $data['result'] : null;
}
return true;
@@ -245,14 +58,30 @@ class CQAPI
"status" => "failed",
"retcode" => -1000,
"data" => null,
- "self_id" => $connection->getQQ()
+ "self_id" => $connection->getOption("connect_id")
];
- $s = ZMBuf::get("sent_api")[$reply["echo"]];
- if (($s["func"] ?? null) !== null)
- call_user_func($s["func"], $response, $reply);
- ZMBuf::unsetByValue("sent_api", $reply["echo"]);
+ SpinLock::lock("wait_api");
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ unset($r[$reply["echo"]]);
+ LightCacheInside::set("wait_api", "wait_api", $r);
+ SpinLock::unlock("wait_api");
if ($function === true) return $response;
return false;
}
}
+
+ /**
+ * @param $connection
+ * @param $reply
+ * @param null $function
+ * @return bool
+ * @noinspection PhpUnusedParameterInspection
+ */
+ public function processHttpAPI($connection, $reply, $function = null) {
+ return false;
+ }
+
+ public function __call($name, $arguments) {
+ return false;
+ }
}
diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php
new file mode 100644
index 00000000..45230948
--- /dev/null
+++ b/src/ZM/API/ZMRobot.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 int $group_id
+ * @param int $user_id
+ * @param int $duration
+ * @return array|bool|null
+ */
+ public function setGroupBan($group_id, $user_id, $duration = 1800) {
+ return $this->processAPI($this->connection, [
+ 'action' => $this->getActionName(__FUNCTION__),
+ '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 e4b6dbce..0024768a 100644
--- a/src/ZM/Annotation/AnnotationParser.php
+++ b/src/ZM/Annotation/AnnotationParser.php
@@ -3,260 +3,187 @@
namespace ZM\Annotation;
-use Doctrine\Common\Annotations\{AnnotationException, AnnotationReader};
-use Co;
-use Framework\{Console, ZMBuf};
-use Error;
-use Exception;
+use Doctrine\Common\Annotations\AnnotationReader;
+use ZM\Annotation\Interfaces\ErgodicAnnotation;
+use ZM\Console\Console;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
-use ZM\Annotation\CQ\{CQAfter,
- CQAPIResponse,
- CQAPISend,
- CQBefore,
- CQCommand,
- CQMessage,
- CQMetaEvent,
- CQNotice,
- CQRequest
-};
-use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping};
-use Swoole\Timer;
-use ZM\Annotation\Interfaces\CustomAnnotation;
+use ZM\Annotation\Http\{HandleAfter, HandleBefore, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping};
use ZM\Annotation\Interfaces\Level;
-use ZM\Annotation\Module\{Closed, InitBuffer, LoadBuffer, SaveBuffer};
-use ZM\Annotation\Swoole\{OnSave, OnStart, OnTick, SwooleEventAfter, SwooleEventAt};
-use ZM\Annotation\Interfaces\Rule;
-use ZM\Connection\WSConnection;
-use ZM\Event\EventHandler;
-use ZM\Http\MiddlewareInterface;
-use Framework\DataProvider;
-use ZM\Utils\ZMUtil;
+use ZM\Annotation\Module\Closed;
+use ZM\Utils\DataProvider;
class AnnotationParser
{
+ private $path_list = [];
+
+ private $start_time;
+
+ private $annotation_map = [];
+ private $middleware_map = [];
+ private $middlewares = [];
+
+ /** @var null|AnnotationReader */
+ private $reader = null;
+ private $req_mapping = [];
+
/**
- * 注册各个模块类的注解和模块level的排序
- * @throws ReflectionException
- * @throws AnnotationException
+ * AnnotationParser constructor.
*/
- public static function registerMods() {
- self::loadAnnotationClasses();
- $all_class = getAllClasses(DataProvider::getWorkingDir() . "/src/Module/", "Module");
- ZMBuf::$req_mapping[0] = [
+ public function __construct() {
+ $this->start_time = microtime(true);
+ $this->loadAnnotationClasses();
+ $this->req_mapping[0] = [
'id' => 0,
'pid' => -1,
'name' => '/'
];
- $reader = new AnnotationReader();
- foreach ($all_class as $v) {
- Console::debug("正在检索 " . $v);
- $reflection_class = new ReflectionClass($v);
- $class_prefix = '';
- $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
- $class_annotations = $reader->getClassAnnotations($reflection_class);
- $middleware_addon = [];
- foreach ($class_annotations as $vs) {
- if ($vs instanceof Closed) {
- continue 2;
- } elseif ($vs instanceof Controller) {
- Console::debug("找到 Controller 中间件: " . $vs->class);
- $class_prefix = $vs->prefix;
- } elseif ($vs instanceof SaveBuffer) {
- Console::debug("注册自动保存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")");
- DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder);
- } elseif ($vs instanceof LoadBuffer) {
- Console::debug("注册到内存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")");
- ZMBuf::set($vs->buf_name, DataProvider::getJsonData(($vs->sub_folder ?? "") . "/" . $vs->buf_name . ".json"));
- } elseif ($vs instanceof InitBuffer) {
- ZMBuf::set($vs->buf_name, []);
- } elseif ($vs instanceof MiddlewareClass) {
- Console::verbose("正在注册中间件 " . $reflection_class->getName());
- $result = [
- "class" => "\\" . $reflection_class->getName()
- ];
- foreach ($methods as $vss) {
- if ($vss->getName() == "getName") {
- /** @var MiddlewareInterface $tmp */
- $tmp = new $v();
- $result["name"] = $tmp->getName();
- continue;
- }
- $method_annotations = $reader->getMethodAnnotations($vss);
- foreach ($method_annotations as $vsss) {
- if ($vss instanceof Rule) $vss = self::registerRuleEvent($vsss, $vss, $reflection_class);
- else $vss = self::registerMethod($vsss, $vss, $reflection_class);
- //echo get_class($vsss) . PHP_EOL;
- if ($vsss instanceof Before) $result["before"] = $vsss->method;
- if ($vsss instanceof After) $result["after"] = $vsss->method;
- if ($vsss instanceof HandleException) {
- $result["exceptions"][$vsss->class_name] = $vsss->method;
- }
- }
- }
- ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result;
- continue 2;
- } elseif ($vs instanceof Middleware) {
- $middleware_addon[] = $vs;
- } elseif ($vs instanceof CustomAnnotation) {
- $vs->class = $reflection_class->getName();
- ZMBuf::$events[get_class($vs)][] = $vs;
- }
- }
- foreach ($methods as $vs) {
- if ($middleware_addon !== []) {
- foreach($middleware_addon as $value){
- Console::debug("Added middleware " . $value->middleware . " to $v -> " . $vs->getName());
- ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $value->middleware;
- }
- }
- $method_annotations = $reader->getMethodAnnotations($vs);
- foreach ($method_annotations as $vss) {
- if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class);
- else $vss = self::registerMethod($vss, $vs, $reflection_class);
- Console::debug("寻找 " . $vs->getName() . " -> " . get_class($vss));
+ }
- if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss;
- elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss;
- elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss;
- elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss;
- elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss;
- elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss;
- elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss;
- elseif ($vss instanceof RequestMapping) {
- self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix);
- } elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss;
- elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss;
- elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss;
- elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss;
- elseif ($vss instanceof OnSave) ZMBuf::$events[OnSave::class][] = $vss;
- elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method][] = $vss->middleware;
- elseif ($vss instanceof OnTick) self::addTimerTick($vss);
- elseif ($vss instanceof CQAPISend) ZMBuf::$events[CQAPISend::class][] = $vss;
- elseif ($vss instanceof CQAPIResponse) ZMBuf::$events[CQAPIResponse::class][$vss->retcode] = [$vss->class, $vss->method];
+ /**
+ * 注册各个模块类的注解和模块level的排序
+ * @throws ReflectionException
+ */
+ public function registerMods() {
+ foreach ($this->path_list as $path) {
+ Console::debug("parsing annotation in ".$path[0]);
+ $all_class = getAllClasses($path[0], $path[1]);
+ $this->reader = new AnnotationReader();
+ foreach ($all_class as $v) {
+ Console::debug("正在检索 " . $v);
+ $reflection_class = new ReflectionClass($v);
+ $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
+ $class_annotations = $this->reader->getClassAnnotations($reflection_class);
+
+ // 这段为新加的:start
+ //这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析
+ /*
+ $annotation_map: {
+ Module\Example\Hello: {
+ class_annotations: [
+ 注解对象1, 注解对象2, ...
+ ],
+ methods: [
+ ReflectionMethod, ReflectionMethod, ...
+ ],
+ methods_annotations: {
+ foo: [ 注解对象1, 注解对象2, ... ],
+ bar: [ 注解对象1, 注解对象2, ... ],
+ }
+ }
}
- }
- }
- $tree = self::genTree(ZMBuf::$req_mapping);
- ZMBuf::$req_mapping = $tree[0];
- //给支持level的排个序
- foreach (ZMBuf::$events as $class_name => $v) {
- if (is_a($class_name, Level::class, true)) {
- for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) {
- for ($j = 0; $j < count(ZMBuf::$events[$class_name]) - $i - 1; ++$j) {
- $l1 = ZMBuf::$events[$class_name][$j]->level;
- $l2 = ZMBuf::$events[$class_name][$j + 1]->level;
- if ($l1 < $l2) {
- $t = ZMBuf::$events[$class_name][$j + 1];
- ZMBuf::$events[$class_name][$j + 1] = ZMBuf::$events[$class_name][$j];
- ZMBuf::$events[$class_name][$j] = $t;
+ */
+
+ // 生成主树
+ $this->annotation_map[$v]["class_annotations"] = $class_annotations;
+ $this->annotation_map[$v]["methods"] = $methods;
+ foreach ($methods as $method) {
+ $this->annotation_map[$v]["methods_annotations"][$method->getName()] = $this->reader->getMethodAnnotations($method);
+ }
+
+
+ foreach ($this->annotation_map[$v]["class_annotations"] as $ks => $vs) {
+ $vs->class = $v;
+
+ //预处理1:将适用于每一个函数的注解到类注解重新注解到每个函数下面
+ if ($vs instanceof ErgodicAnnotation) {
+ foreach ($this->annotation_map[$v]["methods"] as $method) {
+ $copy = clone $vs;
+ $copy->method = $method->getName();
+ $this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
+ }
+ }
+
+ //预处理2:处理 class 下面的注解
+ if ($vs instanceof Closed) {
+ unset($this->annotation_map[$v]);
+ continue 2;
+ } elseif ($vs instanceof MiddlewareClass) {
+ Console::verbose("正在注册中间件 " . $reflection_class->getName());
+ $rs = $this->registerMiddleware($vs, $reflection_class);
+ $this->middlewares[$rs["name"]] = $rs;
+ }
+ }
+
+ //预处理3:处理每个函数上面的特殊注解,就是需要操作一些东西的
+ foreach ($this->annotation_map[$v]["methods_annotations"] as $method_name => $methods_annotations) {
+ foreach ($methods_annotations as $method_anno) {
+ /** @var AnnotationBase $method_anno */
+ $method_anno->class = $v;
+ $method_anno->method = $method_name;
+ if ($method_anno instanceof RequestMapping) {
+ $this->registerRequestMapping($method_anno, $method_name, $v, $methods_annotations); //TODO: 用symfony的routing重写
+ } elseif ($method_anno instanceof Middleware) {
+ $this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno->middleware;
}
}
}
}
}
+
+ //预处理4:生成路由树(换成symfony后就不需要了)
+ $tree = $this->genTree($this->req_mapping);
+ $this->req_mapping = $tree[0];
+
Console::debug("解析注解完毕!");
- if (ZMBuf::isset("timer_count")) {
- Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!");
- ZMBuf::unsetCache("timer_count");
- }
}
- public static function getRuleCallback($rule_str) {
- $func = null;
- $rule = $rule_str;
- if ($rule != "") {
- $asp = explode(":", $rule);
- $asp_name = array_shift($asp);
- $rest = implode(":", $asp);
- //Swoole 事件时走此switch
- switch ($asp_name) {
- case "connectType": //websocket连接类型
- $func = function (?WSConnection $connection) use ($rest) {
- if ($connection === null) return false;
- return $connection->getType() == $rest ? true : false;
- };
- break;
- case "containsGet": //handle http request事件时才能用
- case "containsPost":
- $get_list = explode(",", $rest);
- if ($asp_name == "containsGet")
- $func = function ($request) use ($get_list) {
- foreach ($get_list as $v) if (!isset($request->get[$v])) return false;
- return true;
- };
- else
- $func = function ($request) use ($get_list) {
- foreach ($get_list as $v) if (!isset($request->post[$v])) return false;
- return true;
- };
- /*
- if ($controller_prefix != '') {
- $p = ZMBuf::$req_mapping_node;
- $prefix_exp = explode("/", $controller_prefix);
- foreach ($prefix_exp as $k => $v) {
- if ($v == "" || $v == ".." || $v == ".") {
- unset($prefix_exp[$k]);
- }
- }
- while (($shift = array_shift($prefix_exp)) !== null) {
- $p->addRoute($shift, new MappingNode($shift));
- $p = $p->getRoute($shift);
- }
- if ($p->getNodeName() != "/") {
- $p->setMethod($method->getName());
- $p->setClass($class->getName());
- $p->setRule($func);
- return "mapped";
- }
- }*/
- break;
- case "containsJson": //handle http request事件时才能用
- $json_list = explode(",", $rest);
- $func = function ($json) use ($json_list) {
- foreach ($json_list as $v) if (!isset($json[$v])) return false;
- return true;
- };
- break;
- case "dataEqual": //handle websocket message事件时才能用
- $func = function ($data) use ($rest) {
- return $data == $rest;
- };
- break;
+ /**
+ * @return array
+ */
+ public function generateAnnotationEvents() {
+ $o = [];
+ foreach ($this->annotation_map as $module => $obj) {
+ foreach ($obj["class_annotations"] as $class_annotation) {
+ if ($class_annotation instanceof ErgodicAnnotation) continue;
+ else $o[get_class($class_annotation)][] = $class_annotation;
+ }
+ foreach ($obj["methods_annotations"] as $method_name => $methods_annotations) {
+ foreach ($methods_annotations as $annotation) {
+ $o[get_class($annotation)][] = $annotation;
+ }
}
- switch ($asp_name) {
- case "msgMatch": //handle cq message事件时才能用
- $func = function ($msg) use ($rest) {
- return matchPattern($rest, $msg);
- };
- break;
- case "msgEqual": //handle cq message事件时才能用
- $func = function ($msg) use ($rest) {
- return trim($msg) == $rest;
- };
- break;
+ }
+ foreach ($o as $k => $v) {
+ $this->sortByLevel($o, $k);
+ }
+ return $o;
+ }
+
+ /**
+ * @return array
+ */
+ public function getMiddlewares() { return $this->middlewares; }
+
+ /**
+ * @return array
+ */
+ public function getMiddlewareMap() { return $this->middleware_map; }
+
+ /**
+ * @return array
+ */
+ public function getReqMapping() { return $this->req_mapping; }
+
+ /**
+ * @param $path
+ * @param $indoor_name
+ */
+ public function addRegisterPath($path, $indoor_name) { $this->path_list[] = [$path, $indoor_name]; }
+
+ //private function below
+
+ private function registerRequestMapping(RequestMapping $vss, $method, $class, $methods_annotations) {
+ $prefix = '';
+ foreach ($methods_annotations as $annotation) {
+ if ($annotation instanceof Controller) {
+ $prefix = $annotation->prefix;
+ break;
}
}
- return $func;
- }
-
- public static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
- $vss->callback = self::getRuleCallback($vss->getRule());
- $vss->method = $method->getName();
- $vss->class = $class->getName();
- return $vss;
- }
-
- public static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
- $vss->method = $method->getName();
- $vss->class = $class->getName();
- return $vss;
- }
-
- private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) {
- $array = ZMBuf::$req_mapping;
+ $array = $this->req_mapping;
$uid = count($array);
$prefix_exp = explode("/", $prefix);
$route_exp = explode("/", $vss->route);
@@ -271,10 +198,11 @@ class AnnotationParser
}
}
if ($prefix_exp == [] && $route_exp == []) {
- $array[0]['method'] = $method->getName();
- $array[0]['class'] = $class->getName();
+ $array[0]['method'] = $method;
+ $array[0]['class'] = $class;
$array[0]['request_method'] = $vss->request_method;
- ZMBuf::$req_mapping = $array;
+ $array[0]['route'] = $vss->route;
+ $this->req_mapping = $array;
return;
}
$pid = 0;
@@ -317,16 +245,19 @@ class AnnotationParser
];
$pid = $uid - 1;
}
- $array[$uid - 1]['method'] = $method->getName();
- $array[$uid - 1]['class'] = $class->getName();
+ $array[$uid - 1]['method'] = $method;
+ $array[$uid - 1]['class'] = $class;
$array[$uid - 1]['request_method'] = $vss->request_method;
- ZMBuf::$req_mapping = $array;
+ $array[$uid - 1]['route'] = $vss->route;
+ $this->req_mapping = $array;
}
- private static function loadAnnotationClasses() {
+ /** @noinspection PhpIncludeInspection */
+ private function loadAnnotationClasses() {
$class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
+ //Console::debug("Requiring annotation " . $s);
require_once $s;
}
$class = getAllClasses(DataProvider::getWorkingDir() . "/src/Custom/Annotation/", "Custom\\Annotation");
@@ -337,7 +268,7 @@ class AnnotationParser
}
}
- public static function genTree($items) {
+ private function genTree($items) {
$tree = array();
foreach ($items as $item)
if (isset($items[$item['pid']]))
@@ -347,44 +278,33 @@ class AnnotationParser
return $tree;
}
- private static function addTimerTick(?OnTick $vss) {
- ZMBuf::set("timer_count", ZMBuf::get("timer_count", 0) + 1);
- $class = ZMUtil::getModInstance($vss->class);
- $method = $vss->method;
- $ms = $vss->tick_ms;
- $cid = go(function () use ($class, $method, $ms) {
- Co::suspend();
- $plain_class = get_class($class);
- if (!isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) {
- Console::debug("Added timer: " . $plain_class . " -> " . $method);
- Timer::tick($ms, function () use ($class, $method) {
- set_coroutine_params([]);
- try {
- $class->$method();
- } catch (Exception $e) {
- Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
- } catch (Error $e) {
- Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage());
- echo Console::setColor($e->getTraceAsString(), "gray");
- Console::error("Please check your code!");
- }
- });
- } else {
- Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $method);
- Timer::tick($ms, function () use ($class, $method) {
- set_coroutine_params([]);
- try {
- EventHandler::callWithMiddleware($class, $method, [], []);
- } catch (Exception $e) {
- Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
- } catch (Error $e) {
- Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage());
- echo Console::setColor($e->getTraceAsString(), "gray");
- Console::error("Please check your code!");
- }
- });
+ private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class) {
+ $result = [
+ "class" => "\\" . $reflection_class->getName(),
+ "name" => $vs->name
+ ];
+
+ foreach ($reflection_class->getMethods() as $vss) {
+ $method_annotations = $this->reader->getMethodAnnotations($vss);
+ foreach ($method_annotations as $vsss) {
+ if ($vsss instanceof HandleBefore) $result["before"] = $vss->getName();
+ if ($vsss instanceof HandleAfter) $result["after"] = $vss->getName();
+ if ($vsss instanceof HandleException) {
+ $result["exceptions"][$vsss->class_name] = $vss->getName();
+ }
}
- });
- ZMBuf::append("paused_tick", $cid);
+ }
+ return $result;
+ }
+
+ private function sortByLevel(&$events, string $class_name, $prefix = "") {
+ if (is_a($class_name, Level::class, true)) {
+ $class_name .= $prefix;
+ usort($events[$class_name], function ($a, $b) {
+ $left = $a->level;
+ $right = $b->level;
+ return $left > $right ? -1 : ($left == $right ? 0 : 1);
+ });
+ }
}
}
diff --git a/src/ZM/Annotation/CQ/CQAPISend.php b/src/ZM/Annotation/CQ/CQAPISend.php
deleted file mode 100644
index bf4429c0..00000000
--- a/src/ZM/Annotation/CQ/CQAPISend.php
+++ /dev/null
@@ -1,43 +0,0 @@
-level;
- }
-
- /**
- * @param mixed $level
- */
- public function setLevel($level) {
- $this->level = $level;
- }
-}
diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php
index 6d468b6b..a7ec162b 100644
--- a/src/ZM/Annotation/CQ/CQCommand.php
+++ b/src/ZM/Annotation/CQ/CQCommand.php
@@ -18,9 +18,15 @@ class CQCommand extends AnnotationBase implements Level
/** @var string */
public $match = "";
/** @var string */
- public $regexMatch = "";
+ public $pattern = "";
/** @var string */
- public $fullMatch = "";
+ public $regex = "";
+ /** @var string */
+ public $start_with = "";
+ /** @var string */
+ public $end_with = "";
+ /** @var string */
+ public $keyword = "";
/** @var string[] */
public $alias = [];
/** @var string */
diff --git a/src/ZM/Annotation/Http/Controller.php b/src/ZM/Annotation/Http/Controller.php
index 9556a6bd..347c1caf 100644
--- a/src/ZM/Annotation/Http/Controller.php
+++ b/src/ZM/Annotation/Http/Controller.php
@@ -6,6 +6,7 @@ namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
+use ZM\Annotation\Interfaces\ErgodicAnnotation;
/**
* Class Controller
@@ -13,11 +14,11 @@ use ZM\Annotation\AnnotationBase;
* @Target("CLASS")
* @package ZM\Annotation\Http
*/
-class Controller extends AnnotationBase
+class Controller extends AnnotationBase implements ErgodicAnnotation
{
/**
* @var string
* @Required()
*/
public $prefix = '';
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Http/After.php b/src/ZM/Annotation/Http/HandleAfter.php
similarity index 80%
rename from src/ZM/Annotation/Http/After.php
rename to src/ZM/Annotation/Http/HandleAfter.php
index ef24db54..9d2e43a4 100644
--- a/src/ZM/Annotation/Http/After.php
+++ b/src/ZM/Annotation/Http/HandleAfter.php
@@ -9,11 +9,11 @@ use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
- * Class After
+ * Class HandleAfter
* @package ZM\Annotation\Http
* @Annotation
* @Target("METHOD")
*/
-class After extends AnnotationBase
+class HandleAfter extends AnnotationBase
{
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Http/Before.php b/src/ZM/Annotation/Http/HandleBefore.php
similarity index 65%
rename from src/ZM/Annotation/Http/Before.php
rename to src/ZM/Annotation/Http/HandleBefore.php
index f09cfc40..90a092a5 100644
--- a/src/ZM/Annotation/Http/Before.php
+++ b/src/ZM/Annotation/Http/HandleBefore.php
@@ -4,16 +4,15 @@
namespace ZM\Annotation\Http;
-use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
- * Class Before
+ * Class HandleBefore
* @package ZM\Annotation\Http
* @Annotation
* @Target("METHOD")
*/
-class Before extends AnnotationBase
+class HandleBefore extends AnnotationBase
{
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Http/Middleware.php b/src/ZM/Annotation/Http/Middleware.php
index 60a928c3..cb83ba63 100644
--- a/src/ZM/Annotation/Http/Middleware.php
+++ b/src/ZM/Annotation/Http/Middleware.php
@@ -7,6 +7,7 @@ namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
+use ZM\Annotation\Interfaces\ErgodicAnnotation;
/**
* Class Middleware
@@ -14,7 +15,7 @@ use ZM\Annotation\AnnotationBase;
* @Annotation
* @Target("ALL")
*/
-class Middleware extends AnnotationBase
+class Middleware extends AnnotationBase implements ErgodicAnnotation
{
/**
* @var string
diff --git a/src/ZM/Annotation/Http/MiddlewareClass.php b/src/ZM/Annotation/Http/MiddlewareClass.php
index 4d182aae..58f98d1d 100644
--- a/src/ZM/Annotation/Http/MiddlewareClass.php
+++ b/src/ZM/Annotation/Http/MiddlewareClass.php
@@ -4,6 +4,7 @@
namespace ZM\Annotation\Http;
+use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
@@ -15,5 +16,9 @@ use ZM\Annotation\AnnotationBase;
*/
class MiddlewareClass extends AnnotationBase
{
-
-}
\ No newline at end of file
+ /**
+ * @var string
+ * @Required()
+ */
+ public $name = '';
+}
diff --git a/src/ZM/Annotation/Http/RequestMapping.php b/src/ZM/Annotation/Http/RequestMapping.php
index 966ad84d..7a8538dd 100644
--- a/src/ZM/Annotation/Http/RequestMapping.php
+++ b/src/ZM/Annotation/Http/RequestMapping.php
@@ -10,7 +10,7 @@ use ZM\Annotation\AnnotationBase;
/**
* Class RequestMapping
* @Annotation
- * @Target("ALL")
+ * @Target("METHOD")
* @package ZM\Annotation\Http
*/
class RequestMapping extends AnnotationBase
@@ -36,4 +36,4 @@ class RequestMapping extends AnnotationBase
* @var array
*/
public $params = [];
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Http/RequestMethod.php b/src/ZM/Annotation/Http/RequestMethod.php
index 2c54129e..6332b331 100644
--- a/src/ZM/Annotation/Http/RequestMethod.php
+++ b/src/ZM/Annotation/Http/RequestMethod.php
@@ -9,7 +9,6 @@ use ZM\Annotation\AnnotationBase;
/**
* Class RequestMethod
* @Annotation
- *
* @package ZM\Annotation\Http
*/
class RequestMethod extends AnnotationBase
@@ -27,4 +26,4 @@ class RequestMethod extends AnnotationBase
public const DELETE = 'DELETE';
public const OPTIONS = 'OPTIONS';
public const HEAD = 'HEAD';
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php b/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php
new file mode 100644
index 00000000..b6b3be2f
--- /dev/null
+++ b/src/ZM/Annotation/Interfaces/ErgodicAnnotation.php
@@ -0,0 +1,10 @@
+level = $level;
}
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Annotation/Swoole/OnTaskWorkerStart.php b/src/ZM/Annotation/Swoole/OnTaskWorkerStart.php
deleted file mode 100644
index 462563c6..00000000
--- a/src/ZM/Annotation/Swoole/OnTaskWorkerStart.php
+++ /dev/null
@@ -1,15 +0,0 @@
-type;
- }
-
- /**
- * @param string $type
- */
- public function setType(string $type) {
- $this->type = $type;
- }
-
- /**
- * @return string
- */
- public function getRule(): string {
- return $this->rule;
- }
-
- /**
- * @param string $rule
- */
- public function setRule(string $rule) {
- $this->rule = $rule;
- }
-
- /**
- * @return int
- */
- public function getLevel(): int {
- return $this->level;
- }
-
- /**
- * @param int $level
- */
- public function setLevel(int $level) {
- $this->level = $level;
- }
-
-
-}
\ No newline at end of file
diff --git a/src/ZM/Annotation/Swoole/OnEvent.php b/src/ZM/Annotation/Swoole/SwooleHandler.php
similarity index 83%
rename from src/ZM/Annotation/Swoole/OnEvent.php
rename to src/ZM/Annotation/Swoole/SwooleHandler.php
index 86d26032..d70fb7d0 100644
--- a/src/ZM/Annotation/Swoole/OnEvent.php
+++ b/src/ZM/Annotation/Swoole/SwooleHandler.php
@@ -9,12 +9,12 @@ use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
- * Class OnEvent
+ * Class SwooleHandler
* @package ZM\Annotation\Swoole
* @Annotation
* @Target("METHOD")
*/
-class OnEvent extends AnnotationBase
+class SwooleHandler extends AnnotationBase
{
/**
* @var string
diff --git a/src/ZM/Command/BuildCommand.php b/src/ZM/Command/BuildCommand.php
new file mode 100644
index 00000000..956a3c53
--- /dev/null
+++ b/src/ZM/Command/BuildCommand.php
@@ -0,0 +1,73 @@
+setDescription("Build an \".phar\" file | 将项目构建一个phar包");
+ $this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
+ $this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
+ // ...
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $this->output = $output;
+ $target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/');
+ if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir);
+ if ($target_dir === false) {
+ $output->writeln(TermColor::color8(31) . "Error: No such file or directory (".__DIR__ . '/../../../resources/'.")" . TermColor::RESET);
+ return Command::FAILURE;
+ }
+ $output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"]));
+ if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/";
+ if (ini_get('phar.readonly') == 1) {
+ $output->writeln(TermColor::color8(31) . "You need to set \"phar.readonly\" to \"Off\"!");
+ $output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing");
+ return Command::FAILURE;
+ }
+ if (!is_dir($target_dir)) {
+ $output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET);
+ return Command::FAILURE;
+ }
+ $filename = "server.phar";
+ $this->build($target_dir, $filename);
+
+ return Command::SUCCESS;
+ }
+
+ private function build ($target_dir, $filename) {
+ @unlink($target_dir . $filename);
+ $phar = new Phar($target_dir . $filename);
+ $phar->startBuffering();
+ $src = realpath(__DIR__ . '/../../zhamao-framework/');
+ $hello = file_get_contents($src . '/src/Module/Example/Hello.php');
+ $middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php');
+ unlink($src . '/src/Module/Example/Hello.php');
+ unlink($src . '/src/Module/Middleware/TimerMiddleware.php');
+ $phar->buildFromDirectory($src);
+ $phar->addFromString('tmp/Hello.php.bak', $hello);
+ $phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware);
+ //$phar->compressFiles(Phar::GZ);
+ $phar->setStub($phar->createDefaultStub('phar-starter.php'));
+ $phar->stopBuffering();
+ file_put_contents($src . '/src/Module/Example/Hello.php', $hello);
+ file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware);
+ $this->output->writeln("Successfully built. Location: " . $target_dir . "$filename");
+ }
+}
diff --git a/src/ZM/Command/InitCommand.php b/src/ZM/Command/InitCommand.php
new file mode 100644
index 00000000..19090668
--- /dev/null
+++ b/src/ZM/Command/InitCommand.php
@@ -0,0 +1,101 @@
+setDescription("Initialize framework starter | 初始化框架运行的基础文件");
+ $this->setHelp("此命令将会解压以下文件到项目的根目录:\n" . implode("\n", $this->getExtractFiles()));
+ // ...
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式,最基本的需要初始化的模式
+ $output->writeln("Initializing files");
+ $base_path = LOAD_MODE_COMPOSER_PATH;
+ foreach ($this->extract_files as $file) {
+ if (!file_exists($base_path . $file)) {
+ $info = pathinfo($file);
+ @mkdir($base_path . $info["dirname"], 0777, true);
+ echo "Copying " . $file . PHP_EOL;
+ $package_name = ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["name"]);
+ copy($base_path . "/vendor/" . $package_name . $file, $base_path . $file);
+ } else {
+ echo "Skipping " . $file . " , file exists." . PHP_EOL;
+ }
+ }
+ $autoload = [
+ "psr-4" => [
+ "Module\\" => "src/Module",
+ "Custom\\" => "src/Custom"
+ ],
+ "files" => [
+ "src/Custom/global_function.php"
+ ]
+ ];
+ if (file_exists($base_path . "/composer.json")) {
+ $composer = json_decode(file_get_contents($base_path . "/composer.json"), true);
+ if (!isset($composer["autoload"])) {
+ $composer["autoload"] = $autoload;
+ } else {
+ foreach ($autoload["psr-4"] as $k => $v) {
+ if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v;
+ }
+ foreach ($autoload["files"] as $k => $v) {
+ if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v;
+ }
+ }
+ file_put_contents($base_path . "/composer.json", json_encode($composer, 64 | 128 | 256));
+ $output->writeln("Executing composer update command");
+ exec("composer update");
+ echo PHP_EOL;
+ } else {
+ echo("Error occurred. Please check your updates.\n");
+ return Command::FAILURE;
+ }
+ return Command::SUCCESS;
+ } elseif (LOAD_MODE === 2) { //从phar启动的框架包,初始化的模式
+ $phar_link = new Phar(__DIR__);
+ $current_dir = pathinfo($phar_link->getPath())["dirname"];
+ chdir($current_dir);
+ $phar_link = "phar://" . $phar_link->getPath();
+ foreach ($this->extract_files as $file) {
+ if (!file_exists($current_dir . $file)) {
+ $info = pathinfo($file);
+ @mkdir($current_dir . $info["dirname"], 0777, true);
+ echo "Copying " . $file . PHP_EOL;
+ file_put_contents($current_dir . $file, file_get_contents($phar_link . $file));
+ } else {
+ echo "Skipping " . $file . " , file exists." . PHP_EOL;
+ }
+ }
+ }
+ $output->writeln("initialization must be started with composer-project mode!");
+ return Command::FAILURE;
+ }
+
+ private function getExtractFiles() {
+ return $this->extract_files;
+ }
+}
diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php
new file mode 100644
index 00000000..99c9114c
--- /dev/null
+++ b/src/ZM/Command/PureHttpCommand.php
@@ -0,0 +1,94 @@
+setDescription("Run a simple http server | 启动一个简单的文件 HTTP 服务器");
+ $this->setHelp("直接运行可以启动");
+ $this->addArgument('dir', InputArgument::REQUIRED, 'Your directory');
+ $this->addOption("host", 'H', InputOption::VALUE_REQUIRED, "启动监听地址");
+ $this->addOption("port", 'P', InputOption::VALUE_REQUIRED, "启动监听地址的端口");
+ // ...
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $tty_width = explode(" ", trim(exec("stty size")))[1];
+ if(realpath($input->getArgument('dir') ?? '.') === false) {
+ $output->writeln("Directory error(".($input->getArgument('dir') ?? '.')."): no such file or directory.");
+ return self::FAILURE;
+ }
+ $global = ZMConfig::get("global");
+ $host = $input->getOption("host") ?? $global["host"];
+ $port = $input->getOption("port") ?? $global["port"];
+ $server = new Server($host, $port);
+ $server->set(ZMConfig::get("global", "swoole"));
+ Console::init(0, $server);
+ ZMAtomic::$atomics["request"] = [];
+ for ($i = 0; $i < 32; ++$i) {
+ ZMAtomic::$atomics["request"][$i] = new Atomic(0);
+ }
+ $index = ["index.html", "index.htm"];
+ $server->on("request", function (Request $request, Response $response) use ($input, $index, $server) {
+ ZMAtomic::$atomics["request"][$server->worker_id]->add(1);
+ HttpUtil::handleStaticPage(
+ $request->server["request_uri"],
+ $response,
+ [
+ "document_root" => realpath($input->getArgument('dir') ?? '.'),
+ "document_index" => $index
+ ]);
+ echo "\r".Coroutine::stats()["coroutine_peak_num"];
+ });
+ $server->on("start", function ($server) {
+ Process::signal(SIGINT, function () use ($server) {
+ Console::warning("Server interrupted by keyboard.");
+ for ($i = 0; $i < 32; ++$i) {
+ $num = ZMAtomic::$atomics["request"][$i]->get();
+ if($num != 0)
+ echo "[$i]: ".$num."\n";
+ }
+ $server->shutdown();
+ $server->stop();
+ });
+ Console::success("Server started. Use Ctrl+C to stop.");
+ });
+ $out = [
+ "host" => $host,
+ "port" => $port,
+ "document_root" => realpath($input->getArgument('dir') ?? '.'),
+ "document_index" => implode(", ", $index)
+ ];
+ Console::printProps($out, $tty_width);
+ $server->start();
+ // return this if there was no problem running the command
+ // (it's equivalent to returning int(0))
+ return Command::SUCCESS;
+
+ // or return this if some error happened during the execution
+ // (it's equivalent to returning int(1))
+ // return Command::FAILURE;
+ }
+}
diff --git a/src/ZM/Command/RunServerCommand.php b/src/ZM/Command/RunServerCommand.php
new file mode 100644
index 00000000..639b6091
--- /dev/null
+++ b/src/ZM/Command/RunServerCommand.php
@@ -0,0 +1,57 @@
+setDefinition([
+ new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"),
+ new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"),
+ new InputOption("log-verbose", null, null, "调整消息等级到verbose (log-level=3)"),
+ new InputOption("log-info", null, null, "调整消息等级到info (log-level=2)"),
+ new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"),
+ new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
+ new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
+ new InputOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"),
+ new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
+ new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
+ new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
+ new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
+ ]);
+ $this->setDescription("Run zhamao-framework | 启动框架");
+ $this->setHelp("直接运行可以启动");
+
+ // ...
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ if(($opt = $input->getOption("env")) !== null) {
+ if(!in_array($opt, ["production", "staging", "development", ""])) {
+ $output->writeln(" \"--env\" option only accept production, development, staging and [empty] ! ");
+ return Command::FAILURE;
+ }
+ }
+ // ... put here the code to run in your command
+ // this method must return an integer number with the "exit status code"
+ // of the command. You can also use these constants to make code more readable
+ (new Framework($input->getOptions()))->start();
+ // return this if there was no problem running the command
+ // (it's equivalent to returning int(0))
+ return Command::SUCCESS;
+
+ // or return this if some error happened during the execution
+ // (it's equivalent to returning int(1))
+ // return Command::FAILURE;
+ }
+}
diff --git a/src/ZM/Command/SystemdCommand.php b/src/ZM/Command/SystemdCommand.php
new file mode 100644
index 00000000..8cea8607
--- /dev/null
+++ b/src/ZM/Command/SystemdCommand.php
@@ -0,0 +1,20 @@
+self_id = $self_id;
- }
-
- public function getQQ(){
- return $this->self_id;
- }
-
- public function getType() {
- return "qq";
- }
-}
diff --git a/src/ZM/Connection/ConnectionManager.php b/src/ZM/Connection/ConnectionManager.php
deleted file mode 100644
index 16e01fb4..00000000
--- a/src/ZM/Connection/ConnectionManager.php
+++ /dev/null
@@ -1,76 +0,0 @@
-fd == $fd) return $v;
- }
- return null;
- }
-
- /**
- * @param string $type
- * @param array $option
- * @return WSConnection[]|CQConnection[]
- */
- public static function getByType(string $type, $option = []) {
- $conn = [];
- foreach (ZMBuf::$connect as $v) {
- foreach ($option as $ks => $vs) {
- if (($v->$ks ?? "") == $vs) continue;
- else continue 2;
- }
- if ($v->getType() == $type) $conn[] = $v;
- }
- return $conn;
- }
-
- public static function getTypeClassName(string $type) {
- switch (strtolower($type)) {
- case "qq":
- case "universal":
- return CQConnection::class;
- case "webconsole":
- return WCConnection::class;
- case "proxy":
- return ProxyConnection::class;
- case "terminal":
- return TerminalConnection::class;
- default:
- foreach (ZMBuf::$custom_connection_class as $v) {
- /** @var WSConnection $r */
- $r = new $v(ZMBuf::$server, -1);
- if ($r->getType() == strtolower($type)) return $v;
- }
- return UnknownConnection::class;
- }
- }
-
- public static function close($fd) {
- foreach (ZMBuf::$connect as $k => $v) {
- if ($v->fd == $fd) {
- ZMBuf::$server->close($fd);
- unset(ZMBuf::$connect[$k]);
- break;
- }
- }
- }
-
- public static function registerCustomClass() {
- $classes = getAllClasses(DataProvider::getWorkingDir(). "/src/Custom/Connection/", "Custom\\Connection");
- ZMBuf::$custom_connection_class = $classes;
- }
-}
diff --git a/src/ZM/Connection/ProxyConnection.php b/src/ZM/Connection/ProxyConnection.php
deleted file mode 100644
index f43083f0..00000000
--- a/src/ZM/Connection/ProxyConnection.php
+++ /dev/null
@@ -1,13 +0,0 @@
-server = $server;
- $this->fd = $fd;
- }
-
- public abstract function getType();
-
- public function exists() {
- return $this->available = $this->server->exist($this->fd);
- }
-
- public function close() {
- ConnectionManager::close($this->fd);
- }
-
- public function push($data, $push_error_record = true) {
- if ($data === null || $data == "") {
- Console::warning("推送了空消息");
- return false;
- }
- if (!$this->server->exist($this->fd)) {
- Console::warning("Swoole 原生 websocket连接池中无此连接");
- return false;
- }
- if ($this->server->push($this->fd, $data) === false) {
- $data = unicode_decode($data);
- if ($push_error_record) Console::warning("API push failed. Data: " . $data);
- Console::warning("websocket数据未成功推送,长度:" . strlen($data));
- return false;
- }
- return true;
- }
-}
diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php
new file mode 100644
index 00000000..8e776c0a
--- /dev/null
+++ b/src/ZM/ConsoleApplication.php
@@ -0,0 +1,104 @@
+selfCheck();
+
+ //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令
+ if (LOAD_MODE === 0) define("WORKING_DIR", getcwd());
+ elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
+ elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
+ if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
+ /** @noinspection PhpIncludeInspection */
+ require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
+ }
+ if (LOAD_MODE == 2) {
+ // Phar 模式,2.0 不提供哦
+ //require_once FRAMEWORK_DIR . "/vendor/autoload.php";
+ spl_autoload_register('phar_classloader');
+ } elseif (LOAD_MODE == 0) {
+ /** @noinspection PhpIncludeInspection
+ * @noinspection RedundantSuppression
+ */
+ require_once WORKING_DIR . "/vendor/autoload.php";
+ echo "* This is repository mode.\n";
+ $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
+ if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
+ echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] ";
+ $r = strtolower(trim(fgets(STDIN)));
+ if ($r === "" || $r === "y") {
+ $composer["autoload"]["psr-4"]["Module\\"] = "src/Module";
+ $composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom";
+ $r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
+ if ($r !== false) {
+ echo "成功添加!请重新进行 composer update !\n";
+ exit(1);
+ } else {
+ echo "添加失败!请按任意键继续!";
+ fgets(STDIN);
+ exit(1);
+ }
+ } else {
+ exit(1);
+ }
+ }
+ }
+
+ $this->addCommands([
+ new RunServerCommand(), //运行主服务的指令控制器
+ new InitCommand(), //初始化用的,用于项目初始化和phar初始化
+ new PureHttpCommand() //纯HTTP服务器指令
+ ]);
+ /*
+ $command_register = ZMConfig::get("global", "command_register_class") ?? [];
+ foreach ($command_register as $v) {
+ $obj = new $v();
+ if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command");
+ $this->add($obj);
+ }*/
+ }
+
+ /**
+ * @param InputInterface|null $input
+ * @param OutputInterface|null $output
+ * @return int
+ */
+ public function run(InputInterface $input = null, OutputInterface $output = null) {
+ try {
+ return parent::run($input, $output);
+ } catch (Exception $e) {
+ die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})");
+ }
+ }
+
+ private function selfCheck() {
+ if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19");
+ if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !");
+ //if (!extension_loaded("gd")) die("Can not find gd extension.\n");
+ //if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
+ if (substr(PHP_VERSION, 0, 1) < "7") die("PHP >=7 required.\n");
+ //if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
+ //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
+ //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
+ return true;
+ }
+}
diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php
index 83c5b600..e1de0e1e 100644
--- a/src/ZM/Context/Context.php
+++ b/src/ZM/Context/Context.php
@@ -5,21 +5,22 @@ namespace ZM\Context;
use Co;
-use Framework\ZMBuf;
+use Exception;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use swoole_server;
-use ZM\API\CQAPI;
-use ZM\Connection\ConnectionManager;
-use ZM\Connection\CQConnection;
-use ZM\Connection\WSConnection;
+use ZM\ConnectionManager\ConnectionObject;
+use ZM\ConnectionManager\ManagerGM;
+use ZM\Console\Console;
use ZM\Exception\InvalidArgumentException;
use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
-use ZM\Utils\ZMRobot;
+use ZM\API\ZMRobot;
+use ZM\Utils\CoMessage;
class Context implements ContextInterface
{
+ public static $context = [];
private $cid;
public function __construct($cid) { $this->cid = $cid; }
@@ -27,34 +28,34 @@ class Context implements ContextInterface
/**
* @return swoole_server|null
*/
- public function getServer() { return ZMBuf::$context[$this->cid]["server"] ?? null; }
+ public function getServer() { return self::$context[$this->cid]["server"] ?? server(); }
/**
* @return Frame|null
*/
- public function getFrame() { return ZMBuf::$context[$this->cid]["frame"] ?? null; }
+ public function getFrame() { return self::$context[$this->cid]["frame"] ?? null; }
- public function getFd() { return ZMBuf::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; }
+ public function getFd() { return self::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; }
/**
* @return array|null
*/
- public function getData() { return ZMBuf::$context[$this->cid]["data"] ?? null; }
+ public function getData() { return self::$context[$this->cid]["data"] ?? null; }
- public function setData($data) { ZMBuf::$context[$this->cid]["data"] = $data; }
+ public function setData($data) { self::$context[$this->cid]["data"] = $data; }
/**
* @return Request|null
*/
- public function getRequest() { return ZMBuf::$context[$this->cid]["request"] ?? null; }
+ public function getRequest() { return self::$context[$this->cid]["request"] ?? null; }
/**
* @return Response|null
*/
- public function getResponse() { return ZMBuf::$context[$this->cid]["response"] ?? null; }
+ public function getResponse() { return self::$context[$this->cid]["response"] ?? null; }
- /** @return WSConnection */
- public function getConnection() { return ConnectionManager::get($this->getFd()); }
+ /** @return ConnectionObject|null */
+ public function getConnection() { return ManagerGM::get($this->getFd()); }
/**
* @return int|null
@@ -65,37 +66,37 @@ class Context implements ContextInterface
* @return ZMRobot|null
*/
public function getRobot() {
- $conn = ConnectionManager::get($this->getFrame()->fd);
- return $conn instanceof CQConnection ? new ZMRobot($conn) : null;
+ $conn = ManagerGM::get($this->getFrame()->fd);
+ return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
}
- public function getMessage() { return ZMBuf::$context[$this->cid]["data"]["message"] ?? null; }
+ public function getMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
- public function setMessage($msg) { ZMBuf::$context[$this->cid]["data"]["message"] = $msg; }
+ public function setMessage($msg) { self::$context[$this->cid]["data"]["message"] = $msg; }
public function getUserId() { return $this->getData()["user_id"] ?? null; }
- public function setUserId($id) { ZMBuf::$context[$this->cid]["data"]["user_id"] = $id; }
+ public function setUserId($id) { self::$context[$this->cid]["data"]["user_id"] = $id; }
public function getGroupId() { return $this->getData()["group_id"] ?? null; }
- public function setGroupId($id) { ZMBuf::$context[$this->cid]["data"]["group_id"] = $id; }
+ public function setGroupId($id) { self::$context[$this->cid]["data"]["group_id"] = $id; }
public function getDiscussId() { return $this->getData()["discuss_id"] ?? null; }
- public function setDiscussId($id) { ZMBuf::$context[$this->cid]["data"]["discuss_id"] = $id; }
+ public function setDiscussId($id) { self::$context[$this->cid]["data"]["discuss_id"] = $id; }
public function getMessageType() { return $this->getData()["message_type"] ?? null; }
- public function setMessageType($type) { ZMBuf::$context[$this->cid]["data"]["message_type"] = $type; }
+ public function setMessageType($type) { self::$context[$this->cid]["data"]["message_type"] = $type; }
public function getRobotId() { return $this->getData()["self_id"] ?? null; }
- public function getCache($key) { return ZMBuf::$context[$this->cid]["cache"][$key] ?? null; }
+ public function getCache($key) { return self::$context[$this->cid]["cache"][$key] ?? null; }
- public function setCache($key, $value) { ZMBuf::$context[$this->cid]["cache"][$key] = $value; }
+ public function setCache($key, $value) { self::$context[$this->cid]["cache"][$key] = $value; }
- public function getCQResponse() { return ZMBuf::$context[$this->cid]["cq_response"] ?? null; }
+ public function getCQResponse() { return self::$context[$this->cid]["cq_response"] ?? null; }
/**
* only can used by cq->message event function
@@ -109,13 +110,21 @@ class Context implements ContextInterface
case "private":
case "discuss":
$this->setCache("has_reply", true);
- return CQAPI::quick_reply(ConnectionManager::get($this->getFrame()->fd), $this->getData(), $msg, $yield);
+ $data = $this->getData();
+ $conn = $this->getConnection();
+ switch ($data["message_type"]) {
+ case "group":
+ return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg);
+ case "private":
+ return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg);
+ }
+ return null;
}
return false;
}
public function finalReply($msg, $yield = false) {
- ZMBuf::$context[$this->cid]["cache"]["block_continue"] = true;
+ self::$context[$this->cid]["cache"]["block_continue"] = true;
if ($msg == "") return true;
return $this->reply($msg, $yield);
}
@@ -129,12 +138,24 @@ class Context implements ContextInterface
* @throws WaitTimeoutException
*/
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
- if ($prompt != "") $this->reply($prompt);
if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"]))
throw new InvalidArgumentException("协程等待参数缺失");
+
+ Console::debug("==== 开始等待输入 ====");
+ if ($prompt != "") $this->reply($prompt);
+
+ try {
+ $r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]);
+ } catch (Exception $e) {
+ $r = false;
+ }
+ if ($r === false) {
+ throw new WaitTimeoutException($this, $timeout_prompt);
+ }
+ return $r["message"];
+ /*
$cid = Co::getuid();
- $api_id = ZMBuf::$atomics["wait_msg_id"]->get();
- ZMBuf::$atomics["wait_msg_id"]->add(1);
+ $api_id = ZMAtomic::get("wait_msg_id")->add(1);
$hang = [
"coroutine" => $cid,
"user_id" => $this->getData()["user_id"],
@@ -146,49 +167,57 @@ class Context implements ContextInterface
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
$hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"];
}
- ZMBuf::appendKey("wait_api", $api_id, $hang);
+ 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 = ZMBuf::get("wait_api")[$api_id] ?? null;
- if ($r !== null) {
+ $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null;
+ if (is_array($r)) {
Co::resume($r["coroutine"]);
}
});
Co::suspend();
- $sess = ZMBuf::get("wait_api")[$api_id];
- ZMBuf::unsetByValue("wait_api", $api_id);
+ SpinLock::lock("wait_api");
+ $hw = LightCacheInside::get("wait_api", "wait_api") ?? [];
+ $sess = $hw[$api_id];
+ unset($hw[$api_id]);
+ LightCacheInside::set("wait_api", "wait_api", $hw);
$result = $sess["result"];
if (isset($id)) swoole_timer_clear($id);
if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
- return $result;
+ return $result;*/
}
/**
- * @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed|string
* @throws InvalidArgumentException
* @throws WaitTimeoutException
*/
- public function getArgs(&$arg, $mode, $prompt_msg) {
+ public function getArgs($mode, $prompt_msg) {
+ $arg = ctx()->getCache("match");
switch ($mode) {
case ZM_MATCH_ALL:
$p = $arg;
- array_shift($p);
return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p));
case ZM_MATCH_NUMBER:
foreach ($arg as $k => $v) {
if (is_numeric($v)) {
array_splice($arg, $k, 1);
+ ctx()->setCache("match", $arg);
return $v;
}
}
return $this->waitMessage($prompt_msg);
case ZM_MATCH_FIRST:
- if (isset($arg[1])) {
- $a = $arg[1];
- array_splice($arg, 1, 1);
+ if (isset($arg[0])) {
+ $a = $arg[0];
+ array_splice($arg, 0, 1);
+ ctx()->setCache("match", $arg);
return $a;
} else {
return $this->waitMessage($prompt_msg);
@@ -197,10 +226,28 @@ class Context implements ContextInterface
throw new InvalidArgumentException();
}
+ /**
+ * @param string $prompt_msg
+ * @return int|mixed|string
+ * @throws InvalidArgumentException
+ * @throws WaitTimeoutException
+ */
+ public function getNextArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_FIRST, $prompt_msg); }
+
+ /**
+ * @param string $prompt_msg
+ * @return int|mixed|string
+ * @throws InvalidArgumentException
+ * @throws WaitTimeoutException
+ */
+ public function getFullArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_ALL, $prompt_msg); }
+
public function cloneFromParent() {
- set_coroutine_params(ZMBuf::$context[Co::getPcid()] ?? ZMBuf::$context[$this->cid]);
+ set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]);
return context();
}
- public function copy() { return ZMBuf::$context[$this->cid]; }
+ public function copy() { return self::$context[$this->cid]; }
+
+ public function getOption() { return self::getCache("match"); }
}
diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php
index d736331d..00cbda60 100644
--- a/src/ZM/Context/ContextInterface.php
+++ b/src/ZM/Context/ContextInterface.php
@@ -7,9 +7,9 @@ namespace ZM\Context;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
-use ZM\Connection\WSConnection;
+use ZM\ConnectionManager\ConnectionObject;
use ZM\Http\Response;
-use ZM\Utils\ZMRobot;
+use ZM\API\ZMRobot;
interface ContextInterface
{
@@ -26,7 +26,7 @@ interface ContextInterface
public function setData($data);
- /** @return WSConnection */
+ /** @return ConnectionObject */
public function getConnection();
/** @return int|null */
@@ -97,12 +97,15 @@ interface ContextInterface
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "");
/**
- * @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed
*/
- public function getArgs(&$arg, $mode, $prompt_msg);
+ public function getArgs($mode, $prompt_msg);
+
+ public function getNextArg($prompt_msg = "");
+
+ public function getFullArg($prompt_msg = "");
public function setCache($key, $value);
@@ -115,4 +118,6 @@ interface ContextInterface
public function cloneFromParent();
public function copy();
+
+ public function getOption();
}
diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php
index a2da49a0..0dd1c35d 100644
--- a/src/ZM/DB/DB.php
+++ b/src/ZM/DB/DB.php
@@ -1,15 +1,15 @@
-get();
+ $conn = SqlPoolStorage::$sql_pool->get();
if ($conn === false) {
- ZMBuf::$sql_pool->put(null);
+ SqlPoolStorage::$sql_pool->put(null);
throw new DbException("无法连接SQL!" . $line);
}
$result = $conn->query($line) === false ? false : true;
- ZMBuf::$sql_pool->put($conn);
+ SqlPoolStorage::$sql_pool->put($conn);
return $result;
} catch (DBException $e) {
- if (ZMBuf::get("sql_log") === true) {
- $log =
- "[" . date("Y-m-d H:i:s") .
- " " . round(microtime(true) - $starttime, 5) .
- "] " . $line . " (Error:" . $e->getMessage() . ")\n";
- Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
- }
Console::warning($e->getMessage());
throw $e;
}
@@ -95,24 +85,21 @@ class DB
* @throws DbException
*/
public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) {
- if (ZMBuf::get("sql_log") === true) {
- $starttime = microtime(true);
- }
- Console::debug("MySQL: ".$line);
+ Console::debug("MySQL: ".$line." | ". implode(", ", $params));
try {
- $conn = ZMBuf::$sql_pool->get();
+ $conn = SqlPoolStorage::$sql_pool->get();
if ($conn === false) {
- ZMBuf::$sql_pool->put(null);
+ SqlPoolStorage::$sql_pool->put(null);
throw new DbException("无法连接SQL!" . $line);
}
$ps = $conn->prepare($line);
if ($ps === false) {
- ZMBuf::$sql_pool->put(null);
+ SqlPoolStorage::$sql_pool->put(null);
throw new DbException("SQL语句查询错误," . $line . ",错误信息:" . $conn->error);
} else {
if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) {
var_dump($ps);
- ZMBuf::$sql_pool->put(null);
+ SqlPoolStorage::$sql_pool->put(null);
throw new DbException("语句查询错误!返回的不是 PDOStatement" . $line);
}
if ($params == []) $result = $ps->execute();
@@ -120,28 +107,14 @@ class DB
$result = $ps->execute([$params]);
} else $result = $ps->execute($params);
if ($result !== true) {
- ZMBuf::$sql_pool->put(null);
+ SqlPoolStorage::$sql_pool->put(null);
throw new DBException("语句[$line]错误!" . $ps->errorInfo()[2]);
//echo json_encode(debug_backtrace(), 128 | 256);
}
- ZMBuf::$sql_pool->put($conn);
- if (ZMBuf::get("sql_log") === true) {
- $log =
- "[" . date("Y-m-d H:i:s") .
- " " . round(microtime(true) - $starttime, 4) .
- "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
- Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
- }
+ SqlPoolStorage::$sql_pool->put($conn);
return $ps->fetchAll($fetch_mode);
}
} catch (DbException $e) {
- if (ZMBuf::get("sql_log") === true) {
- $log =
- "[" . date("Y-m-d H:i:s") .
- " " . round(microtime(true) - $starttime, 4) .
- "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n";
- Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
- }
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
@@ -150,13 +123,6 @@ class DB
Console::warning($e->getMessage());
throw $e;
} catch (PDOException $e) {
- if (ZMBuf::get("sql_log") === true) {
- $log =
- "[" . date("Y-m-d H:i:s") .
- " " . round(microtime(true) - $starttime, 4) .
- "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n";
- Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
- }
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php
index 7d0f8995..903916db 100644
--- a/src/ZM/DB/SelectBody.php
+++ b/src/ZM/DB/SelectBody.php
@@ -4,7 +4,6 @@
namespace ZM\DB;
-use Framework\Console;
use ZM\Exception\DbException;
class SelectBody
@@ -46,17 +45,7 @@ class SelectBody
* @throws DbException
*/
public function fetchAll($fetch_mode = ZM_DEFAULT_FETCH_MODE) {
- if ($this->table->isCacheEnabled()) {
- $rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing));
- if (array_key_exists($rr, $this->table->cache)) {
- Console::debug('SQL query cached: ' . $rr);
- return $this->table->cache[$rr]->getResult();
- }
- }
$this->execute($fetch_mode);
- if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) {
- $this->table->cache[$rr] = $this;
- }
return $this->getResult();
}
diff --git a/src/ZM/DB/Table.php b/src/ZM/DB/Table.php
index cf85d4cd..939bc8ad 100644
--- a/src/ZM/DB/Table.php
+++ b/src/ZM/DB/Table.php
@@ -14,10 +14,7 @@ class Table
private static $table_instance = [];
- private $enable_cache;
-
- public function __construct($table_name, $enable_cache) {
- $this->enable_cache = $enable_cache;
+ public function __construct($table_name) {
$this->table_name = $table_name;
self::$table_instance[$table_name] = $this;
}
@@ -75,5 +72,4 @@ class Table
*/
public function getTableName() { return $this->table_name; }
- public function isCacheEnabled() { return $this->enable_cache; }
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php
deleted file mode 100644
index 11f9492a..00000000
--- a/src/ZM/Event/CQ/MessageEvent.php
+++ /dev/null
@@ -1,201 +0,0 @@
-data = $data;
- $this->connection = $conn_or_response;
- $this->circle = $circle;
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onBefore() {
- $obj_list = ZMBuf::$events[CQBefore::class]["message"] ?? [];
- foreach ($obj_list as $v) {
- if ($v->level < 200) break;
- EventHandler::callWithMiddleware(
- $v->class,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if (!$r) context()->setCache("block_continue", true);
- }
- );
- if (context()->getCache("block_continue") === true) return false;
- }
- foreach (ZMBuf::get("wait_api", []) as $k => $v) {
- if (context()->getData()["user_id"] == $v["user_id"] &&
- context()->getData()["self_id"] == $v["self_id"] &&
- context()->getData()["message_type"] == $v["message_type"] &&
- (context()->getData()[context()->getData()["message_type"] . "_id"] ?? context()->getData()["user_id"]) ==
- ($v[$v["message_type"] . "_id"] ?? $v["user_id"])) {
- $v["result"] = context()->getData()["message"];
- ZMBuf::appendKey("wait_api", $k, $v);
- Co::resume($v["coroutine"]);
- return false;
- }
- }
- foreach (ZMBuf::$events[CQBefore::class]["message"] ?? [] as $v) {
- if ($v->level >= 200) continue;
- $c = $v->class;
- if (ctx()->getCache("level") != 0) continue;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if (!$r) context()->setCache("block_continue", true);
- }
- );
- if (context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-
- /**
- * @throws AnnotationException
- */
- public function onActivate() {
- try {
- $word = split_explode(" ", str_replace("\r", "", context()->getMessage()));
- if (count(explode("\n", $word[0])) >= 2) {
- $enter = explode("\n", context()->getMessage());
- $first = split_explode(" ", array_shift($enter));
- $word = array_merge($first, $enter);
- foreach ($word as $k => $v) {
- $word[$k] = trim($word[$k]);
- }
- }
- /** @var ModBase[] $obj */
- $obj = [];
- foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) {
- /** @var CQCommand $v */
- if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") continue;
- elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
- ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
- ($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) &&
- ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"]))
- ) {
- $c = $v->class;
- $class_construct = [
- "data" => context()->getData(),
- "connection" => context()->getConnection()
- ];
- if (!isset($obj[$c])) {
- $obj[$c] = new $c($class_construct);
- }
- if ($word[0] != "" && $v->match == $word[0]) {
- Console::debug("Calling $c -> {$v->method}");
- $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) {
- if (is_string($r)) context()->reply($r);
- return true;
- });
- return;
- } elseif (in_array($word[0], $v->alias)) {
- Console::debug("Calling $c -> {$v->method}");
- $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) {
- if (is_string($r)) context()->reply($r);
- return true;
- });
- return;
- } elseif ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, context()->getMessage())) !== false) {
- Console::debug("Calling $c -> {$v->method}");
- $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) {
- if (is_string($r)) context()->reply($r);
- return true;
- });
- return;
- } elseif ($v->fullMatch != "" && (preg_match("/".$v->fullMatch."/u", ctx()->getMessage(), $args)) != 0) {
- Console::debug("Calling $c -> {$v->method}");
- array_shift($args);
- $this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) {
- if (is_string($r)) context()->reply($r);
- return true;
- });
- return;
- }
- }
- }
- foreach (ZMBuf::$events[CQMessage::class] ?? [] as $v) {
- /** @var CQMessage $v */
- if (
- ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) &&
- ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
- ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
- ($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) &&
- ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) &&
- ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]))) {
- $c = $v->class;
- Console::debug("Calling CQMessage: $c -> {$v->method}");
- if (!isset($obj[$c]))
- $obj[$c] = new $c([
- "data" => context()->getData(),
- "connection" => $this->connection
- ], ModHandleType::CQ_MESSAGE);
- EventHandler::callWithMiddleware($obj[$c], $v->method, [], [context()->getData()["message"]], function ($r) {
- if (is_string($r)) context()->reply($r);
- });
- if (context()->getCache("block_continue") === true) return;
- }
- }
- } catch (WaitTimeoutException $e) {
- $e->module->finalReply($e->getMessage());
- }
- }
-
- /**
- * 在调用完事件后执行的
- * @throws AnnotationException
- */
- public function onAfter() {
- context()->setCache("block_continue", null);
- foreach (ZMBuf::$events[CQAfter::class]["message"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if (!$r) context()->setCache("block_continue", true);
- }
- );
- if (context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-
- public function hasReply() {
- return $this->function_call;
- }
-}
diff --git a/src/ZM/Event/CQ/MetaEvent.php b/src/ZM/Event/CQ/MetaEvent.php
deleted file mode 100644
index bd418917..00000000
--- a/src/ZM/Event/CQ/MetaEvent.php
+++ /dev/null
@@ -1,79 +0,0 @@
-data = $data;
- $this->connection = $connection;
- $this->circle = $circle;
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onBefore() {
- foreach (ZMBuf::$events[CQBefore::class]["meta_event"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if(!$r) context()->setCache("block_continue", true);
- }
- );
- if(context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-
- /**
- * @throws AnnotationException
- */
- public function onActivate() {
- try {
- /** @var ModBase[] $obj */
- $obj = [];
- foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) {
- /** @var CQMetaEvent $v */
- if (
- ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == $this->data["meta_event_type"])) &&
- ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) {
- $c = $v->class;
- if (!isset($obj[$c]))
- $obj[$c] = new $c([
- "data" => $this->data,
- "connection" => $this->connection
- ], ModHandleType::CQ_META_EVENT);
- EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
- if (is_string($r)) context()->reply($r);
- });
- if (context()->getCache("block_continue") === true) return;
- }
- }
- } catch (WaitTimeoutException $e) {
- $e->module->finalReply($e->getMessage());
- }
- }
-}
diff --git a/src/ZM/Event/CQ/NoticeEvent.php b/src/ZM/Event/CQ/NoticeEvent.php
deleted file mode 100644
index 0973f826..00000000
--- a/src/ZM/Event/CQ/NoticeEvent.php
+++ /dev/null
@@ -1,103 +0,0 @@
-data = $data;
- $this->connection = $connection;
- $this->circle = $circle;
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onBefore() {
- foreach (ZMBuf::$events[CQBefore::class]["notice"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if(!$r) context()->setCache("block_continue", true);
- }
- );
- if(context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-
- /**
- * @throws AnnotationException
- */
- public function onActivate() {
- try {
- /** @var ModBase[] $obj */
- $obj = [];
- foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) {
- /** @var CQNotice $v */
- if (
- ($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == $this->data["notice_type"])) &&
- ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
- ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) &&
- ($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) {
- $c = $v->class;
- if (!isset($obj[$c]))
- $obj[$c] = new $c([
- "data" => $this->data,
- "connection" => $this->connection
- ], ModHandleType::CQ_NOTICE);
- EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
- if (is_string($r)) context()->reply($r);
- });
- if (context()->getCache("block_continue") === true) return;
- }
- }
- } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
- $e->module->finalReply($e->getMessage());
- }
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onAfter() {
- foreach (ZMBuf::$events[CQAfter::class]["notice"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if(!$r) context()->setCache("block_continue", true);
- }
- );
- if(context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/CQ/RequestEvent.php b/src/ZM/Event/CQ/RequestEvent.php
deleted file mode 100644
index 84f9cf7f..00000000
--- a/src/ZM/Event/CQ/RequestEvent.php
+++ /dev/null
@@ -1,103 +0,0 @@
-data = $data;
- $this->connection = $connection;
- $this->circle = $circle;
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onBefore() {
- foreach (ZMBuf::$events[CQBefore::class]["request"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if(!$r) context()->setCache("block_continue", true);
- }
- );
- if(context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-
- /**
- * @throws AnnotationException
- */
- public function onActivate() {
- try {
- /** @var ModBase[] $obj */
- $obj = [];
- foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) {
- /** @var CQRequest $v */
- if (
- ($v->request_type == '' || ($v->request_type != '' && $v->request_type == $this->data["request_type"])) &&
- ($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
- ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ($this->data["user_id"] ?? 0))) &&
- ($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) {
- $c = $v->class;
- if (!isset($obj[$c]))
- $obj[$c] = new $c([
- "data" => $this->data,
- "connection" => $this->connection
- ], ModHandleType::CQ_REQUEST);
- EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
- if (is_string($r)) context()->reply($r);
- });
- if (context()->getCache("block_continue") === true) return;
- }
- }
- } catch (WaitTimeoutException $e) {
- $e->module->finalReply($e->getMessage());
- }
- }
-
- /**
- * @return bool
- * @throws AnnotationException
- */
- public function onAfter() {
- foreach (ZMBuf::$events[CQAfter::class]["request"] ?? [] as $v) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["data" => context()->getData(), "connection" => $this->connection],
- [],
- function ($r) {
- if(!$r) context()->setCache("block_continue", true);
- }
- );
- if(context()->getCache("block_continue") === true) return false;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/Event.php b/src/ZM/Event/Event.php
deleted file mode 100644
index f78aff33..00000000
--- a/src/ZM/Event/Event.php
+++ /dev/null
@@ -1,11 +0,0 @@
-class = $class;
+ try {
+ $this->eid = ZMAtomic::get("_event_id")->add(1);
+ $list = LightCacheInside::get("wait_api", "event_trace");
+ } catch (ZMException $e) {
+ $list = [];
+ }
+ if (isset($list[$class])) $this->log = true;
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 开始分发事件: " . $class);
+ }
+
+ public function setRuleFunction(callable $rule = null) {
+ $this->rule = $rule;
+ return $this;
+ }
+
+ public function setReturnFunction(callable $return_func) {
+ $this->return_func = $return_func;
+ return $this;
+ }
+
+ public function dispatchEvents(...$params) {
+ try {
+ foreach ((EventManager::$events[$this->class] ?? []) as $v) {
+ $result = $this->dispatchEvent($v, $this->rule, ...$params);
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。");
+ if ($result !== false && is_callable($this->return_func)) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ...");
+ ($this->return_func)($result);
+ }
+ }
+ return true;
+ } catch (InterruptException $e) {
+ return $e->return_var;
+ } catch (AnnotationException $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param AnnotationBase|null $v
+ * @param null $rule_func
+ * @param mixed ...$params
+ * @throws AnnotationException
+ * @throws InterruptException
+ * @return mixed
+ */
+ public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) {
+ $q_c = $v->class;
+ $q_f = $v->method;
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在判断 " . $q_c . "::" . $q_f . " 方法下的 rule ...");
+ if ($rule_func !== null && !$rule_func($v)) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 判断为 false, 拒绝执行此方法。");
+ return false;
+ }
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 为真,继续执行方法本身 ...");
+ if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
+ $middlewares = EventManager::$middleware_map[$q_c][$q_f];
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware:" . implode(", ", $middlewares));
+ $before_result = true;
+ $r = [];
+ foreach ($middlewares as $k => $middleware) {
+ if (!isset(EventManager::$middlewares[$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
+ $middleware_obj = EventManager::$middlewares[$middleware];
+ $before = $middleware_obj["class"];
+ //var_dump($middleware_obj);
+ $r[$k] = new $before();
+ $r[$k]->class = $q_c;
+ $r[$k]->method = $q_f;
+ if (isset($middleware_obj["before"])) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ...");
+ $rs = $middleware_obj["before"];
+ $before_result = $r[$k]->$rs(...$params);
+ if ($before_result === false) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 false,停止执行原事件,开始执行下一事件。");
+ break;
+ } else {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 true,继续执行原事件。");
+ }
+ }
+ }
+ if ($before_result) {
+ try {
+ $q_o = ZMUtil::getModInstance($q_c);
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
+ $result = $q_o->$q_f(...$params);
+ } catch (Exception $e) {
+ if ($e instanceof InterruptException) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 检测到事件阻断调用,正在跳出事件分发器 ...");
+ throw $e;
+ }
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ...");
+ for ($i = count($middlewares) - 1; $i >= 0; --$i) {
+ $middleware_obj = EventManager::$middlewares[$middlewares[$i]];
+ if (!isset($middleware_obj["exceptions"])) continue;
+ foreach ($middleware_obj["exceptions"] as $name => $method) {
+ if ($e instanceof $name) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 的异常 " . get_class($e) . " 被 Middleware:" . $middlewares[$i] . " 下的 " . get_class($r[$i]) . "::" . $method . " 捕获。");
+ $r[$i]->$method($e);
+ self::interrupt();
+ }
+ }
+ }
+ throw $e;
+ }
+ for ($i = count($middlewares) - 1; $i >= 0; --$i) {
+ $middleware_obj = EventManager::$middlewares[$middlewares[$i]];
+ if (isset($middleware_obj["after"], $r[$i])) {
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ...");
+ $r[$i]->{$middleware_obj["after"]}(...$params);
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 后置事件执行完毕!");
+ }
+ }
+ return $result;
+ }
+ return false;
+ } else {
+ $q_o = ZMUtil::getModInstance($q_c);
+ if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
+ return $q_o->$q_f(...$params);
+ }
+ }
+}
diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php
deleted file mode 100644
index 7d7dce31..00000000
--- a/src/ZM/Event/EventHandler.php
+++ /dev/null
@@ -1,320 +0,0 @@
-shutdown();
- else ZMBuf::$server->shutdown();
- });
- ZMBuf::$server = $param0;
- $r = (new WorkerStartEvent($param0, $param1))->onActivate();
- Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold");
- $r->onAfter();
- self::startTick();
- } catch (Exception $e) {
- Console::error("Worker加载出错!停止服务!");
- Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
- ZMUtil::stop();
- return;
- } catch (Error $e) {
- Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
- Console::error("Maybe it caused by your own code if in your own Module directory.");
- Console::log($e->getTraceAsString(), 'gray');
- ZMUtil::stop();
- }
- break;
- case "message":
- /** @var Frame $param1 */
- /** @var Server $param0 */
- $conn = ConnectionManager::get($param1->fd);
- set_coroutine_params(["server" => $param0, "frame" => $param1, "connection" => $conn]);
- try {
- (new MessageEvent($param0, $param1))->onActivate()->onAfter();
- } catch (Error $e) {
- $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
- Console::error("Fatal error when calling $event_name: " . $error_msg);
- Console::stackTrace();
- }
- break;
- case "request":
- try {
- set_coroutine_params(["request" => $param0, "response" => $param1]);
- (new RequestEvent($param0, $param1))->onActivate()->onAfter();
- } catch (Exception $e) {
- /** @var Response $param1 */
- $param1->status(500);
- Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
- " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
- );
- if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage());
- Console::error("Internal server exception (500), caused by " . get_class($e));
- Console::log($e->getTraceAsString(), "gray");
- } catch (Error $e) {
- /** @var Response $param1 */
- $param1->status(500);
- Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
- " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
- );
- $doc = "Internal server error
";
- $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
- if (ZMBuf::$atomics["info_level"]->get() >= 4) $doc .= $error_msg;
- if (!$param1->isEnd()) $param1->end($doc);
- Console::error("Internal server error (500): " . $error_msg);
- Console::log($e->getTraceAsString(), "gray");
- }
- break;
- case "open":
- /** @var Request $param1 */
- set_coroutine_params(["server" => $param0, "request" => $param1, "fd" => $param1->fd]);
- try {
- (new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
- } catch (Error $e) {
- $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
- Console::error("Fatal error when calling $event_name: " . $error_msg);
- Console::stackTrace();
- }
- break;
- case "close":
- set_coroutine_params(["server" => $param0, "fd" => $param1]);
- try {
- (new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
- } catch (Error $e) {
- $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
- Console::error("Fatal error when calling $event_name: " . $error_msg);
- Console::stackTrace();
- }
- break;
- }
- //Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold"));
- }
-
- /**
- * @param $event_data
- * @param $conn_or_response
- * @param int $level
- * @return bool
- * @throws AnnotationException
- */
- public static function callCQEvent($event_data, $conn_or_response, int $level = 0) {
- ctx()->setCache("level", $level);
- if ($level >= 5) {
- Console::warning("Recursive call reached " . $level . " times");
- Console::stackTrace();
- return false;
- }
- $starttime = microtime(true);
- switch ($event_data["post_type"]) {
- case "message":
- $event = new CQ\MessageEvent($event_data, $conn_or_response, $level);
- if ($event->onBefore()) $event->onActivate();
- $event->onAfter();
- return $event->hasReply();
- break;
- case "notice":
- $event = new CQ\NoticeEvent($event_data, $conn_or_response, $level);
- if ($event->onBefore()) $event->onActivate();
- $event->onAfter();
- return true;
- case "request":
- $event = new CQ\RequestEvent($event_data, $conn_or_response, $level);
- if ($event->onBefore()) $event->onActivate();
- $event->onAfter();
- return true;
- case "meta_event":
- $event = new CQ\MetaEvent($event_data, $conn_or_response, $level);
- if ($event->onBefore()) $event->onActivate();
- return true;
- }
- unset($starttime);
- return false;
- }
-
- /**
- * @param $req
- * @throws AnnotationException
- */
- public static function callCQResponse($req) {
- Console::debug("收到来自API连接的回复:" . json_encode($req, 128 | 256));
- $status = $req["status"];
- $retcode = $req["retcode"];
- $data = $req["data"];
- if (isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) {
- $origin = ZMBuf::get("sent_api")[$req["echo"]];
- $self_id = $origin["self_id"];
- $response = [
- "status" => $status,
- "retcode" => $retcode,
- "data" => $data,
- "self_id" => $self_id,
- "echo" => $req["echo"]
- ];
- set_coroutine_params(["cq_response" => $response]);
- if (isset(ZMBuf::$events[CQAPIResponse::class][$req["retcode"]])) {
- list($c, $method) = ZMBuf::$events[CQAPIResponse::class][$req["retcode"]];
- $class = new $c(["data" => $origin["data"]]);
- call_user_func_array([$class, $method], [$origin["data"], $req]);
- }
- $origin_ctx = ctx()->copy();
- ctx()->setCache("action", $origin["data"]["action"] ?? "unknown");
- ctx()->setData($origin["data"]);
- foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
- if (($v->action == "" || $v->action == ctx()->getCache("action")) && $v->with_result) {
- $c = $v->class;
- self::callWithMiddleware($c, $v->method, context()->copy(), [ctx()->getCache("action"), $origin["data"]["params"] ?? [], ctx()->getRobotId()]);
- if (context()->getCache("block_continue") === true) break;
- }
- }
- set_coroutine_params($origin_ctx);
- if (($origin["func"] ?? null) !== null) {
- call_user_func($origin["func"], $response, $origin["data"]);
- } elseif (($origin["coroutine"] ?? false) !== false) {
- $p = ZMBuf::get("sent_api");
- $p[$req["echo"]]["result"] = $response;
- ZMBuf::set("sent_api", $p);
- Co::resume($origin['coroutine']);
- }
- ZMBuf::unsetByValue("sent_api", $req["echo"]);
- }
- }
-
- public static function callCQAPISend($reply, ?CQConnection $connection) {
- $action = $reply["action"] ?? null;
- if ($action === null) {
- Console::warning("API 激活事件异常!");
- return;
- }
- if (ctx() === null) $content = [];
- else $content = ctx()->copy();
- go(function () use ($action, $reply, $connection, $content) {
- set_coroutine_params($content);
- context()->setCache("action", $action);
- context()->setCache("reply", $reply);
- foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
- if (($v->action == "" || $v->action == $action) && !$v->with_result) {
- $c = $v->class;
- self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getQQ()]);
- if (context()->getCache("block_continue") === true) break;
- }
- }
- });
- }
-
- /**
- * @param $c
- * @param $method
- * @param array $class_construct
- * @param array $func_args
- * @param null $after_call
- * @return mixed|null
- * @throws AnnotationException
- * @throws Exception
- */
- public static function callWithMiddleware($c, $method, array $class_construct, array $func_args, $after_call = null) {
- $return_value = null;
- $plain_class = is_object($c) ? get_class($c) : $c;
- if (isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) {
- $middlewares = ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method];
- $before_result = true;
- $r = [];
- foreach ($middlewares as $k => $middleware) {
- if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
- $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middleware];
- $before = $middleware_obj["class"];
- $r[$k] = new $before();
- $r[$k]->class = is_object($c) ? get_class($c) : $c;
- $r[$k]->method = $method;
- if (isset($middleware_obj["before"])) {
- $before_result = call_user_func_array([$r[$k], $middleware_obj["before"]], $func_args);
- if ($before_result === false) break;
- }
- }
- if ($before_result) {
- try {
- if (is_object($c)) $class = $c;
- elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
- else $class = new $c($class_construct);
- $result = call_user_func_array([$class, $method], $func_args);
- if (is_callable($after_call))
- $return_value = call_user_func_array($after_call, [$result]);
- } catch (Exception $e) {
- for ($i = count($middlewares) - 1; $i >= 0; --$i) {
- $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
- if (!isset($middleware_obj["exceptions"])) continue;
- foreach ($middleware_obj["exceptions"] as $name => $method) {
- if ($e instanceof $name) {
- $r[$i]->$method($e);
- context()->setCache("block_continue", true);
- }
- }
- if (context()->getCache("block_continue") === true) return $return_value;
- }
- throw $e;
- }
- }
- for ($i = count($middlewares) - 1; $i >= 0; --$i) {
- $middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
- if (isset($middleware_obj["after"], $r[$i]))
- call_user_func_array([$r[$i], $middleware_obj["after"]], $func_args);
- }
- } else {
- if (is_object($c)) $class = $c;
- elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
- else $class = new $c($class_construct);
- $result = call_user_func_array([$class, $method], $func_args);
- if (is_callable($after_call))
- $return_value = call_user_func_array($after_call, [$result]);
- }
- return $return_value;
- }
-
- private static function startTick() {
- Console::debug("Starting " . count(ZMBuf::get("paused_tick", [])) . " custom tick function");
- foreach (ZMBuf::get("paused_tick", []) as $cid) {
- Co::resume($cid);
- }
-
- }
-}
diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php
new file mode 100644
index 00000000..3fea9f3e
--- /dev/null
+++ b/src/ZM/Event/EventManager.php
@@ -0,0 +1,62 @@
+generateAnnotationEvents();
+ self::$middlewares = $parser->getMiddlewares();
+ self::$middleware_map = $parser->getMiddlewareMap();
+ self::$req_mapping = $parser->getReqMapping();
+ }
+
+ /**
+ * 注册所有计时器给每个进程
+ */
+ public static function registerTimerTick() {
+ $dispatcher = new EventDispatcher(OnTick::class);
+ foreach (self::$events[OnTick::class] ?? [] as $vss) {
+ if (server()->worker_id !== $vss->worker_id) return;
+ //echo server()->worker_id.PHP_EOL;
+ $plain_class = $vss->class;
+ Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method);
+ Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) {
+ set_coroutine_params([]);
+ if (ZMAtomic::get("stop_signal")->get() != 0) {
+ Timer::clearAll();
+ return;
+ }
+ try {
+ $dispatcher->dispatchEvent($vss, null);
+ } catch (Exception $e) {
+ Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
+ } catch (Error $e) {
+ Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage());
+ echo Console::setColor($e->getTraceAsString(), "gray");
+ Console::error("Please check your code!");
+ }
+ });
+ }
+ }
+}
diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php
new file mode 100644
index 00000000..e1b75265
--- /dev/null
+++ b/src/ZM/Event/ServerEventHandler.php
@@ -0,0 +1,560 @@
+getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
+ } catch (Error $e) {
+ Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
+ }
+ });
+ }
+ Process::signal(SIGINT, function () use ($r) {
+ echo "\r";
+ Console::warning("Server interrupted by keyboard on Master.");
+ if ((Framework::$server->inotify ?? null) !== null)
+ /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
+ ZMUtil::stop();
+ });
+ if (Framework::$argv["watch"]) {
+ if (extension_loaded('inotify')) {
+ Console::warning("Enabled File watcher, do not use in production.");
+ /** @noinspection PhpUndefinedFieldInspection */
+ Framework::$server->inotify = $fd = inotify_init();
+ $this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd);
+ Event::add($fd, function () use ($fd) {
+ $r = inotify_read($fd);
+ var_dump($r);
+ ZMUtil::reload();
+ });
+ } else {
+ Console::warning("You have not loaded inotify extension.");
+ }
+ }
+ }
+
+ /**
+ * @SwooleHandler("shutdown")
+ */
+ public function onShutdown() {
+ Console::debug("正在关闭 Master 进程,pid=" . posix_getpid());
+ }
+
+ /**
+ * @SwooleHandler("WorkerStop")
+ * @param $server
+ * @param $worker_id
+ */
+ public function onWorkerStop(Server $server, $worker_id) {
+ Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
+ }
+
+ /**
+ * @SwooleHandler("WorkerStart")
+ * @param Server $server
+ * @param $worker_id
+ */
+ public function onWorkerStart(Server $server, $worker_id) {
+ //if (ZMBuf::atomic("stop_signal")->get() != 0) return;
+ Process::signal(SIGINT, function () use ($worker_id, $server) {
+ Console::debug("正在关闭 " . ($server->taskworker ? "Task" : "") . "Worker 进程 " . Console::setColor("#" . \server()->worker_id, "gold") . TermColor::frontColor256(59) . ", pid=" . posix_getpid());
+ server()->stop($worker_id);
+ });
+ unset(Context::$context[Co::getCid()]);
+ if ($server->taskworker === false) {
+ try {
+ register_shutdown_function(function () use ($server) {
+ $error = error_get_last();
+ if ($error["type"] != 0) {
+ Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
+ }
+ //DataProvider::saveBuffer();
+ /** @var Server $server */
+ if (server() === null) $server->shutdown();
+ else server()->shutdown();
+ });
+
+ Console::info("Worker #{$server->worker_id} 启动中");
+ Framework::$server = $server;
+ //ZMBuf::resetCache(); //清空变量缓存
+ //ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行
+ foreach ($server->connections as $v) {
+ $server->close($v);
+ }
+
+
+ // 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器
+ /*if ($server->worker_id === 0) {
+ global $terminal_id;
+ if ($terminal_id !== null)
+ go(function () {
+ while (true) {
+ $r = server()->process->exportSocket();
+ $result = $r->recv();
+ try {
+ if (!Terminal::executeCommand($result)) {
+ //if ($result == "stop" || $result == "reload" || $result == "r") {
+ //echo "Stopped.\n";
+ break;
+ }
+ } catch (Exception $e) {
+ Console::error($e->getMessage());
+ } catch (Error $e) {
+ Console::error($e->getMessage());
+ }
+ }
+ });
+ }*/
+ //TODO: 单独抽出来MySQL和Redis连接池
+ if (ZMConfig::get("global", "sql_config")["sql_host"] != "") {
+ if (SqlPoolStorage::$sql_pool !== null) {
+ SqlPoolStorage::$sql_pool->close();
+ SqlPoolStorage::$sql_pool = null;
+ }
+ Console::info("新建SQL连接池中");
+ ob_start();
+ phpinfo();
+ $str = ob_get_clean();
+ $str = explode("\n", $str);
+ foreach ($str as $k => $v) {
+ $v = trim($v);
+ if ($v == "") continue;
+ if (mb_strpos($v, "API Extensions") === false) continue;
+ if (mb_strpos($v, "pdo_mysql") === false) {
+ throw new DbException("未安装 mysqlnd php-mysql扩展。");
+ }
+ }
+ $sql = ZMConfig::get("global", "sql_config");
+ SqlPoolStorage::$sql_pool = new PDOPool((new PDOConfig())
+ ->withHost($sql["sql_host"])
+ ->withPort($sql["sql_port"])
+ // ->withUnixSocket('/tmp/mysql.sock')
+ ->withDbName($sql["sql_database"])
+ ->withCharset('utf8mb4')
+ ->withUsername($sql["sql_username"])
+ ->withPassword($sql["sql_password"])
+ ->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
+ );
+ DB::initTableList();
+ }
+
+ // 开箱即用的Redis
+ $redis = ZMConfig::get("global", "redis_config");
+ if($redis !== null && $redis["host"] != "") {
+ if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n");
+ else ZMRedisPool::init($redis);
+ }
+
+ $this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
+
+ //echo json_encode(debug_backtrace(), 128|256);
+ Console::success("Worker #" . $worker_id . " 已启动");
+ EventManager::registerTimerTick(); //启动计时器
+ //ZMBuf::unsetCache("wait_start");
+ set_coroutine_params(["server" => $server, "worker_id" => $worker_id]);
+ $dispatcher = new EventDispatcher(OnStart::class);
+ $dispatcher->setRuleFunction(function ($v) {
+ return server()->worker_id === $v->worker_id || $v->worker_id === -1;
+ });
+ $dispatcher->dispatchEvents($server, $worker_id);
+ Console::debug("@OnStart 执行完毕");
+ } catch (Exception $e) {
+ Console::error("Worker加载出错!停止服务!");
+ Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
+ ZMUtil::stop();
+ return;
+ } catch (Error $e) {
+ Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
+ Console::error("Maybe it caused by your own code if in your own Module directory.");
+ Console::log($e->getTraceAsString(), 'gray');
+ ZMUtil::stop();
+ }
+ } else {
+ // 这里是TaskWorker初始化的内容部分
+ try {
+ Framework::$server = $server;
+ $this->loadAnnotations();
+ Console::debug("TaskWorker #" . $server->worker_id . " 已启动");
+ } catch (Exception $e) {
+ Console::error("Worker加载出错!停止服务!");
+ Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
+ ZMUtil::stop();
+ return;
+ } catch (Error $e) {
+ Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
+ Console::error("Maybe it caused by your own code if in your own Module directory.");
+ Console::log($e->getTraceAsString(), 'gray');
+ ZMUtil::stop();
+ }
+ }
+ }
+
+ /**
+ * @SwooleHandler("message")
+ * @param $server
+ * @param Frame $frame
+ */
+ public function onMessage($server, Frame $frame) {
+
+ Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET);
+ unset(Context::$context[Co::getCid()]);
+ $conn = ManagerGM::get($frame->fd);
+ set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]);
+ $dispatcher = new EventDispatcher(OnSwooleEvent::class);
+ $dispatcher->setRuleFunction(function ($v) {
+ if ($v->getRule() == '') {
+ return strtolower($v->type) == 'message';
+ } else {
+ /** @noinspection PhpUnreachableStatementInspection
+ * @noinspection RedundantSuppression
+ */
+ if (strtolower($v->type) == 'message' && eval("return " . $v->getRule() . ";")) return true;
+ else return false;
+ }
+ });
+ try {
+ //$starttime = microtime(true);
+ $dispatcher->dispatchEvents($conn);
+ //Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!");
+ } catch (Exception $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg);
+ Console::trace();
+ } catch (Error $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg);
+ Console::trace();
+ }
+
+ }
+
+ /**
+ * @SwooleHandler("request")
+ * @param $request
+ * @param $response
+ */
+ public function onRequest($request, $response) {
+ $response = new Response($response);
+ unset(Context::$context[Co::getCid()]);
+ Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
+ set_coroutine_params(["request" => $request, "response" => $response]);
+
+ $dis = new EventDispatcher(OnSwooleEvent::class);
+ $dis->setRuleFunction(function ($v) {
+ if ($v->getRule() == '') {
+ return strtolower($v->type) == 'request';
+ } else {
+ /** @noinspection PhpUnreachableStatementInspection */
+ if (strtolower($v->type) == 'request' && eval("return " . $v->getRule() . ";")) return true;
+ else return false;
+ }
+ });
+
+ try {
+ $no_interrupt = $dis->dispatchEvents($request, $response);
+ if ($no_interrupt !== null) {
+ $result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params);
+ if ($result === true) {
+ ctx()->setCache("params", $params);
+ $dispatcher = new EventDispatcher(RequestMapping::class);
+ $div = new RequestMapping();
+ $div->route = $node["route"];
+ $div->params = $params;
+ $div->method = $node["method"];
+ $div->request_method = $node["request_method"];
+ $div->class = $node["class"];
+ //Console::success("正在执行路由:".$node["method"]);
+ $r = $dispatcher->dispatchEvent($div, null, $params, $request, $response);
+ if (is_string($r) && !$response->isEnd()) $response->end($r);
+ }
+ }
+ if (!$response->isEnd()) {
+ //Console::warning('返回了404');
+ HttpUtil::responseCodePage($response, 404);
+ }
+ } catch (Exception $e) {
+ $response->status(500);
+ Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] .
+ " [" . $response->getStatusCode() . "] " . $request->server["request_uri"]
+ );
+ if (!$response->isEnd()) {
+ if (ZMConfig::get("global", "debug_mode"))
+ $response->end("Internal server exception: " . $e->getMessage());
+ else
+ $response->end("Internal server error.");
+ }
+ Console::error("Internal server exception (500), caused by " . get_class($e).": ".$e->getMessage());
+ Console::log($e->getTraceAsString(), "gray");
+ } catch (Error $e) {
+ $response->status(500);
+ Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] .
+ " [" . $response->getStatusCode() . "] " . $request->server["request_uri"]
+ );
+ if (!$response->isEnd()) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ if (ZMConfig::get("global", "debug_mode"))
+ $response->end("Internal server error: " . $error_msg);
+ else
+ $response->end("Internal server error.");
+ }
+ Console::error("Internal server error (500), caused by " . get_class($e).": ".$e->getMessage());
+ Console::log($e->getTraceAsString(), "gray");
+ }
+ }
+
+ /**
+ * @SwooleHandler("open")
+ * @param $server
+ * @param Request $request
+ */
+ public function onOpen($server, Request $request) {
+ Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
+ unset(Context::$context[Co::getCid()]);
+ $type = strtolower($request->get["type"] ?? $request->header["x-client-role"] ?? "");
+ $type_conn = ManagerGM::getTypeClassName($type);
+ ManagerGM::pushConnect($request->fd, $type_conn);
+ $conn = ManagerGM::get($request->fd);
+ set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]);
+ $conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? "");
+
+ $dispatcher = new EventDispatcher(OnSwooleEvent::class);
+ $dispatcher->setRuleFunction(function ($v) {
+ if ($v->getRule() == '') {
+ return strtolower($v->type) == 'open';
+ } else {
+ /** @noinspection PhpUnreachableStatementInspection */
+ if (strtolower($v->type) == 'open' && eval("return " . $v->getRule() . ";")) return true;
+ else return false;
+ }
+ });
+ try {
+ if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
+ if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
+ LightCacheInside::set("connect", "conn_fd", $request->fd);
+ }
+ }
+ $dispatcher->dispatchEvents($conn);
+ } catch (Exception $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
+ Console::trace();
+ } catch (Error $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught Error " . get_class($e) . " when calling \"open\": " . $error_msg);
+ Console::trace();
+ }
+ //EventHandler::callSwooleEvent("open", $server, $request);
+ }
+
+ /**
+ * @SwooleHandler("close")
+ * @param $server
+ * @param $fd
+ */
+ public function onClose($server, $fd) {
+ unset(Context::$context[Co::getCid()]);
+ $conn = ManagerGM::get($fd);
+ if ($conn === null) return;
+ Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
+ set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]);
+
+
+ $dispatcher = new EventDispatcher(OnSwooleEvent::class);
+ $dispatcher->setRuleFunction(function ($v) {
+ if ($v->getRule() == '') {
+ return strtolower($v->type) == 'close';
+ } else {
+ /** @noinspection PhpUnreachableStatementInspection */
+ if (strtolower($v->type) == 'close' && eval("return " . $v->getRule() . ";")) return true;
+ else return false;
+ }
+ });
+ try {
+ if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
+ if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
+ LightCacheInside::set("connect", "conn_fd", -1);
+ }
+ }
+ $dispatcher->dispatchEvents($conn);
+ } catch (Exception $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg);
+ Console::trace();
+ } catch (Error $e) {
+ $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
+ Console::error("Uncaught Error " . get_class($e) . " when calling \"close\": " . $error_msg);
+ Console::trace();
+ }
+ ManagerGM::popConnect($fd);
+ }
+
+ /**
+ * @SwooleHandler("pipeMessage")
+ * @param $server
+ * @param $src_worker_id
+ * @param $data
+ */
+ public function onPipeMessage(Server $server, $src_worker_id, $data) {
+ //var_dump($data, $server->worker_id);
+ //unset(Context::$context[Co::getCid()]);
+ $data = json_decode($data, true);
+ switch ($data["action"] ?? '') {
+ case "resume_ws_message":
+ $obj = $data["data"];
+ Co::resume($obj["coroutine"]);
+ break;
+ case "stop":
+ Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器');
+ Timer::clearAll();
+ break;
+ case "terminate":
+ $server->stop();
+ break;
+ case 'echo':
+ Console::success('接收到来自 #' . $src_worker_id . ' 的消息');
+ break;
+ case 'send':
+ $server->sendMessage(json_encode(["action" => "echo"]), $data["target"]);
+ break;
+ default:
+ echo $data . PHP_EOL;
+ }
+ }
+
+ /**
+ * @SwooleHandler("task")
+ */
+ public function onTask() {
+ }
+
+ /**
+ * @throws ReflectionException
+ * @throws Exception
+ */
+ private function loadAnnotations() {
+ //加载phar包
+ /*Console::debug("加载外部phar包中");
+ $dir = DataProvider::getWorkingDir() . "/resources/package/";
+ if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll();
+ if (is_dir($dir)) {
+ $list = scandir($dir);
+ unset($list[0], $list[1]);
+ foreach ($list as $v) {
+ if (is_dir($dir . $v)) continue;
+ if (pathinfo($dir . $v, 4) == "phar") {
+ Console::debug("加载Phar: " . $dir . $v . " 中");
+ require_once($dir . $v);
+ }
+ }
+ }*/
+
+ //加载各个模块的注解类,以及反射
+ Console::debug("检索Module中");
+ $parser = new AnnotationParser();
+ $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module");
+ $parser->registerMods();
+ EventManager::loadEventByParser($parser); //加载事件
+
+ //加载自定义的全局函数
+ Console::debug("加载自定义上下文中...");
+ $context_class = ZMConfig::get("global", "context_class");
+ if (!is_a($context_class, ContextInterface::class, true)) {
+ throw new Exception("Context class must implemented from ContextInterface!");
+ }
+
+ //加载插件
+ $plugins = ZMConfig::get("global", "modules") ?? [];
+ if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false];
+
+ if ($plugins["onebot"]) {
+ $obj = new OnSwooleEvent();
+ $obj->class = QQBot::class;
+ $obj->method = 'handle';
+ $obj->type = 'message';
+ $obj->level = 99999;
+ $obj->rule = 'connectIsQQ()';
+ EventManager::addEvent(OnSwooleEvent::class, $obj);
+ if ($plugins["onebot"]["single_bot_mode"]) {
+ LightCacheInside::set("connect", "conn_fd", -1);
+ } else {
+ LightCacheInside::set("connect", "conn_fd", -2);
+ }
+ }
+
+ //TODO: 编写加载外部插件的方式
+ $this->loadExternalModules($plugins);
+ }
+
+ private function addWatcher($maindir, $fd) {
+ $dir = scandir($maindir);
+ unset($dir[0], $dir[1]);
+ foreach ($dir as $subdir) {
+ if (is_dir($maindir . "/" . $subdir)) {
+ Console::debug("添加监听目录:" . $maindir . "/" . $subdir);
+ inotify_add_watch($fd, $maindir . "/" . $subdir, IN_ATTRIB | IN_ISDIR);
+ $this->addWatcher($maindir . "/" . $subdir, $fd);
+ }
+ }
+ }
+
+ private function loadExternalModules($plugins) {
+ foreach ($plugins as $k => $v) {
+ if ($k == "onebot") continue;
+
+ }
+ }
+}
diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php
deleted file mode 100644
index 0c6f9298..00000000
--- a/src/ZM/Event/Swoole/MessageEvent.php
+++ /dev/null
@@ -1,103 +0,0 @@
-server = $server;
- $this->frame = $frame;
- }
-
- /**
- * @inheritDoc
- */
- public function onActivate() {
- ZMUtil::checkWait();
- $conn = ConnectionManager::get(context()->getFrame()->fd);
- try {
- if ($conn->getType() == "qq") {
- $data = json_decode(context()->getFrame()->data, true);
- if (isset($data["post_type"])) {
- set_coroutine_params(["data" => $data, "connection" => $conn]);
- ctx()->setCache("level", 0);
- Console::debug("Calling CQ Event from fd=" . $conn->fd);
- EventHandler::callCQEvent($data, ConnectionManager::get(context()->getFrame()->fd), 0);
- } else{
- set_coroutine_params(["connection" => $conn]);
- EventHandler::callCQResponse($data);
- }
- }
- foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
- if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["server" => $this->server, "frame" => $this->frame, "connection" => $conn],
- [$conn]
- );
- if (context()->getCache("block_continue") === true) break;
- }
- }
- } catch (Exception $e) {
- Console::warning("Websocket message event exception: " . (($cs = $e->getMessage()) == "" ? get_class($e) : $cs));
- Console::warning("In ". $e->getFile() . " at line ".$e->getLine());
- }
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function onAfter() {
- foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
- if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) {
- $conn = ConnectionManager::get($this->frame->fd);
- $c = $v->class;
- /** @var ModBase $class */
- $class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
- call_user_func_array([$class, $v->method], []);
- if (context()->getCache("block_continue") === true) break;
- }
- }
- return $this;
- }
-
- private function parseSwooleRule($v) {
- switch (explode(":", $v->rule)[0]) {
- case "connectType": //websocket连接类型
- if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->frame->fd));
- break;
- case "dataEqual": //handle websocket message事件时才能用
- if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data);
- break;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/Swoole/RequestEvent.php b/src/ZM/Event/Swoole/RequestEvent.php
deleted file mode 100644
index 45e59bf4..00000000
--- a/src/ZM/Event/Swoole/RequestEvent.php
+++ /dev/null
@@ -1,198 +0,0 @@
-request = $request;
- $this->response = $response;
- }
-
- /**
- * @return $this|SwooleEvent
- * @throws Exception
- */
- public function onActivate()
- {
- ZMUtil::checkWait();
- foreach (ZMBuf::globals("http_header") as $k => $v) {
- $this->response->setHeader($k, $v);
- }
- $uri = $this->request->server["request_uri"];
- Console::verbose($this->request->server["remote_addr"] . " request " . $uri);
- $uri = explode("/", $uri);
- $uri = array_diff($uri, ["..", "", "."]);
- $node = ZMBuf::$req_mapping;
- $params = [];
- while (true) {
- $r = array_shift($uri);
- if ($r === null) {
- if ($node == ZMBuf::$req_mapping) goto statics;
- else break;
- }
- if (($cnt = count($node["son"] ?? [])) == 1) {
- if (isset($node["param_route"])) {
- foreach ($node["son"] as $k => $v) {
- if ($v["id"] == $node["param_route"]) {
- $node = $v;
- $params[mb_substr($v["name"], 1, -1)] = $r;
- continue 2;
- }
- }
- } elseif ($node["son"][0]["name"] == $r) {
- $node = $node["son"][0];
- continue;
- }
- } elseif ($cnt >= 1) {
- if (isset($node["param_route"])) {
- foreach ($node["son"] as $k => $v) {
- if ($v["id"] == $node["param_route"]) {
- $node = $v;
- $params[mb_substr($v["name"], 1, -1)] = $r;
- continue 2;
- }
- }
- }
- foreach ($node["son"] as $k => $v) {
- if ($v["name"] == $r) {
- $node = $v;
- continue 2;
- }
- }
- }
- statics:
- if (ZMBuf::globals("static_file_server")["status"]) {
- $base_dir = ZMBuf::globals("static_file_server")["document_root"];
- $base_index = ZMBuf::globals("static_file_server")["document_index"];
- $uri = $this->request->server["request_uri"];
- $path = realpath($base_dir . urldecode($uri));
- if ($path !== false) {
- if (is_dir($path) && mb_substr($uri, -1, 1) != "/") {
- $this->response->redirect($uri . "/", 301);
- $this->response->end();
- return $this;
- }
- if (is_dir($path)) $path = $path . '/';
- $work = realpath(DataProvider::getWorkingDir()) . '/';
- if (strpos($path, $work) !== 0) {
- $this->responseStatus(403);
- return $this;
- }
- if (is_dir($path)) {
- foreach ($base_index as $vp) {
- if (is_file($path . $vp)) {
- Console::info("[200] " . $uri . " (static)");
- $exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown");
- $this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
- $this->response->end(file_get_contents($path . $vp));
- return $this;
- }
- }
- } elseif (is_file($path)) {
- Console::info("[200] " . $uri . " (static)");
- $exp = strtolower(pathinfo($path)['extension'] ?? "unknown");
- $this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
- $this->response->end(file_get_contents($path));
- return $this;
- }
- }
- }
- $this->response->status(404);
- $this->response->end(ZMUtil::getHttpCodePage(404));
- return $this;
- }
-
- context()->setCache("params", $params);
-
- if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面
- $c_name = $node["class"];
- EventHandler::callWithMiddleware(
- $c_name,
- $node["method"],
- ["request" => $this->request, "response" => &$this->response, "params" => $params],
- [$params],
- function ($result) {
- if (is_string($result) && !$this->response->isEnd()) $this->response->end($result);
- if ($this->response->isEnd()) context()->setCache("block_continue", true);
- }
- );
- }
- foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
- if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
- $c = $v->class;
- EventHandler::callWithMiddleware($c, $v->method, ["request" => $this->request, "response" => $this->response], []);
- if (context()->getCache("block_continue") === true) break;
- }
- }
-
- if (!$this->response->isEnd()) {
- $this->response->status(404);
- $this->response->end(ZMUtil::getHttpCodePage(404));
- }
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function onAfter()
- {
- foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
- if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
- $c = $v->class;
- $class = new $c(["request" => $this->request, "response" => $this->response]);
- call_user_func_array([$class, $v->method], []);
- if ($class->block_continue) break;
- }
- }
- return $this;
- }
-
- private function responseStatus(int $int)
- {
- $this->response->status($int);
- $this->response->end();
- }
-
- private function parseSwooleRule($v)
- {
- switch (explode(":", $v->rule)[0]) {
- case "containsGet":
- case "containsPost":
- if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request);
- break;
- case "containsJson":
- $content = $this->request->rawContent();
- $content = json_decode($content, true);
- if ($content === null) return false;
- if ($v->callback instanceof Closure) return call_user_func($v->callback, $content);
- break;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/Swoole/SwooleEvent.php b/src/ZM/Event/Swoole/SwooleEvent.php
deleted file mode 100644
index 73b1d54e..00000000
--- a/src/ZM/Event/Swoole/SwooleEvent.php
+++ /dev/null
@@ -1,20 +0,0 @@
-server = $server;
- $this->fd = $fd;
- }
-
- /**
- * @inheritDoc
- * @throws AnnotationException
- */
- public function onActivate() {
- ZMUtil::checkWait();
- set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ConnectionManager::get($this->fd)]);
- foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
- if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) {
- $c = $v->class;
- EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
- if(context()->getCache("block_continue") === true) break;
- }
- }
- ConnectionManager::close($this->fd);
- return $this;
- }
-
- /**
- * @inheritDoc
- * @throws AnnotationException
- */
- public function onAfter() {
- foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
- if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) {
- $c = $v->class;
- EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
- if(context()->getCache("block_continue") === true) break;
- }
- }
- return $this;
- }
-
- private function parseSwooleRule($v) {
- switch (explode(":", $v->rule)[0]) {
- case "connectType": //websocket连接类型
- if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->fd));
- break;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/Swoole/WSOpenEvent.php b/src/ZM/Event/Swoole/WSOpenEvent.php
deleted file mode 100644
index 833749e1..00000000
--- a/src/ZM/Event/Swoole/WSOpenEvent.php
+++ /dev/null
@@ -1,109 +0,0 @@
-server = $server;
- $this->request = $request;
- }
-
- /**
- * @inheritDoc
- * @throws AnnotationException
- */
- public function onActivate() {
- ZMUtil::checkWait();
- $type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? "");
- $type_conn = ConnectionManager::getTypeClassName($type);
- if ($type_conn == CQConnection::class) {
- $qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? "";
- $self_token = ZMBuf::globals("access_token") ?? "";
- if(isset($this->request->header["authorization"])) {
- Console::debug($this->request->header["authorization"]);
- }
- $remote_token = $this->request->get["token"] ?? (isset($this->request->header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : "");
- if ($qq != "" && ($self_token == $remote_token)) $this->conn = new CQConnection($this->server, $this->request->fd, $qq);
- else {
- $this->conn = new UnknownConnection($this->server, $this->request->fd);
- Console::warning("connection of CQ has invalid QQ or token!");
- Console::debug("Remote token: ".$remote_token);
- }
- } else {
- $this->conn = new $type_conn($this->server, $this->request->fd);
- }
- ZMBuf::$connect[$this->request->fd] = $this->conn;
- set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]);
- foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
- if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
- $c = $v->class;
- EventHandler::callWithMiddleware(
- $c,
- $v->method,
- ["server" => $this->server, "request" => $this->request, "connection" => $this->conn],
- [$this->conn]
- );
- if (context()->getCache("block_continue") === true) break;
- }
- }
- return $this;
- }
-
- /**
- * @inheritDoc
- */
- public function onAfter() {
- if (!$this->conn->exists()) return $this;
- foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
- if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
- /** @var ModBase $class */
- $class = new $v["class"](["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN);
- call_user_func_array([$class, $v["method"]], [$this->conn]);
- if (context()->getCache("block_continue") === true) break;
- }
- }
- return $this;
- }
-
- private function parseSwooleRule($v) {
- switch (explode(":", $v->rule)[0]) {
- case "connectType": //websocket连接类型
- if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->conn);
- break;
- }
- return true;
- }
-}
diff --git a/src/ZM/Event/Swoole/WorkerStartEvent.php b/src/ZM/Event/Swoole/WorkerStartEvent.php
deleted file mode 100644
index 5eb11223..00000000
--- a/src/ZM/Event/Swoole/WorkerStartEvent.php
+++ /dev/null
@@ -1,212 +0,0 @@
-server = $server;
- $this->worker_id = $worker_id;
- }
-
- /**
- * @return WorkerStartEvent
- * @throws AnnotationException
- * @throws ReflectionException
- * @throws DbException
- */
- public function onActivate(): WorkerStartEvent {
- Console::info("Worker启动中");
- ZMBuf::$server = $this->server;
- Console::listenConsole(); //这个方法只能在这里调用,且如果worker_num不为1的话,此功能不可用
-
- Process::signal(SIGINT, function () {
- Console::warning("Server interrupted by keyboard.");
- ZMUtil::stop(true);
- });
- ZMBuf::resetCache(); //清空变量缓存
- ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行
- $this->resetConnections();//释放所有与framework的连接
-
- //设置炸毛buf中储存的对象
- ZMBuf::$globals = new GlobalConfig();
- ZMBuf::$config = [];
- $file = scandir(DataProvider::getWorkingDir() . '/config/');
- unset($file[0], $file[1]);
- foreach ($file as $k => $v) {
- if ($v == "global.php") continue;
- $name = explode(".", $v);
- if (($prefix = end($name)) == "json") {
- ZMBuf::$config[$name[0]] = json_decode(Co::readFile(DataProvider::getWorkingDir() . '/config/' . $v), true);
- Console::info("已读取配置文件:" . $v);
- } elseif ($prefix == "php") {
- ZMBuf::$config[$name[0]] = include_once DataProvider::getWorkingDir() . '/config/' . $v;
- if (is_array(ZMBuf::$config[$name[0]]))
- Console::info("已读取配置文件:" . $v);
- }
- }
- if (ZMBuf::globals("sql_config")["sql_host"] != "") {
- Console::info("新建SQL连接池中");
- ob_start();
- phpinfo();
- $str = ob_get_clean();
- $str = explode("\n", $str);
- foreach($str as $k => $v) {
- $v = trim($v);
- if($v == "") continue;
- if(mb_strpos($v, "API Extensions") === false) continue;
- if(mb_strpos($v, "pdo_mysql") === false) {
- throw new DbException("未安装 mysqlnd php-mysql扩展。");
- }
- }
- $sql = ZMBuf::globals("sql_config");
- ZMBuf::$sql_pool = new PDOPool((new PDOConfig())
- ->withHost($sql["sql_host"])
- ->withPort($sql["sql_port"])
- // ->withUnixSocket('/tmp/mysql.sock')
- ->withDbName($sql["sql_database"])
- ->withCharset('utf8mb4')
- ->withUsername($sql["sql_username"])
- ->withPassword($sql["sql_password"])
- ->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
- );
- DB::initTableList();
- }
-
- ZMBuf::$atomics['reload_time']->add(1);
-
- Console::info("监听console输入");
-
- $this->setAutosaveTimer(ZMBuf::globals("auto_save_interval"));
- $this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等
- return $this;
- }
-
- /**
- * @return WorkerStartEvent
- * @throws AnnotationException
- */
- public function onAfter(): WorkerStartEvent {
- foreach (ZMBuf::get("wait_start") as $v) {
- Coroutine::resume($v);
- }
- ZMBuf::unsetCache("wait_start");
- set_coroutine_params(["server" => $this->server, "worker_id" => $this->worker_id]);
-
- foreach (ZMBuf::$events[OnStart::class] ?? [] as $v) {
- $class_name = $v->class;
- Console::debug("正在调用启动时函数: " . $class_name . " -> " . $v->method);
- EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []);
- }
- foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
- /** @var AnnotationBase $v */
- if (strtolower($v->type) == "workerstart") {
- $class_name = $v->class;
- Console::debug("正在调用启动时函数after: " . $class_name . " -> " . $v->method);
- EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []);
- if (context()->getCache("block_continue") === true) break;
- }
- }
- Console::debug("调用完毕!");
- return $this;
- }
-
- private function resetConnections() {
- foreach ($this->server->connections as $v) {
- $this->server->close($v);
- }
- if (ZMBuf::$sql_pool !== null) {
- ZMBuf::$sql_pool->close();
- ZMBuf::$sql_pool = null;
- }
- }
-
- /**
- * @throws AnnotationException
- * @throws ReflectionException
- * @throws Exception
- */
- private function loadAllClass() {
- //加载phar包
- Console::info("加载外部phar包中");
- $dir = DataProvider::getWorkingDir() . "/resources/package/";
- if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll();
- if (is_dir($dir)) {
- $list = scandir($dir);
- unset($list[0], $list[1]);
- foreach ($list as $v) {
- if (is_dir($dir . $v)) continue;
- if (pathinfo($dir . $v, 4) == "phar") {
- Console::verbose("加载Phar: " . $dir . $v . " 中");
- require_once($dir . $v);
- }
- }
- }
- //加载composer类
- //remove stupid duplicate code
-
- //加载各个模块的注解类,以及反射
- Console::info("检索Module中");
- AnnotationParser::registerMods();
-
- //加载Custom目录下的自定义的内部类
- ConnectionManager::registerCustomClass();
-
- //加载自定义的全局函数
- Console::debug("加载自定义的全局函数中");
- if (file_exists(DataProvider::getWorkingDir() . "/src/Custom/global_function.php"))
- require_once DataProvider::getWorkingDir() . "/src/Custom/global_function.php";
- $this->afterCheck();
- }
-
- private function setAutosaveTimer($globals) {
- DataProvider::$buffer_list = [];
- zm_timer_tick($globals * 1000, function () {
- DataProvider::saveBuffer();
- });
- }
-
- /**
- * @throws Exception
- */
- private function afterCheck() {
- $context_class = ZMBuf::globals("context_class");
- if (!is_a($context_class, ContextInterface::class, true)) {
- throw new Exception("Context class must implemented from ContextInterface!");
- }
- }
-}
diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php
index f5575ce2..007d9ffe 100644
--- a/src/ZM/Exception/DbException.php
+++ b/src/ZM/Exception/DbException.php
@@ -4,9 +4,7 @@
namespace ZM\Exception;
-use Exception;
-
-class DbException extends Exception
+class DbException extends ZMException
{
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php
new file mode 100644
index 00000000..cb97d954
--- /dev/null
+++ b/src/ZM/Exception/InterruptException.php
@@ -0,0 +1,17 @@
+return_var = $return_var;
+ }
+}
diff --git a/src/ZM/Exception/InvalidArgumentException.php b/src/ZM/Exception/InvalidArgumentException.php
index eb4b47a5..0052a29e 100644
--- a/src/ZM/Exception/InvalidArgumentException.php
+++ b/src/ZM/Exception/InvalidArgumentException.php
@@ -4,9 +4,7 @@
namespace ZM\Exception;
-use Exception;
-
-class InvalidArgumentException extends Exception
+class InvalidArgumentException extends ZMException
{
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php
new file mode 100644
index 00000000..6f56e830
--- /dev/null
+++ b/src/ZM/Exception/NotInitializedException.php
@@ -0,0 +1,10 @@
+module = $module;
}
-}
\ No newline at end of file
+}
diff --git a/src/ZM/Exception/ZMException.php b/src/ZM/Exception/ZMException.php
new file mode 100644
index 00000000..01d8bf96
--- /dev/null
+++ b/src/ZM/Exception/ZMException.php
@@ -0,0 +1,12 @@
+getTtyWidth();
+
+ self::$argv = $args;
+
+ //定义常量
+ include_once "global_defines.php";
+
+ ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
+ ZMConfig::setEnv($args["env"] ?? "");
+ if (ZMConfig::get("global") === false) {
+ die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
+ }
+ ZMAtomic::init();
+ try {
+ ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [
+ [
+ "key" => "connect_id",
+ "type" => "string",
+ "size" => 30
+ ],
+ [
+ "key" => "type",
+ "type" => "int"
+ ]
+ ]);
+ } catch (ConnectionManager\TableException $e) {
+ die($e->getMessage());
+ }
+ try {
+ self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
+ $this->server_set = ZMConfig::get("global", "swoole");
+ Console::init(
+ ZMConfig::get("global", "info_level") ?? 2,
+ self::$server,
+ $args["log-theme"] ?? "default",
+ ($o = ZMConfig::get("console_color")) === false ? [] : $o
+ );
+
+ $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
+ date_default_timezone_set($timezone);
+
+ $this->parseCliArgs(self::$argv);
+
+ $out = [
+ "host" => ZMConfig::get("global", "host"),
+ "port" => ZMConfig::get("global", "port"),
+ "log_level" => Console::getLevel(),
+ "version" => ZM_VERSION,
+ "config" => $args["env"] === null ? 'global.php' : $args["env"]
+ ];
+ if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
+ $out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"];
+ }
+ if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) {
+ $out["worker_num"] = $num;
+ }
+ $out["working_dir"] = DataProvider::getWorkingDir();
+ Console::printProps($out, $tty_width);
+
+ self::$server->set($this->server_set);
+ if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
+ $motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
+ } else {
+ $motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
+ }
+ $motd = explode("\n", $motd);
+ foreach ($motd as $k => $v) {
+ $motd[$k] = substr($v, 0, $tty_width);
+ }
+ $motd = implode("\n", $motd);
+ echo $motd;
+ global $asd;
+ $asd = get_included_files();
+ // 注册 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
+ ];
+ LightCache::init($r);
+ LightCacheInside::init();
+ SpinLock::init($r["size"]);
+ } catch (Exception $e) {
+ Console::error("Framework初始化出现错误,请检查!");
+ Console::error($e->getMessage());
+ die;
+ }
+ }
+
+ public function start() {
+ self::$server->start();
+ }
+
+ /**
+ * 从全局配置文件里读取注入系统事件的类
+ * @throws ReflectionException
+ * @throws ReflectionException
+ */
+ private function registerServerEvents() {
+ $all_event_class = ZMConfig::get("global", "server_event_handler_class") ?? [];
+ if (!in_array(ServerEventHandler::class, $all_event_class)) {
+ $all_event_class[] = ServerEventHandler::class;
+ }
+ $event_list = [];
+ foreach ($all_event_class as $v) {
+ $reader = new AnnotationReader();
+ $reflection_class = new ReflectionClass($v);
+ $methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
+ foreach ($methods as $vs) {
+ $method_annotations = $reader->getMethodAnnotations($vs);
+ if ($method_annotations != []) {
+ $annotation = $method_annotations[0];
+ if ($annotation instanceof SwooleHandler) {
+ $annotation->class = $v;
+ $annotation->method = $vs->getName();
+ $event_list[strtolower($annotation->event)] = $annotation;
+ } elseif ($annotation instanceof OnSetup) {
+ $annotation->class = $v;
+ $annotation->method = $vs->getName();
+ $c = new $v();
+ $m = $annotation->method;
+ $c->$m();
+ }
+ }
+ }
+ }
+ foreach ($event_list as $k => $v) {
+ self::$server->on($k, function (...$param) use ($v) {
+ $c = ZMUtil::getModInstance($v->class);
+ $m = $v->method;
+ $c->$m(...$param);
+ });
+ }
+ }
+
+ /**
+ * 解析命令行的 $argv 参数们
+ * @param $args
+ * @throws Exception
+ */
+ private function parseCliArgs($args) {
+ $coroutine_mode = true;
+ global $terminal_id;
+ $terminal_id = call_user_func(function () {
+ try {
+ $data = random_bytes(16);
+ } catch (Exception $e) {
+ return "";
+ }
+ $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
+ $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
+ return strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)));
+ });
+ foreach ($args as $x => $y) {
+ switch ($x) {
+ case 'disable-coroutine':
+ if ($y) {
+ $coroutine_mode = false;
+ }
+ break;
+ case 'debug-mode':
+ if ($y || ZMConfig::get("global", "debug_mode")) {
+ $coroutine_mode = false;
+ $terminal_id = null;
+ Console::warning("You are in debug mode, do not use in production!");
+ }
+ break;
+ case 'daemon':
+ if ($y) {
+ $this->server_set["daemonize"] = 1;
+ Console::log("已启用守护进程,输出重定向到 " . $this->server_set["log_file"]);
+ $terminal_id = null;
+ }
+ break;
+ case 'disable-console-input':
+ if ($y) $terminal_id = null;
+ break;
+ case 'log-error':
+ if ($y) Console::setLevel(0);
+ break;
+ case 'log-warning':
+ if ($y) Console::setLevel(1);
+ break;
+ case 'log-info':
+ if ($y) Console::setLevel(2);
+ break;
+ case 'log-verbose':
+ if ($y) Console::setLevel(3);
+ break;
+ case 'log-debug':
+ if ($y) Console::setLevel(4);
+ break;
+ case 'log-theme':
+ if ($y !== null) {
+ Console::$theme = $y;
+ }
+ break;
+ }
+ }
+ if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
+ }
+
+ public static function getTtyWidth() {
+ return explode(" ", trim(exec("stty size")))[1];
+ }
+}
diff --git a/src/ZM/Http/MiddlewareInterface.php b/src/ZM/Http/MiddlewareInterface.php
index 79c00865..a0ed6969 100644
--- a/src/ZM/Http/MiddlewareInterface.php
+++ b/src/ZM/Http/MiddlewareInterface.php
@@ -6,5 +6,5 @@ namespace ZM\Http;
interface MiddlewareInterface
{
- public function getName();
+
}
diff --git a/src/ZM/Http/Response.php b/src/ZM/Http/Response.php
index 061a597b..a304e88d 100644
--- a/src/ZM/Http/Response.php
+++ b/src/ZM/Http/Response.php
@@ -4,6 +4,8 @@
namespace ZM\Http;
+use ZM\Console\Console;
+
class Response
{
@@ -184,6 +186,7 @@ class Response
* @return mixed
*/
public function redirect($location, $http_code = null) {
+ $this->is_end = true;
return $this->response->redirect($location, $http_code);
}
diff --git a/src/ZM/Http/StaticFileHandler.php b/src/ZM/Http/StaticFileHandler.php
index fec943bc..e31407d0 100644
--- a/src/ZM/Http/StaticFileHandler.php
+++ b/src/ZM/Http/StaticFileHandler.php
@@ -4,9 +4,9 @@
namespace ZM\Http;
-use Framework\Console;
-use Framework\ZMBuf;
-use ZM\Utils\ZMUtil;
+use ZM\Config\ZMConfig;
+use ZM\Console\Console;
+use ZM\Utils\HttpUtil;
class StaticFileHandler
{
@@ -22,14 +22,14 @@ class StaticFileHandler
} else {
if(is_file($full_path)) {
$exp = strtolower(pathinfo($full_path)['extension'] ?? "unknown");
- $response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
+ $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
$response->end(file_get_contents($full_path));
return true;
}
}
}
$response->status(404);
- $response->end(ZMUtil::getHttpCodePage(404));
+ $response->end(HttpUtil::getHttpCodePage(404));
return true;
}
}
diff --git a/src/ZM/ModBase.php b/src/ZM/ModBase.php
deleted file mode 100644
index a6097a89..00000000
--- a/src/ZM/ModBase.php
+++ /dev/null
@@ -1,170 +0,0 @@
-server = $param0["server"];
- if (isset($param0["frame"])) $this->frame = $param0["frame"];
- if (isset($param0["data"])) $this->data = $param0["data"];
- if (isset($param0["request"])) $this->request = $param0["request"];
- if (isset($param0["response"])) $this->response = $param0["response"];
- if (isset($param0["fd"])) $this->fd = $param0["fd"];
- if (isset($param0["worker_id"])) $this->worker_id = $param0["worker_id"];
- if (isset($param0["connection"])) $this->connection = $param0["connection"];
- $this->handle_type = $handle_type;
- }
-
- /**
- * only can used by cq->message event function
- * @param $msg
- * @param bool $yield
- * @return mixed
- */
- public function reply($msg, $yield = false) {
- switch ($this->data["message_type"]) {
- case "group":
- case "private":
- case "discuss":
- return CQAPI::quick_reply($this->connection, $this->data, $msg, $yield);
- }
- return false;
- }
-
- public function finalReply($msg, $yield = false) {
- $this->setBlock();
- if ($msg == "") return true;
- return $this->reply($msg, $yield);
- }
-
- /**
- * @param string $prompt
- * @param int $timeout
- * @param string $timeout_prompt
- * @return string
- * @throws InvalidArgumentException
- * @throws WaitTimeoutException
- */
- public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
- if ($prompt != "") $this->reply($prompt);
- if (!isset($this->data["user_id"], $this->data["message"], $this->data["self_id"]))
- throw new InvalidArgumentException("协程等待参数缺失");
- $cid = Co::getuid();
- $api_id = ZMBuf::$atomics["wait_msg_id"]->get();
- ZMBuf::$atomics["wait_msg_id"]->add(1);
- $hang = [
- "coroutine" => $cid,
- "user_id" => $this->data["user_id"],
- "message" => $this->data["message"],
- "self_id" => $this->data["self_id"],
- "message_type" => $this->data["message_type"],
- "result" => null
- ];
- if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
- $hang[$hang["message_type"] . "_id"] = $this->data[$this->data["message_type"] . "_id"];
- }
- ZMBuf::appendKey("wait_api", $api_id, $hang);
- $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) {
- $r = ZMBuf::get("wait_api")[$api_id] ?? null;
- if ($r !== null) {
- Co::resume($r["coroutine"]);
- }
- });
-
- Co::suspend();
- $sess = ZMBuf::get("wait_api")[$api_id];
- ZMBuf::unsetByValue("wait_api", $api_id);
- $result = $sess["result"];
- if (isset($id)) swoole_timer_clear($id);
- if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
- return $result;
- }
-
- /**
- * @param $arg
- * @param $mode
- * @param $prompt_msg
- * @return mixed|string
- * @throws InvalidArgumentException
- * @throws WaitTimeoutException
- */
- public function getArgs(&$arg, $mode, $prompt_msg) {
- switch ($mode) {
- case ZM_MATCH_ALL:
- $p = $arg;
- array_shift($p);
- return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p));
- case ZM_MATCH_NUMBER:
- foreach ($arg as $k => $v) {
- if (is_numeric($v)) {
- array_splice($arg, $k, 1);
- return $v;
- }
- }
- return $this->waitMessage($prompt_msg);
- case ZM_MATCH_FIRST:
- if (isset($arg[1])) {
- $a = $arg[1];
- array_splice($arg, 1, 1);
- return $a;
- } else {
- return $this->waitMessage($prompt_msg);
- }
- }
- throw new InvalidArgumentException();
- }
-
- public function getMessage() { return $this->data["message"] ?? null; }
-
- public function getUserId() { return $this->data["user_id"] ?? null; }
-
- public function getGroupId() { return $this->data["group_id"] ?? null; }
-
- public function getMessageType() { return $this->data["message_type"] ?? null; }
-
- public function getRobotId() { return $this->data["self_id"]; }
-
- public function getConnection() { return $this->connection; }
-
- public function setBlock($result = true) { context()->setCache("block_continue", $result); }
-}
diff --git a/src/ZM/ModHandleType.php b/src/ZM/ModHandleType.php
deleted file mode 100644
index 1082dfc7..00000000
--- a/src/ZM/ModHandleType.php
+++ /dev/null
@@ -1,20 +0,0 @@
-getFrame()->data, true);
+ if (isset($data["post_type"])) {
+ //echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
+ set_coroutine_params(["data" => $data]);
+ ctx()->setCache("level", 0);
+ //Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
+ $this->dispatchBeforeEvents($data); // >= 200 的level before在这里执行
+ if (CoMessage::resumeByWS()) {
+ EventDispatcher::interrupt();
+ }
+ //Console::warning("最上数据包:".json_encode($data));
+ $this->dispatchEvents($data);
+ } else {
+ $this->dispatchAPIResponse($data);
+ }
+ } /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
+ $e->module->finalReply($e->getMessage());
+ }
+ }
+
+ public function dispatchBeforeEvents($data) {
+ $before = new EventDispatcher(CQBefore::class);
+ $before->setRuleFunction(function ($v) use ($data) {
+ if ($v->level < 200) EventDispatcher::interrupt();
+ elseif ($v->cq_event != $data["post_type"]) return false;
+ return true;
+ });
+ $before->setReturnFunction(function ($result) {
+ if (!$result) EventDispatcher::interrupt();
+ });
+ $before->dispatchEvents($data);
+ }
+
+ private function dispatchEvents($data) {
+ //Console::warning("最xia数据包:".json_encode($data));
+ switch ($data["post_type"]) {
+ case "message":
+ $word = explodeMsg(str_replace("\r", "", context()->getMessage()));
+ if (count(explode("\n", $word[0])) >= 2) {
+ $enter = explode("\n", context()->getMessage());
+ $first = split_explode(" ", array_shift($enter));
+ $word = array_merge($first, $enter);
+ foreach ($word as $k => $v) {
+ $word[$k] = trim($word[$k]);
+ }
+ }
+
+ //分发CQCommand事件
+ $dispatcher = new EventDispatcher(CQCommand::class);
+ $dispatcher->setRuleFunction(function (CQCommand $v) use ($word) {
+ if(array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) return false;
+ elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) &&
+ ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) &&
+ ($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType()))
+ ) {
+ if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
+ array_shift($word);
+ ctx()->setCache("match", $word);
+ return true;
+ } elseif ($v->start_with != "" && mb_strpos(ctx()->getMessage(), $v->start_with) === 0) {
+ ctx()->setCache("match", [mb_substr(ctx()->getMessage(), mb_strlen($v->start_with))]);
+ return true;
+ } elseif ($v->end_with != "" && strlen(ctx()->getMessage()) == (strripos(ctx()->getMessage(), $v->end_with) + strlen($v->end_with))) {
+ ctx()->setCache("match", [substr(ctx()->getMessage(), 0, strripos(ctx()->getMessage(), $v->end_with))]);
+ return true;
+ } elseif ($v->keyword != "" && mb_strpos(ctx()->getMessage(), $v->keyword) !== false) {
+ ctx()->setCache("match", explode($v->keyword, ctx()->getMessage()));
+ return true;
+ }elseif ($v->pattern != "") {
+ $match = matchArgs($v->pattern, ctx()->getMessage());
+ if($match !== false) {
+ ctx()->setCache("match", $match);
+ return true;
+ }
+ } elseif ($v->regex != "") {
+ if(preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) {
+ ctx()->setCache("match", $word2);
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ $dispatcher->setReturnFunction(function ($result) {
+ if (is_string($result)) ctx()->reply($result);
+ EventDispatcher::interrupt();
+ });
+ $r = $dispatcher->dispatchEvents();
+ if ($r === null) EventDispatcher::interrupt();
+
+ //分发CQMessage事件
+ $msg_dispatcher = new EventDispatcher(CQMessage::class);
+ $msg_dispatcher->setRuleFunction(function ($v) {
+ return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) &&
+ ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
+ ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
+ ($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) &&
+ ($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]));
+ });
+ $msg_dispatcher->setReturnFunction(function ($result) {
+ if (is_string($result)) ctx()->reply($result);
+ });
+ $msg_dispatcher->dispatchEvents(ctx()->getMessage());
+ return;
+ case "meta_event":
+ //Console::success("当前数据包:".json_encode(ctx()->getData()));
+ $dispatcher = new EventDispatcher(CQMetaEvent::class);
+ $dispatcher->setRuleFunction(function (CQMetaEvent $v) {
+ return ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == ctx()->getData()["meta_event_type"])) &&
+ ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == (ctx()->getData()["sub_type"] ?? '')));
+ });
+ //eval(BP);
+ $dispatcher->dispatchEvents(ctx()->getData());
+ return;
+ case "notice":
+ $dispatcher = new EventDispatcher(CQNotice::class);
+ $dispatcher->setRuleFunction(function (CQNotice $v) {
+ return
+ ($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == ctx()->getData()["notice_type"])) &&
+ ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()["sub_type"])) &&
+ ($v->group_id == '' || ($v->group_id != '' && $v->group_id == ctx()->getData()["group_id"])) &&
+ ($v->operator_id == '' || ($v->operator_id != '' && $v->operator_id == ctx()->getData()["operator_id"]));
+ });
+ $dispatcher->dispatchEvents(ctx()->getData());
+ return;
+ case "request":
+ $dispatcher = new EventDispatcher(CQRequest::class);
+ $dispatcher->setRuleFunction(function (CQRequest $v) {
+ return ($v->request_type == '' || ($v->request_type != '' && $v->request_type == ctx()->getData()['request_type'])) &&
+ ($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()['sub_type'])) &&
+ ($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getData()["user_id"])) &&
+ ($v->comment == '' || ($v->comment != '' && $v->comment == ctx()->getData()['comment']));
+ });
+ $dispatcher->dispatchEvents(ctx()->getData());
+ return;
+ }
+ }
+
+ private function dispatchAPIResponse($req) {
+ $status = $req["status"];
+ $retcode = $req["retcode"];
+ $data = $req["data"];
+ if (isset($req["echo"]) && is_numeric($req["echo"])) {
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ if (isset($r[$req["echo"]])) {
+ $origin = $r[$req["echo"]];
+ $self_id = $origin["self_id"];
+ $response = [
+ "status" => $status,
+ "retcode" => $retcode,
+ "data" => $data,
+ "self_id" => $self_id,
+ "echo" => $req["echo"]
+ ];
+ set_coroutine_params(["cq_response" => $response]);
+ $dispatcher = new EventDispatcher(CQAPIResponse::class);
+ $dispatcher->setRuleFunction(function (CQAPIResponse $response) {
+ return $response->retcode == ctx()->getCQResponse()["retcode"];
+ });
+ $dispatcher->dispatchEvents($response);
+
+ $origin_ctx = ctx()->copy();
+ set_coroutine_params($origin_ctx);
+ if (($origin["coroutine"] ?? false) !== false) {
+ SpinLock::lock("wait_api");
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ $r[$req["echo"]]["result"] = $response;
+ LightCacheInside::set("wait_api", "wait_api", $r);
+ SpinLock::unlock("wait_api");
+ Coroutine::resume($origin['coroutine']);
+ }
+ SpinLock::lock("wait_api");
+ $r = LightCacheInside::get("wait_api", "wait_api");
+ unset($r[$req["echo"]]);
+ LightCacheInside::set("wait_api", "wait_api", $r);
+ SpinLock::unlock("wait_api");
+ }
+ }
+ }
+}
diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php
new file mode 100644
index 00000000..c28c61e5
--- /dev/null
+++ b/src/ZM/Store/LightCache.php
@@ -0,0 +1,207 @@
+column("value", Table::TYPE_STRING, $config["max_strlen"]);
+ self::$kv_table->column("expire", Table::TYPE_INT);
+ self::$kv_table->column("data_type", Table::TYPE_STRING, 8);
+ $result = self::$kv_table->create();
+ self::$kv_lock = new Table($config["size"], $config["hash_conflict_proportion"]);
+ $result = $result && self::$kv_lock->create();
+ if ($result === true && isset($config["persistence_path"])) {
+ if (file_exists($config["persistence_path"])) {
+ $r = json_decode(file_get_contents($config["persistence_path"]), true);
+ if ($r === null) $r = [];
+ foreach ($r as $k => $v) {
+ $write = self::set($k, $v, -2);
+ Console::verbose("Writing LightCache: " . $k);
+ if ($write === false) {
+ self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
+ return false;
+ }
+ }
+ }
+ }
+ if ($result === false) {
+ self::$last_error = '系统内存不足,申请失败';
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $key
+ * @return null|string
+ * @throws Exception
+ */
+ public static function get(string $key) {
+ if (self::$kv_table === null) throw new Exception("not initialized LightCache");
+ self::checkExpire($key);
+ $r = self::$kv_table->get($key);
+ return $r === false ? null : self::parseGet($r);
+ }
+
+ /**
+ * @param string $key
+ * @return mixed|null
+ * @throws Exception
+ */
+ public static function getExpire(string $key) {
+ if (self::$kv_table === null) throw new Exception("not initialized LightCache");
+ self::checkExpire($key);
+ $r = self::$kv_table->get($key, "expire");
+ return $r === false ? null : $r - time();
+ }
+
+ /**
+ * @param string $key
+ * @param string|array|int $value
+ * @param int $expire
+ * @return mixed
+ * @throws Exception
+ */
+ public static function set(string $key, $value, int $expire = -1) {
+ if (self::$kv_table === null) throw new Exception("not initialized LightCache");
+ if (is_array($value)) {
+ $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+ if (strlen($value) >= self::$config["max_strlen"]) return false;
+ $data_type = "json";
+ } elseif (is_string($value)) {
+ $data_type = "";
+ } elseif (is_int($value)) {
+ $data_type = "int";
+ } elseif (is_bool($value)) {
+ $data_type = "bool";
+ $value = json_encode($value);
+ } else {
+ throw new Exception("Only can set string, array and int");
+ }
+ try {
+ return self::$kv_table->set($key, [
+ "value" => $value,
+ "expire" => $expire >= 0 ? $expire + time() : $expire,
+ "data_type" => $data_type
+ ]);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * @param string $key
+ * @param $value
+ * @return bool|mixed
+ * @throws Exception
+ */
+ public static function update(string $key, $value) {
+ if (self::$kv_table === null) throw new Exception("not initialized LightCache.");
+ if (is_array($value)) {
+ $value = json_encode($value, JSON_UNESCAPED_UNICODE);
+ if (strlen($value) >= self::$config["max_strlen"]) return false;
+ $data_type = "json";
+ } elseif (is_string($value)) {
+ $data_type = "";
+ } elseif (is_int($value)) {
+ $data_type = "int";
+ } else {
+ throw new Exception("Only can set string, array and int");
+ }
+ try {
+ if (self::$kv_table->get($key) === false) return false;
+ return self::$kv_table->set($key, [
+ "value" => $value,
+ "data_type" => $data_type
+ ]);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ public static function getMemoryUsage() {
+ return self::$kv_table->getMemorySize();
+ }
+
+ /**
+ * @param string $key
+ * @return bool
+ * @throws Exception
+ */
+ public static function isset(string $key) {
+ return self::get($key) !== null;
+ }
+
+ public static function unset(string $key) {
+ return self::$kv_table->del($key);
+ }
+
+ public static function getAll() {
+ $r = [];
+ $del = [];
+ foreach (self::$kv_table as $k => $v) {
+ if ($v["expire"] <= time() && $v["expire"] >= 0) {
+ $del[] = $k;
+ continue;
+ }
+ $r[$k] = self::parseGet($v);
+ }
+ foreach ($del as $v) {
+ self::unset($v);
+ }
+ return $r;
+ }
+
+ public static function savePersistence() {
+ if (self::$kv_table === null) return;
+ $r = [];
+ foreach (self::$kv_table as $k => $v) {
+ if ($v["expire"] === -2) {
+ Console::verbose("Saving " . $k);
+ $r[$k] = self::parseGet($v);
+ }
+ }
+ if(self::$config["persistence_path"] == "") return;
+ if (file_exists(self::$config["persistence_path"])) {
+ $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256));
+ if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!");
+ }
+ }
+
+ private static function checkExpire($key) {
+ if (($expire = self::$kv_table->get($key, "expire")) >= 0) {
+ if ($expire <= time()) {
+ self::$kv_table->del($key);
+ }
+ }
+ }
+
+ private static function parseGet($r) {
+ switch ($r["data_type"]) {
+ case "json":
+ case "int":
+ case "bool":
+ return json_decode($r["value"], true);
+ case "":
+ default:
+ return $r["value"];
+ }
+ }
+}
diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php
new file mode 100644
index 00000000..c161eca0
--- /dev/null
+++ b/src/ZM/Store/LightCacheInside.php
@@ -0,0 +1,64 @@
+column("value", Table::TYPE_STRING, 65536);
+ self::$kv_table["connect"] = new Table(8, 0);
+ self::$kv_table["connect"]->column("value", Table::TYPE_STRING, 256);
+ $result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create();
+ if ($result === false) {
+ self::$last_error = '系统内存不足,申请失败';
+ return $result;
+ }
+ return $result;
+ }
+
+ /**
+ * @param string $table
+ * @param string $key
+ * @return mixed|null
+ * @throws ZMException
+ */
+ public static function get(string $table, string $key) {
+ if (!isset(self::$kv_table[$table])) throw new ZMException("not initialized LightCache");
+ $r = self::$kv_table[$table]->get($key);
+ return $r === false ? null : json_decode($r["value"], true);
+ }
+
+ /**
+ * @param string $table
+ * @param string $key
+ * @param string|array|int $value
+ * @return mixed
+ * @throws ZMException
+ */
+ public static function set(string $table, string $key, $value) {
+ if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
+ try {
+ return self::$kv_table[$table]->set($key, [
+ "value" => json_encode($value, 256)
+ ]);
+ } catch (Exception $e) {
+ return false;
+ }
+ }
+
+ public static function unset(string $table, string $key) {
+ return self::$kv_table[$table]->del($key);
+ }
+}
diff --git a/src/ZM/Store/Lock/SpinLock.php b/src/ZM/Store/Lock/SpinLock.php
new file mode 100644
index 00000000..25b85fa4
--- /dev/null
+++ b/src/ZM/Store/Lock/SpinLock.php
@@ -0,0 +1,44 @@
+column('lock_num', Table::TYPE_INT, 8);
+ return self::$kv_lock->create();
+ }
+
+ public static function lock(string $key)
+ {
+ while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { //此资源已经被锁上了
+ if(Coroutine::getCid() != -1) System::sleep(self::$delay / 1000);
+ else usleep(self::$delay * 1000);
+ }
+ }
+
+ public static function tryLock(string $key) {
+ if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) {
+ return false;
+ }
+ return true;
+ }
+
+ public static function unlock(string $key) {
+ return self::$kv_lock->set($key, ['lock_num' => 0]);
+ }
+}
diff --git a/src/ZM/Store/MySQL/SqlPoolStorage.php b/src/ZM/Store/MySQL/SqlPoolStorage.php
new file mode 100644
index 00000000..1df61eda
--- /dev/null
+++ b/src/ZM/Store/MySQL/SqlPoolStorage.php
@@ -0,0 +1,13 @@
+get();
+ $result = $callable($r);
+ if (isset($r->wasted)) ZMRedisPool::$pool->put(null);
+ else ZMRedisPool::$pool->put($r);
+ return $result;
+ }
+
+ /**
+ * ZMRedis constructor.
+ * @throws NotInitializedException
+ */
+ public function __construct() {
+ if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
+ $this->conn = ZMRedisPool::$pool->get();
+ }
+
+ /**
+ * @return Redis
+ */
+ public function get() {
+ return $this->conn;
+ }
+
+ public function __destruct() {
+ if (isset($this->conn->wasted)) ZMRedisPool::$pool->put(null);
+ else ZMRedisPool::$pool->put($this->conn);
+ }
+}
diff --git a/src/ZM/Store/Redis/ZMRedisPool.php b/src/ZM/Store/Redis/ZMRedisPool.php
new file mode 100644
index 00000000..2ca1473e
--- /dev/null
+++ b/src/ZM/Store/Redis/ZMRedisPool.php
@@ -0,0 +1,37 @@
+withHost($config['host'])
+ ->withPort($config['port'])
+ ->withAuth($config['auth'])
+ ->withDbIndex($config['db_index'])
+ ->withTimeout($config['timeout'] ?? 1)
+ );
+ try {
+ $r = self::$pool->get()->ping('123');
+ if(strpos(strtolower($r), "123") !== false) {
+ Console::debug("成功连接redis连接池!");
+ } else {
+ var_dump($r);
+ }
+ } catch (RedisException $e) {
+ Console::error("Redis init failed! ".$e->getMessage());
+ self::$pool = null;
+ }
+ }
+}
diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php
new file mode 100644
index 00000000..b4e88ff8
--- /dev/null
+++ b/src/ZM/Store/ZMAtomic.php
@@ -0,0 +1,39 @@
+ $v) {
+ self::$atomics[$k] = new Atomic($v);
+ }
+ self::$atomics["stop_signal"] = new Atomic(0);
+ self::$atomics["wait_msg_id"] = new Atomic(0);
+ self::$atomics["_event_id"] = new Atomic(0);
+ for ($i = 0; $i < 10; ++$i) {
+ self::$atomics["_tmp_" . $i] = new Atomic(0);
+ }
+ }
+
+
+}
diff --git a/src/ZM/Store/ZMBuf.php b/src/ZM/Store/ZMBuf.php
new file mode 100755
index 00000000..32c0621a
--- /dev/null
+++ b/src/ZM/Store/ZMBuf.php
@@ -0,0 +1,17 @@
+add(1);
+ $hang["compare"] = $compare;
+ $hang["coroutine"] = $cid;
+ $hang["worker_id"] = server()->worker_id;
+ $hang["result"] = null;
+ SpinLock::lock("wait_api");
+ $wait = LightCacheInside::get("wait_api", "wait_api");
+ $wait[$api_id] = $hang;
+ LightCacheInside::set("wait_api", "wait_api", $wait);
+ SpinLock::unlock("wait_api");
+ $id = swoole_timer_after($timeout * 1000, function () use ($api_id) {
+ $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null;
+ if (is_array($r)) {
+ Co::resume($r["coroutine"]);
+ }
+ });
+ Co::suspend();
+ SpinLock::lock("wait_api");
+ $sess = LightCacheInside::get("wait_api", "wait_api");
+ $result = $sess[$api_id]["result"];
+ unset($sess[$api_id]);
+ LightCacheInside::set("wait_api", "wait_api", $sess);
+ SpinLock::unlock("wait_api");
+ if (isset($id)) swoole_timer_clear($id);
+ if ($result === null) return false;
+ return $result;
+ }
+
+ public static function resumeByWS() {
+ $dat = ctx()->getData();
+ $last = null;
+ SpinLock::lock("wait_api");
+ $all = LightCacheInside::get("wait_api", "wait_api") ?? [];
+ foreach ($all as $k => $v) {
+ if(!isset($v["compare"])) continue;
+ foreach ($v["compare"] as $vs) {
+ if ($v[$vs] != ($dat[$vs] ?? null)) {
+ continue 2;
+ }
+ }
+ $last = $k;
+ }
+ if($last !== null) {
+ $all[$last]["result"] = $dat;
+ LightCacheInside::set("wait_api", "wait_api", $all);
+ SpinLock::unlock("wait_api");
+ if ($all[$last]["worker_id"] != server()->worker_id) {
+ ZMUtil::sendActionToWorker($all[$k]["worker_id"], "resume_ws_message", $all[$last]);
+ } else {
+ Co::resume($all[$last]["coroutine"]);
+ }
+ return true;
+ } else {
+ SpinLock::unlock("wait_api");
+ return false;
+ }
+ }
+}
diff --git a/src/ZM/Utils/CoroutinePool.php b/src/ZM/Utils/CoroutinePool.php
new file mode 100644
index 00000000..042500b8
--- /dev/null
+++ b/src/ZM/Utils/CoroutinePool.php
@@ -0,0 +1,51 @@
+= (self::$sizes[$name] ?? self::$default_size)) {
+ self::$yields[] = Coroutine::getCid();
+ Coroutine::suspend();
+ }
+ go(function () use ($func, $name) {
+ self::$cids[$name][] = Coroutine::getCid();
+ //Console::debug("正在执行协程,当前协程池中有 " . count(self::$cids[$name]) . " 个正在运行的协程: ".implode(", ", self::$cids[$name]));
+ $func();
+ self::checkCids($name);
+ });
+ }
+
+ public static function defaultSize(int $size) {
+ self::$default_size = $size;
+ }
+
+ public static function setSize($name, int $size) {
+ self::$sizes[$name] = $size;
+ }
+
+ private static function checkCids($name) {
+ if (in_array(Coroutine::getCid(), self::$cids[$name])) {
+ $a = array_search(Coroutine::getCid(), self::$cids[$name]);
+ array_splice(self::$cids[$name], $a, 1);
+ $r = array_shift(self::$yields);
+ if ($r !== null) {
+ Coroutine::resume($r);
+ }
+ }
+ }
+}
diff --git a/src/ZM/Utils/DataProvider.php b/src/ZM/Utils/DataProvider.php
new file mode 100644
index 00000000..04b02738
--- /dev/null
+++ b/src/ZM/Utils/DataProvider.php
@@ -0,0 +1,31 @@
+ $v) {
+ if ($v["id"] == $node["param_route"]) {
+ $node = $v;
+ $params[mb_substr($v["name"], 1, -1)] = $r;
+ continue 2;
+ }
+ }
+ } elseif ($node["son"][0]["name"] == $r) {
+ $node = $node["son"][0];
+ continue;
+ }
+ } elseif ($cnt >= 1) {
+ if (isset($node["param_route"])) {
+ foreach ($node["son"] as $k => $v) {
+ if ($v["id"] == $node["param_route"]) {
+ $node = $v;
+ $params[mb_substr($v["name"], 1, -1)] = $r;
+ continue 2;
+ }
+ }
+ }
+ foreach ($node["son"] as $k => $v) {
+ if ($v["name"] == $r) {
+ $node = $v;
+ continue 2;
+ }
+ }
+ }
+ if (ZMConfig::get("global", "static_file_server")["status"]) {
+ HttpUtil::handleStaticPage($request->server["request_uri"], $response);
+ return null;
+ }
+ }
+ return !isset($node["route"]) ? false : true;
+ }
+
+ public static function getHttpCodePage(int $http_code) {
+ if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) {
+ return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]);
+ } else return null;
+ }
+
+ /**
+ * @param $uri
+ * @param Response|\Swoole\Http\Response $response
+ * @param array $settings
+ * @return bool
+ */
+ public static function handleStaticPage($uri, $response, $settings = []) {
+ $base_dir = $settings["document_root"] ?? ZMConfig::get("global", "static_file_server")["document_root"];
+ $base_index = $settings["document_index"] ?? ZMConfig::get("global", "static_file_server")["document_index"];
+ $path = realpath($base_dir . urldecode($uri));
+ if ($path !== false) {
+ if (is_dir($path)) $path = $path . '/';
+ $work = realpath($base_dir) . '/';
+ if (strpos($path, $work) !== 0) {
+ Console::info("[403] " . $uri);
+ self::responseCodePage($response, 403);
+ return true;
+ }
+ if (is_dir($path)) {
+ if(mb_substr($uri, -1, 1) != "/") {
+ Console::info("[302] " . $uri);
+ $response->redirect($uri."/", 302);
+ return true;
+ }
+ foreach ($base_index as $vp) {
+ if (is_file($path . "/" . $vp)) {
+ Console::info("[200] " . $uri);
+ $exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown");
+ $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
+ $response->end(file_get_contents($path . $vp));
+ return true;
+ }
+ }
+ } elseif (is_file($path)) {
+ Console::info("[200] " . $uri);
+ $exp = strtolower(pathinfo($path)['extension'] ?? "unknown");
+ $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
+ $response->end(file_get_contents($path));
+ return true;
+ }
+ }
+ Console::info("[404] " . $uri);
+ self::responseCodePage($response, 404);
+ return true;
+ }
+
+ public static function responseCodePage($response, $code) {
+ $response->status($code);
+ $response->end(self::getHttpCodePage($code));
+ }
+}
diff --git a/src/ZM/Utils/SQLPool.php b/src/ZM/Utils/SQLPool.php
deleted file mode 100644
index 9fb66280..00000000
--- a/src/ZM/Utils/SQLPool.php
+++ /dev/null
@@ -1,122 +0,0 @@
-pool = new SplQueue;
- $this->info = [
- "host" => ZMBuf::globals("sql_config")["sql_host"],
- "port" => ZMBuf::globals("sql_config")["sql_port"],
- "user" => ZMBuf::globals("sql_config")["sql_username"],
- "password" => ZMBuf::globals("sql_config")["sql_password"],
- "database" => ZMBuf::globals("sql_config")["sql_database"]
- ];
- Console::debug("新建检测 MySQL 连接的计时器");
- zm_timer_tick(10000, function () {
- //Console::debug("正在检测是否有坏死的MySQL连接,当前连接池有 ".count($this->pool) . " 个连接");
- if (count($this->pool) > 0) {
- /** @var PDO $cnn */
- $cnn = $this->pool->pop();
- $this->connect_cnt -= 1;
- try {
- $cnn->getAttribute(PDO::ATTR_SERVER_INFO);
- } catch (PDOException $e) {
- if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
- Console::info("MySQL 长连接丢失,取消连接");
- unset($cnn);
- return;
- }
- }
- $this->pool->push($cnn);
- $this->connect_cnt += 1;
- }
- });
- }
-
- /**
- * 将利用过的连接入队
- * @param $mysql
- */
- public function put($mysql) {
- $this->pool->push($mysql);
- if (($a = array_shift($this->co_list)) !== null) {
- Coroutine::resume($a);
- }
- }
-
- /**
- * 获取队中的连接,如果不存在则创建新的
- * @param bool $no_new_conn
- * @return bool|mixed|PDO
- */
- public function get($no_new_conn = false) {
- if (count($this->pool) == 0 && $this->connect_cnt <= 70) {
- if ($no_new_conn) return false;
- $this->connect_cnt += 1;
- $r = $this->newConnect();
- if ($r !== false) {
- return $r;
- } else {
- $this->connect_cnt -= 1;
- return false;
- }
- } elseif (count($this->pool) > 0) {
- /** @var PDO $con */
- $con = $this->pool->pop();
- return $con;
- } elseif ($this->connect_cnt > 70) {
- $this->co_list[] = Coroutine::getuid();
- Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 " . Coroutine::stats()["coroutine_num"]);
- Coroutine::suspend();
- return $this->get($no_new_conn);
- }
- return false;
- }
-
- public function getCount() {
- return $this->pool->count();
- }
-
- public function destruct() {
- // 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出
- $this->available = false;
- while (!$this->pool->isEmpty()) {
- $this->pool->pop();
- }
- }
-
- private function newConnect() {
- //无空闲连接,创建新连接
- $dsn = "mysql:host=" . $this->info["host"] . ";dbname=" . $this->info["database"] . ";charset=utf8";
- try {
- $mysql = new PDO($dsn, $this->info["user"], $this->info["password"], array(PDO::ATTR_PERSISTENT => true));
- } catch (PDOException $e) {
- Console::error("PDO Error: " . $e->getMessage());
- return false;
- }
- Console::info("创建SQL连接中,当前有" . $this->connect_cnt . "个连接");
- return $mysql;
- }
-}
diff --git a/src/ZM/Utils/ScheduleManager.php b/src/ZM/Utils/ScheduleManager.php
deleted file mode 100644
index e87e15ce..00000000
--- a/src/ZM/Utils/ScheduleManager.php
+++ /dev/null
@@ -1,10 +0,0 @@
-$function_name();
+ return true;
+ case 'psysh':
+ if (Framework::$argv["disable-coroutine"]) {
+ (new Shell())->run();
+ } else
+ Console::error("Only \"--disable-coroutine\" mode can use psysh!!!");
+ return true;
+ case 'bc':
+ $code = base64_decode($it[1] ?? '', true);
+ try {
+ eval($code);
+ } catch (Exception $e) {
+ }
+ return true;
+ case 'echo':
+ Console::info($it[1]);
+ return true;
+ case 'color':
+ Console::log($it[2], $it[1]);
+ return true;
+ case 'stop':
+ Event::del($resource);
+ ZMUtil::stop();
+ return false;
+ case 'reload':
+ case 'r':
+ ZMUtil::reload();
+ return false;
+ case '':
+ return true;
+ default:
+ Console::info("Command not found: " . $cmd);
+ return true;
+ }
+ }
+}
diff --git a/src/ZM/Utils/ZMRequest.php b/src/ZM/Utils/ZMRequest.php
deleted file mode 100644
index fe1c5da5..00000000
--- a/src/ZM/Utils/ZMRequest.php
+++ /dev/null
@@ -1,153 +0,0 @@
-set($set == [] ? ['timeout' => 15.0] : $set);
- $cli->setHeaders($headers);
- $cli->get($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
- if ($return_body) {
- if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
- $a = $cli->body;
- $cli->close();
- return $a;
- } else {
- $cli->close();
- return $cli;
- }
- }
-
- /**
- * 使用Swoole协程客户端发起HTTP POST请求
- * 返回请求后的body
- * 如果请求失败或返回状态不是200,则返回 false
- * @param $url
- * @param array $header
- * @param $data
- * @param array $set
- * @param bool $return_body
- * @return bool|string|Client
- */
- public static function post($url, array $header, $data, $set = [], $return_body = true) {
- /** @var Client $cli */
- list($cli, $parse) = self::getNewClient($url);
- if($cli === null) return false;
- $cli->set($set == [] ? ['timeout' => 15.0] : $set);
- $cli->setHeaders($header);
- $cli->post($parse["path"] . (isset($parse["query"]) ? ("?" . $parse["query"]) : ""), $data);
- if ($return_body) {
- if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
- $a = $cli->body;
- $cli->close();
- return $a;
- } else {
- $cli->close();
- return $cli;
- }
- }
-
- /**
- * @param $url
- * @param array $set
- * @param array $header
- * @return ZMWebSocket
- * @since 1.5
- */
- public static function websocket($url, $set = ['websocket_mask' => true], $header = []) {
- return new ZMWebSocket($url, $set, $header);
- }
-
- /**
- * @param $option
- * @return Saber
- */
- public static function session($option) {
- return Saber::session($option);
- }
-
- /**
- * @param $url
- * @param array $attribute
- * @param bool $return_body
- * @return bool|string|Client
- */
- public static function request($url, $attribute = [], $return_body = true) {
- /** @var Client $cli */
- list($cli, $parse) = self::getNewClient($url);
- if($cli === null) return false;
- $cli->set($attribute["set"] ?? ["timeout" => 15.0]);
- $cli->setMethod($attribute["method"] ?? "GET");
- $cli->setHeaders($attribute["headers"] ?? []);
- if(isset($attribute["data"])) $cli->setData($attribute["data"]);
- if(isset($attribute["file"])) {
- foreach($attribute["file"] as $k => $v) {
- $cli->addFile($v["path"], $v["name"], $v["mime_type"] ?? null, $v["filename"] ?? null, $v["offset"] ?? 0, $v["length"] ?? 0);
- }
- }
- $cli->execute($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
- if ($return_body) {
- if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
- $a = $cli->body;
- $cli->close();
- return $a;
- } else {
- $cli->close();
- return $cli;
- }
- }
-
- /**
- * @param $url
- * @param null|bool $dst
- * @return bool
- */
- public static function downloadFile($url, $dst = null) {
- /** @var Client $cli */
- list($cli, $parse) = self::getNewClient($url);
- if($cli === null) return false;
- $cli->set(["timeout" => 60.0]);
- $save_path = $dst === null ? "/tmp/_zm_".mt_rand(1000000, 9999999) : $dst;
- $result = $cli->download($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""), $save_path);
- if($result === false) return false;
- elseif ($dst === null) return $save_path;
- else return true;
- }
-
- /**
- * @param $url
- * @return bool|array
- */
- private static function getNewClient($url) {
- $parse = parse_url($url);
- if (!isset($parse["host"])) {
- Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\"");
- return false;
- }
- if(!isset($parse["path"])) $parse["path"] = "/";
- $port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
- $cli = new Client($parse["host"], $port, ($parse["scheme"] ?? "http") == "https");
- return [$cli, $parse];
- }
-}
diff --git a/src/ZM/Utils/ZMRobot.php b/src/ZM/Utils/ZMRobot.php
deleted file mode 100644
index 13184072..00000000
--- a/src/ZM/Utils/ZMRobot.php
+++ /dev/null
@@ -1,412 +0,0 @@
- $robot_id]);
- if ($r == []) throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!");
- return new ZMRobot($r[0]);
- }
-
- /**
- * @throws RobotNotFoundException
- * @return ZMRobot
- */
- public static function getRandom() {
- $r = ConnectionManager::getByType("qq");
- if($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
- return new ZMRobot($r[array_rand($r)]);
- }
-
- public function __construct(CQConnection $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 sendPrivateMsg($user_id, $message, $auto_escape = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'user_id' => $user_id,
- 'message' => $message,
- 'auto_escape' => $auto_escape
- ]
- ], $this->callback);
- }
-
- public function sendGroupMsg($group_id, $message, $auto_escape = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'message' => $message,
- 'auto_escape' => $auto_escape
- ]
- ], $this->callback);
- }
-
- public function sendDiscussMsg($discuss_id, $message, $auto_escape = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'discuss_id' => $discuss_id,
- 'message' => $message,
- 'auto_escape' => $auto_escape
- ]
- ], $this->callback);
- }
-
- public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
- return CQAPI::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);
- }
-
- public function deleteMsg($message_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'message_id' => $message_id
- ]
- ], $this->callback);
- }
-
- public function sendLike($user_id, $times = 1) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'user_id' => $user_id,
- 'times' => $times
- ]
- ], $this->callback);
- }
-
- public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'reject_add_request' => $reject_add_request
- ]
- ], $this->callback);
- }
-
- public function setGroupBan($group_id, $user_id, $duration = 1800) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'duration' => $duration
- ]
- ], $this->callback);
- }
-
- public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
- return CQAPI::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);
- }
-
- public function setGroupWholeBan($group_id, $enable = true) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'enable' => $enable
- ]
- ], $this->callback);
- }
-
- public function setGroupAdmin($group_id, $user_id, $enable = true) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'enable' => $enable
- ]
- ], $this->callback);
- }
-
- public function setGroupAnonymous($group_id, $enable = true) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'enable' => $enable
- ]
- ], $this->callback);
- }
-
- public function setGroupCard($group_id, $user_id, $card = "") {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'card' => $card
- ]
- ], $this->callback);
- }
-
- public function setGroupLeave($group_id, $is_dismiss = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'is_dismiss' => $is_dismiss
- ]
- ], $this->callback);
- }
-
- public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'special_title' => $special_title,
- 'duration' => $duration
- ]
- ], $this->callback);
- }
-
- public function setDiscussLeave($discuss_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'discuss_id' => $discuss_id
- ]
- ], $this->callback);
- }
-
- public function setFriendAddRequest($flag, $approve = true, $remark = "") {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'flag' => $flag,
- 'approve' => $approve,
- 'remark' => $remark
- ]
- ], $this->callback);
- }
-
- public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'flag' => $flag,
- 'sub_type' => $sub_type,
- 'approve' => $approve,
- 'reason' => $reason
- ]
- ], $this->callback);
- }
-
- public function getLoginInfo() {
- return CQAPI::processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
- }
-
- public function getStrangerInfo($user_id, $no_cache = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'user_id' => $user_id,
- 'no_cache' => $no_cache
- ]
- ], $this->callback);
- }
-
- public function getFriendList() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getGroupList() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getGroupInfo($group_id, $no_cache = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'no_cache' => $no_cache
- ]
- ], $this->callback);
- }
-
- public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'user_id' => $user_id,
- 'no_cache' => $no_cache
- ]
- ], $this->callback);
- }
-
- public function getGroupMemberList($group_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id
- ]
- ]);
- }
-
- public function getCookies($domain = "") {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'domain' => $domain
- ]
- ]);
- }
-
- public function getCsrfToken() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getCredentials($domain = "") {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'domain' => $domain
- ]
- ], $this->callback);
- }
-
- public function getRecord($file, $out_format, $full_path = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'file' => $file,
- 'out_format' => $out_format,
- 'full_path' => $full_path
- ]
- ], $this->callback);
- }
-
- public function getImage($file) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'file' => $file
- ]
- ], $this->callback);
- }
-
- public function canSendImage() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function canSendRecord() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getStatus() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getVersionInfo() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function setRestartPlugin($delay = 0) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'delay' => $delay
- ]
- ], $this->callback);
- }
-
- public function cleanDataDir($data_dir) {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__),
- 'params' => [
- 'data_dir' => $data_dir
- ]
- ], $this->callback);
- }
-
- public function cleanPluginLog() {
- return CQAPI::processAPI($this->connection, [
- 'action' => $this->getActionName(__FUNCTION__)
- ], $this->callback);
- }
-
- public function getExperimentAPI() {
- return new ZMRobotExperiment($this->connection, $this->callback, $this->prefix);
- }
-
- 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/Utils/ZMRobotExperiment.php b/src/ZM/Utils/ZMRobotExperiment.php
deleted file mode 100644
index 2043fc67..00000000
--- a/src/ZM/Utils/ZMRobotExperiment.php
+++ /dev/null
@@ -1,104 +0,0 @@
-connection = $connection;
- $this->callback = $callback;
- $this->prefix = $prefix;
- }
-
- public function setCallback($callback = true) {
- $this->callback = $callback;
- return $this;
- }
-
- public function setPrefix($prefix = ZMRobot::API_NORMAL) {
- $this->prefix = $prefix;
- return $this;
- }
-
- public function getFriendList($flat = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'flat' => $flat
- ]
- ], $this->callback);
- }
-
- public function getGroupInfo($group_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id
- ]
- ], $this->callback);
- }
-
- public function getVipInfo($user_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'user_id' => $user_id
- ]
- ], $this->callback);
- }
-
- public function getGroupNotice($group_id) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id
- ]
- ], $this->callback);
- }
-
- public function sendGroupNotice($group_id, $title, $content) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'group_id' => $group_id,
- 'title' => $title,
- 'content' => $content
- ]
- ], $this->callback);
- }
-
- public function setRestart($clean_log = false, $clean_cache = false, $clean_event = false) {
- return CQAPI::processAPI($this->connection, [
- 'action' => '_' . $this->getActionName(__FUNCTION__),
- 'params' => [
- 'clean_log' => $clean_log,
- 'clean_cache' => $clean_cache,
- 'clean_event' => $clean_event
- ]
- ], $this->callback);
- }
-
- private function getActionName(string $method) {
- $prefix = ($this->prefix == ZMRobot::API_ASYNC ? '_async' : ($this->prefix == ZMRobot::API_RATE_LIMITED ? '_rate_limited' : ''));
- $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
- return $prefix . $func_name;
- }
-}
diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php
index f09982a5..a4a3e949 100644
--- a/src/ZM/Utils/ZMUtil.php
+++ b/src/ZM/Utils/ZMUtil.php
@@ -5,68 +5,54 @@ namespace ZM\Utils;
use Co;
-use framework\Console;
-use Framework\DataProvider;
-use Framework\ZMBuf;
-use ZM\API\CQ;
+use Exception;
+use Swoole\Event;
+use Swoole\Timer;
+use ZM\Console\Console;
+use ZM\Store\LightCache;
+use ZM\Store\LightCacheInside;
+use ZM\Store\ZMAtomic;
+use ZM\Store\ZMBuf;
class ZMUtil
{
- /**
- * 检查workerStart是否运行结束
- */
- public static function checkWait() {
- if (ZMBuf::isset("wait_start")) {
- ZMBuf::append("wait_start", Co::getCid());
- Co::suspend();
+ public static function stop() {
+ Console::warning(Console::setColor("Stopping server...", "red"));
+ LightCache::savePersistence();
+ if (ZMBuf::$terminal !== null)
+ Event::del(ZMBuf::$terminal);
+ ZMAtomic::get("stop_signal")->set(1);
+ try {
+ LightCache::set('stop', 'OK');
+ } catch (Exception $e) {
}
+ server()->shutdown();
+ server()->stop();
}
- public static function stop($without_shutdown = false) {
- Console::info(Console::setColor("Stopping server...", "red"));
- foreach ((ZMBuf::$server->connections ?? []) as $v) {
- ZMBuf::$server->close($v);
- }
- DataProvider::saveBuffer();
- if (!$without_shutdown)
- ZMBuf::$server->shutdown();
- ZMBuf::$server->stop();
- }
-
- public static function getHttpCodePage(int $http_code) {
- if (isset(ZMBuf::globals("http_default_code_page")[$http_code])) {
- return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMBuf::globals("http_default_code_page")[$http_code]);
- } else return null;
- }
-
- public static function reload() {
+ public static function reload($delay = 800) {
Console::info(Console::setColor("Reloading server...", "gold"));
- foreach (ZMBuf::get("wait_api", []) as $k => $v) {
- if ($v["result"] === null) Co::resume($v["coroutine"]);
+ usleep($delay * 1000);
+ foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {
+ if (($v["result"] ?? false) === null && isset($v["coroutine"])) Co::resume($v["coroutine"]);
}
- foreach (ZMBuf::$server->connections as $v) {
- ZMBuf::$server->close($v);
- }
- DataProvider::saveBuffer();
- ZMBuf::$server->reload();
- }
-
- /**
- * 解析CQ码
- * @param $msg
- * @return array|null
- */
- static function getCQ($msg) {
- return CQ::getCQ($msg);
+ LightCacheInside::unset("wait_api", "wait_api");
+ LightCache::savePersistence();
+ //DataProvider::saveBuffer();
+ Timer::clearAll();
+ server()->reload();
}
public static function getModInstance($class) {
if (!isset(ZMBuf::$instance[$class])) {
- Console::debug("Class instance $class not exist, so I created it.");
- ZMBuf::$instance[$class] = new $class();
- return ZMBuf::$instance[$class];
+ //Console::debug("Class instance $class not exist, so I created it.");
+ return ZMBuf::$instance[$class] = new $class();
} else {
return ZMBuf::$instance[$class];
}
}
+
+ public static function sendActionToWorker($target_id, $action, $data) {
+ server()->sendMessage(json_encode(["action" => $action, "data" => $data]), $target_id);
+ }
}
diff --git a/src/ZM/Utils/ZMWebSocket.php b/src/ZM/Utils/ZMWebSocket.php
deleted file mode 100644
index 6340ba7c..00000000
--- a/src/ZM/Utils/ZMWebSocket.php
+++ /dev/null
@@ -1,106 +0,0 @@
- true], $header = []) {
- $this->parse = parse_url($url);
- if (!isset($this->parse["host"])) {
- Console::warning("ZMRequest: url must contains scheme such as \"ws(s)://\"");
- return;
- }
- if (!isset($this->parse["path"])) $this->parse["path"] = "/";
- $port = $this->parse["port"] ?? (($this->parse["scheme"] ?? "ws") == "wss" ? 443 : 80);
- $this->client = new Client($this->parse["host"], $port, (($this->parse["scheme"] ?? "ws") == "wss" ? true : false));
- $this->client->set($set);
- if ($header != []) $this->client->setHeaders($header);
- $this->is_available = true;
- }
-
- /**
- * @return bool
- */
- public function upgrade() {
- if (!$this->is_available) return false;
- $r = $this->client->upgrade($this->parse["path"] . (isset($this->parse["query"]) ? ("?" . $this->parse["query"]) : ""));
- if ($r) {
- go(function () {
- while (true) {
- $result = $this->client->recv(60);
- if ($result === false) {
- if ($this->client->connected === false) {
- go(function () {
- call_user_func($this->close_func, $this->client);
- });
- break;
- }
- } elseif ($result instanceof Frame) {
- go(function () use ($result) {
- $this->is_available = false;
- call_user_func($this->message_func, $result, $this->client);
- });
- }
- }
- });
- return true;
- }
- return false;
- }
-
- /**
- * @param callable $callable
- * @return $this
- */
- public function onMessage(callable $callable) {
- $this->message_func = $callable;
- return $this;
- }
-
- /**
- * @param callable $callable
- * @return $this
- */
- public function onClose(callable $callable) {
- $this->close_func = $callable;
- return $this;
- }
-}
-
-if (!debug_backtrace()) {
- go(function () {
- require_once __DIR__ . "/../../Framework/Console.php";
- $cli = new ZMWebSocket("ws://127.0.0.1:20001/");
- if (!$cli->is_available) die("Error!\n");
- $cli->onMessage(function (Frame $frame) {
- var_dump($frame);
- });
- $cli->onClose(function () {
- echo "Connection closed.\n";
- });
- if ($cli->upgrade()) {
- echo "成功连接!\n";
- } else {
- echo "连接失败!\n";
- }
- });
-}
diff --git a/src/ZM/global_defines.php b/src/ZM/global_defines.php
new file mode 100644
index 00000000..9612da44
--- /dev/null
+++ b/src/ZM/global_defines.php
@@ -0,0 +1,20 @@
+getConnection()->getName() == 'qq';
+}
+
+function connectIsDefault() {
+ return ctx()->getConnection()->getName() == 'default';
+}
+
+function connectIs($type) {
+ return ctx()->getConnection()->getName() == $type;
+}
+
+function getAnnotations() {
+ $s = debug_backtrace()[1];
+ //echo json_encode($s, 128|256);
+ $list = [];
+ foreach (EventManager::$events as $event => $v) {
+ foreach ($v as $ks => $vs) {
+ //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL;
+ if ($vs->class == $s["class"] && $vs->method == $s["function"]) {
+ $list[get_class($vs)][] = $vs;
+ }
+ }
+ }
+ return $list;
+}
+
function set_coroutine_params($array) {
$cid = Co::getCid();
if ($cid == -1) die("Cannot set coroutine params at none coroutine mode.");
- if (isset(ZMBuf::$context[$cid])) ZMBuf::$context[$cid] = array_merge(ZMBuf::$context[$cid], $array);
- else ZMBuf::$context[$cid] = $array;
- foreach (ZMBuf::$context as $c => $v) {
- if (!Co::exists($c)) unset(ZMBuf::$context[$c], ZMBuf::$context_class[$c]);
+ if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array);
+ else Context::$context[$cid] = $array;
+ foreach (Context::$context as $c => $v) {
+ if (!Co::exists($c)) unset(Context::$context[$c], ZMBuf::$context_class[$c]);
}
}
@@ -182,22 +218,30 @@ function context() {
*/
function ctx() {
$cid = Co::getCid();
- $c_class = ZMBuf::globals("context_class");
- if (isset(ZMBuf::$context[$cid])) {
+ $c_class = ZMConfig::get("global", "context_class");
+ if (isset(Context::$context[$cid])) {
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
} else {
Console::debug("未找到当前协程的上下文($cid),正在找父进程的上下文");
while (($pcid = Co::getPcid($cid)) !== -1) {
$cid = $pcid;
- if (isset(ZMBuf::$context[$cid])) return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
+ if (isset(Context::$context[$cid])) return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
}
return null;
}
}
-function debug($msg) { Console::debug($msg); }
+function zm_debug($msg) { Console::debug($msg); }
-function zm_sleep($s = 1) { Co::sleep($s); }
+function onebot_target_id_name($message_type) {
+ return ($message_type == "group" ? "group_id" : "user_id");
+}
+
+function zm_sleep($s = 1) {
+ if (Coroutine::getCid() != -1) System::sleep($s);
+ else usleep($s * 1000 * 1000);
+ return true;
+}
function zm_exec($cmd): array { return System::exec($cmd); }
@@ -209,19 +253,41 @@ function zm_resume(int $cid) { Co::resume($cid); }
function zm_timer_after($ms, callable $callable) {
go(function () use ($ms, $callable) {
- ZMUtil::checkWait();
Swoole\Timer::after($ms, $callable);
});
}
function zm_timer_tick($ms, callable $callable) {
go(function () use ($ms, $callable) {
- ZMUtil::checkWait();
Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable);
});
}
+function zm_data_hash($v) {
+ return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"]));
+}
+
+function server() {
+ return Framework::$server;
+}
+
+/**
+ * @return ZMRobot
+ * @throws RobotNotFoundException
+ * @throws ZMException
+ */
+function bot() {
+ if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) {
+ return ZMRobot::getRandom();
+ } elseif ($conn != -1) {
+ if (($obj = ManagerGM::get($conn)) !== null) return new ZMRobot($obj);
+ else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!");
+ } else {
+ throw new RobotNotFoundException("没有任何机器人连接到框架!");
+ }
+}
+
diff --git a/test/CoroutinePoolTest.php b/test/CoroutinePoolTest.php
new file mode 100644
index 00000000..cefced7b
--- /dev/null
+++ b/test/CoroutinePoolTest.php
@@ -0,0 +1,28 @@
+assertTrue(true);
+ Console::init(4);
+ CoroutinePool::setSize("default", 2);
+ CoroutinePool::defaultSize(50);
+ for ($i = 0; $i < 59; ++$i) {
+ CoroutinePool::go(function () use ($i) {
+ //Console::debug("第 $i 个马上进入睡眠...");
+ ZMRequest::get("http://localhost:9002/test/ping");
+ Console::verbose(strval($i));
+ });
+ }
+ }
+
+ public function testA() {
+ $this->assertTrue(true);
+ }
+}
diff --git a/test/LightCacheTest.php b/test/LightCacheTest.php
new file mode 100644
index 00000000..59dff6f3
--- /dev/null
+++ b/test/LightCacheTest.php
@@ -0,0 +1,26 @@
+ 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/ZMTest/Mock/Context.php b/test/ZMTest/Mock/Context.php
new file mode 100644
index 00000000..5992f718
--- /dev/null
+++ b/test/ZMTest/Mock/Context.php
@@ -0,0 +1,256 @@
+set参数 */
+$config['swoole'] = [
+ 'log_file' => $config['crash_dir'] . 'swoole_error.log',
+ 'worker_num' => 8,
+ 'dispatch_mode' => 2,
+ 'max_coroutine' => 30000,
+ //'task_worker_num' => 4,
+ //'task_enable_coroutine' => true
+];
+
+/** 轻量字符串缓存,默认开启 */
+$config['light_cache'] = [
+ "status" => true,
+ "size" => 2048, //最多允许储存的条数(需要2的倍数)
+ "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数)
+ "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多)
+];
+
+/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
+$config['sql_config'] = [
+ 'sql_host' => '',
+ 'sql_port' => 3306,
+ 'sql_username' => 'name',
+ 'sql_database' => 'db_name',
+ 'sql_password' => '',
+ 'sql_enable_cache' => true,
+ 'sql_reset_cache' => '0300',
+ 'sql_options' => [
+ PDO::ATTR_STRINGIFY_FETCHES => false,
+ PDO::ATTR_EMULATE_PREPARES => false
+ ],
+ 'sql_no_exception' => false,
+ 'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6
+];
+
+/** CQHTTP连接约定的token */
+$config["access_token"] = "";
+
+/** HTTP服务器固定请求头的返回 */
+$config['http_header'] = [
+ 'X-Powered-By' => 'zhamao-framework',
+ 'Content-Type' => 'text/html; charset=utf-8'
+];
+
+/** HTTP服务器在指定状态码下回复的页面(默认) */
+$config['http_default_code_page'] = [
+ '404' => '404.html'
+];
+
+/** zhamao-framework在框架启动时初始化的atomic们 */
+$config['init_atomics'] = [
+ //'custom_atomic_name' => 0, //自定义添加的Atomic
+];
+
+/** 终端日志显示等级(0-4) */
+$config["info_level"] = 2;
+
+/** 自动保存计时器的缓存保存时间(秒) */
+$config['auto_save_interval'] = 900;
+
+/** 上下文接口类 implemented from ContextInterface */
+$config['context_class'] = \ZMTest\Mock\Context::class;
+
+/** 静态文件访问 */
+$config['static_file_server'] = [
+ 'status' => false,
+ 'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
+ 'document_index' => [
+ 'index.html'
+ ]
+];
+
+/** 注册 Swoole Server 事件注解的类列表 */
+$config['server_event_handler_class'] = [
+ \ZM\Event\ServerEventHandler::class,
+];
+
+/** 注册自定义指令的类 */
+$config['command_register_class'] = [
+ //\Custom\Command\CustomCommand::class
+];
+
+/** 服务器启用的外部第三方和内部插件 */
+$config['modules'] = [
+ 'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭
+];
+
+return $config;
diff --git a/test/ZMTest/Mock/mock.php b/test/ZMTest/Mock/mock.php
new file mode 100644
index 00000000..a4abe2da
--- /dev/null
+++ b/test/ZMTest/Mock/mock.php
@@ -0,0 +1,2 @@
+parser = new AnnotationParser();
+ $this->parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
+ try {
+ $this->parser->registerMods();
+ } catch (ReflectionException $e) {
+ throw $e;
+ }
+ }
+
+ public function testAnnotation() {
+ ob_start();
+ $gen = $this->parser->generateAnnotationEvents();
+ $m = $gen[OnStart::class][0]->method;
+ $class = $gen[OnStart::class][0]->class;
+ $c = new $class();
+ try {
+ $c->$m();
+ } catch (Exception $e) {
+ }
+ $result = ob_get_clean();
+ echo $result;
+ $this->assertStringContainsString("我开始了!", $result);
+ }
+
+ public function testAnnotation2() {
+
+ foreach ($this->parser->generateAnnotationEvents() as $k => $v) {
+ foreach ($v as $vs) {
+ $this->assertTrue($vs->method === null || $vs->method != '');
+ $this->assertTrue(strlen($vs->class) > 0);
+ }
+ }
+ }
+
+ public function testAnnotationMap() {
+ $map = $this->parser->getMiddlewareMap();
+ $this->assertContainsEquals("timer", $map[Hello::class]["timer"]);
+ }
+
+ public function testMiddlewares() {
+ $wares = $this->parser->getMiddlewares();
+ $this->assertArrayHasKey("timer", $wares);
+ }
+
+ public function testReqMapping() {
+ $mapping = $this->parser->getReqMapping();
+ $this->assertEquals("index", $mapping["method"]);
+ }
+}
diff --git a/test/ZMTest/Testing/EventDispatcherTest.php b/test/ZMTest/Testing/EventDispatcherTest.php
new file mode 100644
index 00000000..26984427
--- /dev/null
+++ b/test/ZMTest/Testing/EventDispatcherTest.php
@@ -0,0 +1,51 @@
+addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
+ try {
+ $parser->registerMods();
+ } catch (ReflectionException $e) {
+ throw $e;
+ }
+ EventManager::loadEventByParser($parser);
+ $dispatcher = new EventDispatcher(CQCommand::class);
+ $dispatcher->setReturnFunction(function ($result) {
+ echo $result . PHP_EOL;
+ });
+ //$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; });
+ $dispatcher->setRuleFunction(function ($v) { return $v->match == "你好"; });
+ //$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe");
+ ob_start();
+ try {
+ $dispatcher->dispatchEvents();
+ } catch (AnnotationException $e) {
+ }
+ $r = ob_get_clean();
+ echo $r;
+ $this->assertStringContainsString("你好啊", $r);
+ }
+}
diff --git a/test/ZMTest/Testing/ModuleTest.php b/test/ZMTest/Testing/ModuleTest.php
new file mode 100644
index 00000000..a42644f6
--- /dev/null
+++ b/test/ZMTest/Testing/ModuleTest.php
@@ -0,0 +1,27 @@
+randNum(["随机数", "1", "5"]);
+ $out = ob_get_clean();
+ $this->assertEquals("随机数是:1\n", $out);
+ }
+}
diff --git a/test/test.php b/test/test.php
new file mode 100644
index 00000000..13ae52ed
--- /dev/null
+++ b/test/test.php
@@ -0,0 +1,64 @@
+ 'MyController'));
+$routes = new RouteCollection();
+$routes->add('route_name', $route);
+
+
+
+$route = new Route(
+ '/archive/{month}', // path
+ array('controller' => 'showArchive', 'asd' => 'feswf'), // default values
+ array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
+ array(), // options
+ '', // host
+ array(), // schemes
+ array() // methods
+);
+
+// ...
+
+$routes->add('date', new Route(
+ '/archive/{month}', // path
+ array('controller' => 'showArchive', 'asd' => 'feswf'), // default values
+ array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements
+ array(), // options
+ '', // host
+ array(), // schemes
+ array() // methods
+));
+
+$route = new Route('/archive/test');
+
+$routes->add('qwerty', $route);
+
+$route = new Route('/');
+
+$routes->add('root', $route);
+
+$context = new RequestContext();
+
+$matcher = new UrlMatcher($routes, $context);
+
+//$parameters = $matcher->match('/test/foo');var_dump($parameters);
+
+$parameters = $matcher->match('/archive/2012-01');
+var_dump($parameters);
+// array(
+// 'controller' => 'showArchive',
+// 'month' => '2012-01',
+// 'subdomain' => 'www',
+// '_route' => ...
+// )
+
+$parameters = $matcher->match('/');
+var_dump($parameters);
+
+$sub = new RouteCollection();