mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 12:44:51 +08:00
update to v2.4.0 (build 399)
add CheckConfigCommand.php add config update record docs adjust swoole version to 4.5.0 fix stop and reload bugs add $_running_annotation add remote terminal update global config add timer tick exception handler add zm_xxx global functions add isAtMe(), splitCommand(), matchCommand() function for MessageUtil add workerAction(), sendActionToWorker(), resumeAllWorkerCoroutines() functions for ProcessManager optimize CQCommand match function add custom TerminalCommand annotation add TuringAPI add getReloadableFiles() function for ZMUtil
This commit is contained in:
parent
28f7f20728
commit
6155236d3c
@ -1,3 +0,0 @@
|
||||
FROM zmbot/swoole:latest
|
||||
|
||||
# TODO: auto-setup entrypoint
|
||||
14
SECURITY.md
14
SECURITY.md
@ -1,14 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.0 | :white_check_mark: |
|
||||
| 1.6.x | :white_check_mark: |
|
||||
| 1.1.x | :x: |
|
||||
| 1.0.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a bug which is safety related, you should post a new issue named **Security Issue**, and I will check it as soon as possible.
|
||||
@ -112,22 +112,19 @@ $config['server_event_handler_class'] = [
|
||||
// 这里添加例如 \ZM\Event\ServerEventHandler::class 这样的启动注解类
|
||||
];
|
||||
|
||||
/** 服务器启用的外部第三方和内部插件 */
|
||||
$config['modules'] = [
|
||||
'onebot' => [ // 机器人解析模块,关闭后无法使用如@CQCommand等注解
|
||||
/** 机器人解析模块,关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
|
||||
$config['onebot'] = [
|
||||
'status' => true,
|
||||
'single_bot_mode' => false
|
||||
],
|
||||
'http_proxy_server' => [ // 一个内置的简单HTTP代理服务器,目前还没有认证功能,预计2.4.0版本完成
|
||||
'single_bot_mode' => false,
|
||||
'message_level' => 99999
|
||||
];
|
||||
|
||||
/** 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */
|
||||
$config['remote_terminal'] = [
|
||||
'status' => false,
|
||||
'host' => '0.0.0.0',
|
||||
'port' => 8083,
|
||||
'swoole_set_override' => [
|
||||
'backlog' => 128,
|
||||
'buffer_output_size' => 1024 * 1024 * 128,
|
||||
'socket_buffer_size' => 1024 * 1024 * 1
|
||||
]
|
||||
],
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 20002,
|
||||
'token' => ''
|
||||
];
|
||||
|
||||
return $config;
|
||||
|
||||
20
docs/FAQ.md
20
docs/FAQ.md
@ -1,3 +1,23 @@
|
||||
# FAQ
|
||||
|
||||
这里会写一些常见的疑难解答。
|
||||
|
||||
## 启动时报错 Address already in use
|
||||
|
||||
1. 检查是否开启了两次框架,每个端口只能开启一个框架。
|
||||
2. 如果是之前已经在 20001 端口或者你设置了别的应用同样占用此端口,更换配置文件 `global.php` 中的 port 即可。
|
||||
3. 如果是之前框架成功启动,但是使用 Ctrl+C 停止后再次启动导致的报错,请根据下面的步骤来检查是否存在僵尸进程。
|
||||
|
||||
- 如果系统内装有 `htop`,可以直接在 `htop` 中开启 Tree 模式并使用 filter 过滤 php,检查残留的框架进程。
|
||||
- 如果系统没有 `htop`,使用 `ps aux | grep vendor/bin/start | grep -v grep` 如果存在进程,请使用以下命令尝试杀掉:
|
||||
|
||||
```bash
|
||||
# 如果确定框架的数据都已保存且没有需要保存的缓存数据,直接杀掉 SIGKILL 即可,输入下面这条
|
||||
ps aux | grep vendor/bin/start | grep -v grep | awk '{print $2}' | xargs kill -9
|
||||
|
||||
# 如果不确定框架是不是还继续运行,想尝试正常关闭(走一遍储存保存数据的事件),使用下面这条
|
||||
# 首先使用 'ps aux | grep vendor/bin/start | grep -v grep' 找到进程中第二列最小的pid
|
||||
# 然后使用下面的这条命令,假设最小的pid是23643
|
||||
kill -INT 23643
|
||||
# 如果使用 ps aux 看不到框架相关进程,证明关闭成功,否则需要使用第一条强行杀死
|
||||
```
|
||||
@ -27,3 +27,44 @@
|
||||
TODO:先放一放。
|
||||
```
|
||||
|
||||
## ZM\Entity\MatchObject
|
||||
|
||||
此类是调用方法 `MessageUtil::matchCommand()` 返回的对象体,含有匹配成功与否和匹配到的注解相关的信息。
|
||||
|
||||
### 属性
|
||||
|
||||
- `$match`:`bool` 类型,返回匹配是否成功
|
||||
- `$object`:`CQCommand` 注解类,如果匹配成功则返回对应的 `@CQCommand` 信息
|
||||
- `match`:`array` 类型,如果匹配成功则返回匹配到的参数
|
||||
|
||||
```php
|
||||
// 假设我有一个注解事件 @CQCommand(match="你好"),绑定的函数是 \Module\Example\Hello 下的 hello123()
|
||||
|
||||
$obj = MessageUtil::matchCommand("你好 我叫顺溜 我今年二十八", ctx()->getData());
|
||||
/* 以下是返回信息,仅供参考
|
||||
$obj->match ==> true
|
||||
$obj->object ==> \ZM\Annotation\CQ\CQCommand: (
|
||||
match: "你好",
|
||||
pattern: "",
|
||||
regex: "",
|
||||
start_with: "",
|
||||
end_with: "",
|
||||
keyword: "",
|
||||
alias: [],
|
||||
message_type: "",
|
||||
user_id: 0,
|
||||
group_id: 0,
|
||||
discuss_id: 0,
|
||||
level: 20,
|
||||
method: "hello123",
|
||||
class: \Module\Example\Hello::class
|
||||
)
|
||||
$obj->match ==> [
|
||||
"我叫顺溜",
|
||||
"我今年二十八"
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
3
docs/advanced/task-worker.md
Normal file
3
docs/advanced/task-worker.md
Normal file
@ -0,0 +1,3 @@
|
||||
# 使用 TaskWorker 进程处理密集运算
|
||||
|
||||
> 新开个坑,有时间补上。(__填坑标记__)
|
||||
0
docs/assets/img/image-20210321193956832.png
Normal file
0
docs/assets/img/image-20210321193956832.png
Normal file
@ -134,10 +134,6 @@ set_coroutine_params(["data" => [
|
||||
|
||||
别名:`context()`,获取当前协程的上下文,见 [上下文](/component/context/)。
|
||||
|
||||
## zm_debug()
|
||||
|
||||
同 `Console::debug($msg)`。
|
||||
|
||||
## zm_sleep()
|
||||
|
||||
协程版 `sleep()` 函数。
|
||||
@ -256,3 +252,61 @@ bot()->sendPrivateMsg(123456, "你好啊!!");
|
||||
定义:`getAllFdByConnectType(string $type = 'default'): array`
|
||||
|
||||
当 `$type` 为 `qq` 时,则返回所有 OneBot 机器人接入的 WebSocket 连接号。
|
||||
|
||||
## zm_dump()
|
||||
|
||||
更漂亮地输出变量值,可替代 `var_dump()`。
|
||||
|
||||
```php
|
||||
class Pass {
|
||||
public $foo = 123;
|
||||
public $bar = ["a", "b"];
|
||||
}
|
||||
$pass = new Pass();
|
||||
$pass->obj = true;
|
||||
zm_dump($pass);
|
||||
```
|
||||
|
||||
<img src="../assets/img/image-20210321193956832.png" alt="image-20210321193956832" style="zoom:50%;" />
|
||||
|
||||
## zm_config()
|
||||
|
||||
> v2.4.0 起可用。
|
||||
|
||||
同 `ZMConfig::get()`。
|
||||
|
||||
定义:`zm_config($name, $key = null)`。
|
||||
|
||||
有关 ZMConfig 模块的说明,见 [指南 - 基本配置](/guide/basic-config/)。
|
||||
|
||||
```php
|
||||
zm_config("global"); //等同于 ZMConfig::get("global");
|
||||
zm_config("global", "swoole"); //等同于 ZMConfig::get("global", "swoole");
|
||||
```
|
||||
|
||||
## zm_info()
|
||||
|
||||
> v2.4.0 起可用。(下面的 log 类也一样)
|
||||
|
||||
同 `Console::info($msg)`。
|
||||
|
||||
## zm_debug()
|
||||
|
||||
同 `Console::debug($msg)`。
|
||||
|
||||
## zm_warning()
|
||||
|
||||
同 `Console::warning($msg)`。
|
||||
|
||||
## zm_success()
|
||||
|
||||
同 `Console::success($msg)`。
|
||||
|
||||
## zm_error()
|
||||
|
||||
同 `Console::error($msg)`。
|
||||
|
||||
## zm_verbose()
|
||||
|
||||
同 `Console::verbose($msg)`。
|
||||
|
||||
|
||||
89
docs/component/message-util.md
Normal file
89
docs/component/message-util.md
Normal file
@ -0,0 +1,89 @@
|
||||
# MessageUtil 消息处理工具类
|
||||
|
||||
类定义:`\ZM\Utils\MessageUtil`
|
||||
|
||||
> 2.3.0 版本起可用。
|
||||
|
||||
这里放置一些机器人聊天消息处理的便捷静态方法,例如下载图片等。
|
||||
|
||||
## 方法
|
||||
|
||||
### downloadCQImage()
|
||||
|
||||
下载用户消息中所带的所有图片,并返回文件路径。
|
||||
|
||||
定义:`downloadCQImage($msg, $path = null)`
|
||||
|
||||
参数 `$msg` 为带图片的用户消息,例如 `你好啊!\n[CQ:image,file=a.jpg,url=https://zhamao.xin/file/hello.jpg]`
|
||||
|
||||
参数 `$path` 为图片下载的路径,如果不填(默认 null)则指定为 `zm_data/images/` 目录,且不存在会自动创建。
|
||||
|
||||
```php
|
||||
$r = MessageUtil::downloadCQImage("你好啊!\n[CQ:image,file=a.jpg,url=https://zhamao.xin/file/hello.jpg]");
|
||||
/*
|
||||
$r == [
|
||||
"/path-to/zhamao-framework/zm_data/images/a.jpg"
|
||||
];
|
||||
*/
|
||||
```
|
||||
|
||||
如果返回的是空数组 `[ ]`,则表明消息中没有图片。如果返回的是 `false`,则表明其中至少一张下载失败或路径有误。
|
||||
|
||||
### containsImage()
|
||||
|
||||
检查消息中是否含有图片。
|
||||
|
||||
定义:`containsImage($msg)`
|
||||
|
||||
返回:`bool`,你懂的,true 就是有,false 就没有。
|
||||
|
||||
```php
|
||||
MessageUtil::containsImage("[CQ:image,file=a.jpg,url=http://xxx]"); // true
|
||||
MessageUtil::containsImage("[CQ:face,id=140] 咦,这是一条带表情的消息"); // false
|
||||
```
|
||||
|
||||
### getImageCQFromLocal()
|
||||
|
||||
通过文件路径获取图片的发送 CQ 码。
|
||||
|
||||
定义:`getImageCQFromLocal($file, $type = 0)`
|
||||
|
||||
参数 `$file` 为图片的绝对路径。
|
||||
|
||||
返回:图片的 CQ 码,如 `[CQ:image,file=xxxxx]`
|
||||
|
||||
参数 `$type`:
|
||||
|
||||
- `0`:以 base64 的方式发送图片,返回结果如 `[CQ:image,file=base64://xxxxxx]`
|
||||
- `1`:以 `file://` 本地文件的方式发送图片,返回结果如 `[CQ:image,file=file:///path-to/images/a.jpg]`
|
||||
- `2`:返回图片的 http:// CQ 码(默认为 /images/ 路径就是文件对应所在的目录),如 `[CQ:image,file=http://127.0.0.1:20001/images/a.jpg]`
|
||||
|
||||
### splitCommand()
|
||||
|
||||
切割用户消息为数组形式(`@CQCommand` 就是使用此方式切割的)
|
||||
|
||||
定义:`splitCommand($msg): array`
|
||||
|
||||
返回:数组,切分后的。
|
||||
|
||||
!!! tip "为什么不直接使用 explode 呢"
|
||||
|
||||
因为 `explode()` 只会简单粗暴的切割字符串,假设用户输入的消息中两个词中间有多个空格,则会有空的词出现。例如 `你好 我是一个长空格`。此函数会将多个空格当作一个空格来对待。
|
||||
|
||||
```php
|
||||
MessageUtil::splitCommand("你好 我是傻瓜\n我是傻瓜二号"); // ["你好","我是傻瓜","我是傻瓜二号"]
|
||||
MessageUtil::splitCommand("我有 三个空格"); // ["我有","三个空格"]
|
||||
```
|
||||
|
||||
### matchCommand()
|
||||
|
||||
匹配一条消息到 `@CQCommand` 规则的注解事件,返回要执行的类和函数位置。
|
||||
|
||||
定义:`matchCommand($msg, $obj)`
|
||||
|
||||
参数 `$msg` 为消息内容。
|
||||
|
||||
参数 `$obj` 为事件的对象,可使用 `ctx()->getData()` 获取原先的事件体(仅限 OneBot 消息类型事件中使用)
|
||||
|
||||
返回:`\ZM\Entity\MatchObject` 对象,含有匹配成功与否,匹配到的注解对象,匹配到的分割词等,见 []
|
||||
|
||||
54
docs/component/route-manager.md
Normal file
54
docs/component/route-manager.md
Normal file
@ -0,0 +1,54 @@
|
||||
# HTTP 路由管理
|
||||
|
||||
HTTP 路由管理器用作管理炸毛框架内 `@RequestMapping` 和静态目录的路由操作的,可在运行过程中编写添加路由。
|
||||
|
||||
类定义:`\ZM\Http\RouteManager`
|
||||
|
||||
> 2.3.0 版本起可用。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
因为炸毛框架的路由实现是不基于跨进程的共享内存的,所以每次使用这里面的工具函数都需要单独在所有 Worker 进程中执行一次,最好的办法就是在启动框架时执行(`@OnStart(-1)` 即可,代表此注解事件将在每个工作进程中都被执行一次)。
|
||||
|
||||
## 方法
|
||||
|
||||
### importRouteByAnnotation()
|
||||
|
||||
通过注解类导入路由。(注:此方法一般为框架内部使用)
|
||||
|
||||
定义:`importRouteByAnnotation(RequestMapping $vss, $method, $class, $methods_annotations)`
|
||||
|
||||
参数 `$vss`:RequestMapping 注解类,类中定义 `route` 和 `request_method` 即可。
|
||||
|
||||
参数 `$method, $class`:执行的目标注解事件函数位置,比如 `$class = \Module\Example\Hello::class`,`$method = 'hitokoto'`。
|
||||
|
||||
参数 `$methods_annotations`:需要绑定的 Controller 注解类数组,一般数组内建议只带有一个 Controller,如 `[$controller]`。
|
||||
|
||||
### addStaticFileRoute()
|
||||
|
||||
添加一个单目录(此目录下无子目录,只有文件)并绑定为一个路由。
|
||||
|
||||
定义:`addStaticFileRoute($route, $path)`
|
||||
|
||||
参数 `$route`:绑定的目标路由,如 `/images/`。
|
||||
|
||||
参数 `$path`:绑定的文件目录位置,如 `/root/zhamao-framework-starter/zm_data/images/`。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnStart(-1)
|
||||
*/
|
||||
public function onStart() {
|
||||
RouteManager::addStaticFileRoute("/images/", DataProvider::getDataFolder()."images/");
|
||||
}
|
||||
```
|
||||
|
||||
## 属性
|
||||
|
||||
### RouteManager::$routes
|
||||
|
||||
此为存放路由树的变量,请谨慎操作。
|
||||
|
||||
定义:`\Symfony\Component\Routing\RouteCollection | null`
|
||||
|
||||
炸毛框架使用了 Symfony 框架的 route 组件,有关详情请查阅 [文档](https://symfony.com/doc/current/routing.html)。
|
||||
26
docs/component/task-worker.md
Normal file
26
docs/component/task-worker.md
Normal file
@ -0,0 +1,26 @@
|
||||
# TaskManager 工作进程管理
|
||||
|
||||
此类管理的是 TaskWorker 相关工作。有关使用 TaskWorker 的教程,见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)
|
||||
|
||||
类定义:`\ZM\Utils\TaskManager`
|
||||
|
||||
使用 TaskWorker 需要先在 `global.php` 配置文件中开启!
|
||||
|
||||
## 方法
|
||||
|
||||
### runTask()
|
||||
|
||||
在 TaskWorker 运行任务。
|
||||
|
||||
定义:`runTask($task_name, $timeout = -1, ...$params)`
|
||||
|
||||
参数 `$task_name`:对应 `@OnTask` 注解绑定的任务函数。
|
||||
|
||||
参数 `$timeout`:等待任务函数最长运行的时间(秒),如果超过此时间将返回 false。
|
||||
|
||||
参数 `剩余`:将变量传入 TaskWorker 进程,除 Closure,资源类型外,可序列化的变量均可。
|
||||
|
||||
```php
|
||||
TaskManager::runTask("heavy_task", 100, "param1", "param2");
|
||||
```
|
||||
|
||||
@ -21,3 +21,47 @@ class ASD{
|
||||
ZMUtil::getModInstance(ASD::class)->test = 5;
|
||||
```
|
||||
|
||||
## ZMUtil::getReloadableFiles()
|
||||
|
||||
返回可通过热重启(reload)来重新加载的 php 文件列表。
|
||||
|
||||
以下是示例模块下的例子(直接拉取最新的框架源码并运行框架后获取的)。
|
||||
|
||||
```php
|
||||
array:31 [
|
||||
94 => "src/ZM/Context/Context.php"
|
||||
95 => "src/ZM/Context/ContextInterface.php"
|
||||
96 => "src/ZM/Annotation/AnnotationParser.php"
|
||||
97 => "src/Custom/Annotation/Example.php"
|
||||
98 => "src/ZM/Annotation/Interfaces/CustomAnnotation.php"
|
||||
99 => "src/Module/Example/Hello.php"
|
||||
100 => "src/ZM/Annotation/Swoole/OnStart.php"
|
||||
101 => "src/ZM/Annotation/CQ/CQCommand.php"
|
||||
102 => "src/ZM/Annotation/Interfaces/Level.php"
|
||||
103 => "src/ZM/Annotation/Command/TerminalCommand.php"
|
||||
104 => "src/ZM/Annotation/Http/RequestMapping.php"
|
||||
105 => "src/ZM/Annotation/Http/RequestMethod.php"
|
||||
106 => "src/ZM/Annotation/Http/Middleware.php"
|
||||
107 => "src/ZM/Annotation/Interfaces/ErgodicAnnotation.php"
|
||||
108 => "src/ZM/Annotation/Swoole/OnOpenEvent.php"
|
||||
109 => "src/ZM/Annotation/Swoole/OnSwooleEventBase.php"
|
||||
110 => "src/ZM/Annotation/Interfaces/Rule.php"
|
||||
111 => "src/ZM/Annotation/Swoole/OnCloseEvent.php"
|
||||
112 => "src/ZM/Annotation/Swoole/OnRequestEvent.php"
|
||||
113 => "src/ZM/Http/RouteManager.php"
|
||||
114 => "vendor/symfony/routing/RouteCollection.php"
|
||||
115 => "vendor/symfony/routing/Route.php"
|
||||
116 => "src/Module/Middleware/TimerMiddleware.php"
|
||||
117 => "src/ZM/Http/MiddlewareInterface.php"
|
||||
118 => "src/ZM/Annotation/Http/MiddlewareClass.php"
|
||||
119 => "src/ZM/Annotation/Http/HandleBefore.php"
|
||||
120 => "src/ZM/Annotation/Http/HandleAfter.php"
|
||||
121 => "src/ZM/Annotation/Http/HandleException.php"
|
||||
122 => "src/ZM/Event/EventManager.php"
|
||||
123 => "src/ZM/Annotation/Swoole/OnSwooleEvent.php"
|
||||
124 => "src/ZM/Event/EventDispatcher.php"
|
||||
]
|
||||
```
|
||||
|
||||
> 为什么不能重载所有文件?因为框架是多进程模型,而重载相当于只重新启动了一次 Worker 进程,Manager 和 Master 进程未重启,所以被 Manager、Master 进程已经加载的 PHP 文件无法使用 reload 命令重新加载。详见 [进阶 - 进程间隔离](/advanced/multi-process/#_5)。
|
||||
|
||||
|
||||
@ -262,6 +262,27 @@
|
||||
|
||||
无。
|
||||
|
||||
## TerminalCommand()
|
||||
|
||||
添加一个远程终端的自定义命令。(2.4.0 版本起可用)
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | --------------------------------------- |
|
||||
| 名称 | `@TerminalCommand` |
|
||||
| 触发前提 | 连接到远程终端可触发 |
|
||||
| 命名空间 | `ZM\Annotation\Command\TerminalCommand` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 默认 |
|
||||
| ----------- | ------------------------------ | ---- |
|
||||
| command | `string`,**必填**,命令字符串 | |
|
||||
| description | `string`,要显示的帮助文本 | 空 |
|
||||
|
||||
## 示例1(机器人连接框架后输出信息)
|
||||
|
||||
```php
|
||||
@ -406,3 +427,6 @@ public function onCrawl() {
|
||||
}
|
||||
```
|
||||
|
||||
## 示例6(创建一个远程终端命令并调试框架)
|
||||
|
||||
> 开个坑,以后填。(__填坑标记__)
|
||||
25
docs/update/config.md
Normal file
25
docs/update/config.md
Normal file
@ -0,0 +1,25 @@
|
||||
# 配置文件变更记录
|
||||
|
||||
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
|
||||
|
||||
## v2.4.0 (build 400)
|
||||
- 调整 `$config['modules']['onebot']` 配置项到 `$config['onebot']`,旧版本的此段会向下兼容,建议更新,
|
||||
- 新增 `$config['remote_terminal']` 远程终端的配置项,新增此段即可。
|
||||
|
||||
更新部分:
|
||||
```php
|
||||
/** 机器人解析模块,关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
|
||||
$config['onebot'] = [
|
||||
'status' => true,
|
||||
'single_bot_mode' => false,
|
||||
'message_level' => 99999
|
||||
];
|
||||
|
||||
/** 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */
|
||||
$config['remote_terminal'] = [
|
||||
'status' => false,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 20002,
|
||||
'token' => ''
|
||||
];
|
||||
```
|
||||
@ -1,5 +1,39 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.4.0(build 400)
|
||||
|
||||
> 更新时间:2021.3.24
|
||||
|
||||
- 新增:检查全局配置文件的命令
|
||||
- 新增:全局配置文件更新记录
|
||||
- 依赖变更:**Swoole 最低版本需要 4.5.0**
|
||||
- 优化:reload 和 stop 命令重载和停止框架的逻辑
|
||||
- 新增:`$_running_annotation` 变量,可在注解事件中的类使用
|
||||
- 新增:远程终端(Remote Terminal),弥补原来删掉的本地终端,通过 nc 命令连接即可
|
||||
- 新增:启动参数 `--worker-num`,`--task-worker-num`,`--remote-terminal`
|
||||
- 更新:全局配置文件结构
|
||||
- 新增:Swoole 计时器报错处理
|
||||
- 新增:全局方法(`zm_dump()`,`zm_error()`,`zm_warning()`,`zm_info()`,`zm_success()`,`zm_verbose()`,`zm_debug()`,`zm_config()`)
|
||||
- 新增:示例模块的图灵机器人和 at 机器人的处理函数
|
||||
- 新增:MessageUtil 工具类新增 `isAtMe(), splitCommand(), matchCommand()` 方法
|
||||
- 新增:ProcessManager 进程管理类新增 `workerAction(), sendActionToWorker(), resumeAllWorkerCoroutines()` 方法
|
||||
- 优化:CQCommand 的匹配逻辑
|
||||
- 新增:支持添加自定义远程终端指令的 `@TerminalCommand` 注解
|
||||
- 新增:图灵机器人 API 封装函数
|
||||
- 新增:ZMUtil 工具杂项类 `getReloadableFiles()` 函数
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
- 修复:部分内部存储存在的内存泄漏问题
|
||||
- 新增:remote_terminal(远程终端)内置模块,可以用 nc 直接远程连接框架(弥补原先删除掉的终端输入)
|
||||
- 新增:`DataProvider::getReloadableFiles()` 返回可通过热重启(reload)来重新加载的 php 文件列表(多用于调试)
|
||||
- 兼容性变更:**要求最低 Swoole 版本为 4.5.0**
|
||||
- 优化:reload 和 stop 重载和停止框架的逻辑,防止卡死
|
||||
- 新增:远程终端命令支持自定义添加(`@TerminalCommand` 注解)
|
||||
|
||||
## v2.3.5 (build 398)
|
||||
|
||||
> 更新时间:2021.3.23
|
||||
@ -15,7 +49,6 @@
|
||||
|
||||
- 规范代码,修复一个小报错的 bug
|
||||
|
||||
|
||||
## v2.3.0
|
||||
|
||||
> 更新时间:2021.3.16
|
||||
|
||||
12
mkdocs.yml
12
mkdocs.yml
@ -72,9 +72,12 @@ nav:
|
||||
- 事件分发器: event/event-dispatcher.md
|
||||
- 框架组件:
|
||||
- 框架组件: component/index.md
|
||||
- 上下文: component/context.md
|
||||
- 聊天机器人组件:
|
||||
- 机器人 API: component/robot-api.md
|
||||
- CQ 码(多媒体消息): component/cqcode.md
|
||||
- 上下文: component/context.md
|
||||
- 机器人消息处理: component/message-util.md
|
||||
- Token 验证: component/access-token.md
|
||||
- 存储:
|
||||
- LightCache 轻量缓存: component/light-cache.md
|
||||
- MySQL 数据库: component/mysql.md
|
||||
@ -82,13 +85,15 @@ nav:
|
||||
- ZMAtomic 原子计数器: component/atomics.md
|
||||
- SpinLock 自旋锁: component/spin-lock.md
|
||||
- 文件管理: component/data-provider.md
|
||||
- HTTP 服务器工具类:
|
||||
- HTTP 和 WebSocket 客户端: component/zmrequest.md
|
||||
- HTTP 路由管理: component/route-manager.md
|
||||
- 协程池: component/coroutine-pool.md
|
||||
- 单例类: component/singleton-trait.md
|
||||
- ZMUtil 杂项: component/zmutil.md
|
||||
- 全局方法: component/global-functions.md
|
||||
- HTTP 和 WebSocket 客户端: component/zmrequest.md
|
||||
- Console 终端: component/console.md
|
||||
- Token 验证: component/access-token.md
|
||||
- TaskWorker 管理: component/task-worker.md
|
||||
- 进阶开发:
|
||||
- 进阶开发: advanced/index.md
|
||||
- 框架剖析: advanced/framework-structure.md
|
||||
@ -97,6 +102,7 @@ nav:
|
||||
- 内部类文件手册: advanced/inside-class.md
|
||||
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
|
||||
- 框架多进程: advanced/multi-process.md
|
||||
- TaskWorker 提高并发: advanced/task-worker.md
|
||||
- 开发实战教程:
|
||||
- 编写管理员才能触发的功能: advanced/example/admin.md
|
||||
- FAQ: FAQ.md
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 385 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 115 KiB |
@ -2,17 +2,23 @@
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
use ZM\Annotation\Http\Middleware;
|
||||
use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\API\CQ;
|
||||
use ZM\API\TuringAPI;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Module\QQBot;
|
||||
use ZM\Requests\ZMRequest;
|
||||
use ZM\Utils\MessageUtil;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
/**
|
||||
@ -67,6 +73,42 @@ class Hello
|
||||
return $obj["hitokoto"] . "\n----「" . $obj["from"] . "」";
|
||||
}
|
||||
|
||||
/**
|
||||
* 图灵机器人的内置实现,在tuling123.com申请一个apikey填入下方变量即可。
|
||||
* @CQCommand(start_with="机器人",end_with="机器人",message_type="group")
|
||||
* @CQMessage(message_type="private",level=1)
|
||||
*/
|
||||
public function turingAPI() {
|
||||
$user_id = ctx()->getUserId();
|
||||
$api = ""; // 请在这里填入你的图灵机器人的apikey
|
||||
if ($api === "") return false; //如果没有填入apikey则此功能关闭
|
||||
if (($this->_running_annotation ?? null) instanceof CQCommand) {
|
||||
$msg = ctx()->getFullArg("我在!有什么事吗?");
|
||||
} else {
|
||||
$msg = ctx()->getMessage();
|
||||
}
|
||||
ctx()->setMessage($msg);
|
||||
if (MessageUtil::matchCommand($msg, ctx()->getData())->status === false) {
|
||||
return TuringAPI::getTuringMsg($msg, $user_id, $api);
|
||||
} else {
|
||||
QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应at机器人的消息
|
||||
* @CQBefore("message")
|
||||
*/
|
||||
public function changeAt() {
|
||||
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
|
||||
$msg = str_replace(CQ::at(ctx()->getRobotId()), "", ctx()->getMessage());
|
||||
ctx()->setMessage("机器人" . $msg);
|
||||
Console::info(ctx()->getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个简单随机数的功能demo
|
||||
* 问法1:随机数 1 20
|
||||
|
||||
118
src/ZM/API/TuringAPI.php
Normal file
118
src/ZM/API/TuringAPI.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class TuringAPI
|
||||
{
|
||||
/**
|
||||
* 请求图灵API,返回图灵的消息
|
||||
* @param $msg
|
||||
* @param $user_id
|
||||
* @param $api
|
||||
* @return string
|
||||
*/
|
||||
public static function getTuringMsg($msg, $user_id, $api) {
|
||||
$origin = $msg;
|
||||
if (($cq = CQ::getCQ($msg)) !== null) {//如有CQ码则去除
|
||||
if ($cq["type"] == "image") {
|
||||
$url = $cq["params"]["url"];
|
||||
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
|
||||
}
|
||||
$msg = trim($msg);
|
||||
}
|
||||
//构建将要发送的json包给图灵
|
||||
$content = [
|
||||
"reqType" => 0,
|
||||
"userInfo" => [
|
||||
"apiKey" => $api,
|
||||
"userId" => $user_id
|
||||
]
|
||||
];
|
||||
if ($msg != "") {
|
||||
$content["perception"]["inputText"]["text"] = $msg;
|
||||
}
|
||||
$msg = trim($msg);
|
||||
if (mb_strlen($msg) < 1 && !isset($url)) return "请说出你想说的话";
|
||||
if (isset($url)) {
|
||||
$content["perception"]["inputImage"]["url"] = $url;
|
||||
$content["reqType"] = 1;
|
||||
}
|
||||
if (!isset($content["perception"])) return "请说出你想说的话";
|
||||
$client = new Client("openapi.tuling123.com", 80);
|
||||
$client->setHeaders(["Content-type" => "application/json"]);
|
||||
$client->post("/openapi/api/v2", json_encode($content, JSON_UNESCAPED_UNICODE));
|
||||
$api_return = json_decode($client->body, true);
|
||||
if (!isset($api_return["intent"]["code"])) return "XD 哎呀,我脑子突然短路了,请稍后再问我吧!";
|
||||
$status = self::getResultStatus($api_return);
|
||||
if ($status !== true) {
|
||||
if ($status == "err:输入文本内容超长(上限150)") return "你的话太多了!!!";
|
||||
if ($api_return["intent"]["code"] == 4003) {
|
||||
return "哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗";
|
||||
}
|
||||
Console::error("图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status);
|
||||
//echo json_encode($r, 128|256);
|
||||
return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?";
|
||||
}
|
||||
$result = $api_return["results"];
|
||||
//Console::info(Console::setColor(json_encode($result, 128 | 256), "green"));
|
||||
$final = "";
|
||||
foreach ($result as $k => $v) {
|
||||
switch ($v["resultType"]) {
|
||||
case "url":
|
||||
$final .= "\n" . $v["values"]["url"];
|
||||
break;
|
||||
case "text":
|
||||
$final .= "\n" . $v["values"]["text"];
|
||||
break;
|
||||
case "image":
|
||||
$final .= "\n" . CQ::image($v["values"]["image"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trim($final);
|
||||
}
|
||||
|
||||
public static function getResultStatus($r) {
|
||||
switch ($r["intent"]["code"]) {
|
||||
case 5000:
|
||||
return "err:无解析结果";
|
||||
case 4000:
|
||||
case 6000:
|
||||
return "err:暂不支持该功能";
|
||||
case 4001:
|
||||
return "err:加密方式错误";
|
||||
case 4005:
|
||||
case 4002:
|
||||
return "err:无功能权限";
|
||||
case 4003:
|
||||
return "err:该apikey没有可用请求次数";
|
||||
case 4007:
|
||||
return "err:apikey不合法";
|
||||
case 4100:
|
||||
return "err:userid获取失败";
|
||||
case 4200:
|
||||
return "err:上传格式错误";
|
||||
case 4300:
|
||||
return "err:批量操作超过限制";
|
||||
case 4400:
|
||||
return "err:没有上传合法userid";
|
||||
case 4500:
|
||||
return "err:userid申请个数超过限制";
|
||||
case 4600:
|
||||
return "err:输入内容为空";
|
||||
case 4602:
|
||||
return "err:输入文本内容超长(上限150)";
|
||||
case 7002:
|
||||
return "err:上传信息失败";
|
||||
case 8008:
|
||||
return "err:服务器错误";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/ZM/Annotation/Command/TerminalCommand.php
Normal file
27
src/ZM/Annotation/Command/TerminalCommand.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Command;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class TerminalCommand
|
||||
* @package ZM\Annotation\Command
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class TerminalCommand
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $command;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $description = "";
|
||||
}
|
||||
56
src/ZM/Command/CheckConfigCommand.php
Normal file
56
src/ZM/Command/CheckConfigCommand.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckConfigCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'check-config';
|
||||
|
||||
private $need_update = false;
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("检查配置文件是否和框架当前版本有更新");
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
if (LOAD_MODE !== 1) {
|
||||
$output->writeln("<error>仅限在Composer依赖模式中使用此命令!");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$current_cfg = WORKING_DIR . "/config/";
|
||||
$remote_cfg = include_once $current_cfg . "/vendor/zhamao/framework/config/global.php";
|
||||
if (file_exists($current_cfg . "global.php")) {
|
||||
$this->check($remote_cfg, "global.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.development.php")) {
|
||||
$this->check($remote_cfg, "global.development.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.staging.php")) {
|
||||
$this->check($remote_cfg, "global.staging.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.production.php")) {
|
||||
$this->check($remote_cfg, "global.production.php", $output);
|
||||
}
|
||||
if ($this->need_update === true) {
|
||||
$output->writeln("<comment>有配置文件需要更新,详情见文档 https://framework.zhamao.xin/update/config.md</comment>");
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function check($remote, $local, OutputInterface $out) {
|
||||
$local_file = include_once WORKING_DIR . "/config/".$local;
|
||||
foreach($remote as $k => $v) {
|
||||
if (!isset($local_file[$k])) {
|
||||
$out->writeln("<info>配置文件 ".$local . " 需要更新!</info>");
|
||||
$this->need_update = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,7 +18,7 @@ class DaemonStopCommand extends DaemonCommand
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
parent::execute($input, $output);
|
||||
system("kill -TERM " . intval($this->daemon_file["pid"]));
|
||||
system("kill -INT " . intval($this->daemon_file["pid"]));
|
||||
unlink(DataProvider::getWorkingDir() . "/.daemon_pid");
|
||||
$output->writeln("<info>成功停止!</info>");
|
||||
return Command::SUCCESS;
|
||||
|
||||
@ -23,8 +23,11 @@ class RunServerCommand extends Command
|
||||
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
|
||||
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
|
||||
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
|
||||
new InputOption("remote-terminal", null, null, "启用远程终端,配置使用global.php中的"),
|
||||
new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
|
||||
new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
|
||||
new InputOption("worker-num", null, InputOption::VALUE_REQUIRED, "启动框架时运行的 Worker 进程数量"),
|
||||
new InputOption("task-worker-num", null, InputOption::VALUE_REQUIRED, "启动框架时运行的 TaskWorker 进程数量"),
|
||||
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
|
||||
new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"),
|
||||
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
|
||||
|
||||
@ -5,6 +5,7 @@ namespace ZM;
|
||||
|
||||
|
||||
use Exception;
|
||||
use ZM\Command\CheckConfigCommand;
|
||||
use ZM\Command\DaemonReloadCommand;
|
||||
use ZM\Command\DaemonStatusCommand;
|
||||
use ZM\Command\DaemonStopCommand;
|
||||
@ -18,8 +19,8 @@ use ZM\Utils\DataProvider;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
const VERSION_ID = 398;
|
||||
const VERSION = "2.3.5";
|
||||
const VERSION_ID = 399;
|
||||
const VERSION = "2.4.0";
|
||||
|
||||
public function __construct(string $name = 'UNKNOWN') {
|
||||
define("ZM_VERSION_ID", self::VERSION_ID);
|
||||
@ -75,6 +76,9 @@ class ConsoleApplication extends Application
|
||||
new InitCommand(), //初始化用的,用于项目初始化和phar初始化
|
||||
new PureHttpCommand() //纯HTTP服务器指令
|
||||
]);
|
||||
if (LOAD_MODE === 1) {
|
||||
$this->add(new CheckConfigCommand());
|
||||
}
|
||||
/*
|
||||
$command_register = ZMConfig::get("global", "command_register_class") ?? [];
|
||||
foreach ($command_register as $v) {
|
||||
@ -100,7 +104,7 @@ class ConsoleApplication extends Application
|
||||
|
||||
private function selfCheck(): bool {
|
||||
if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19\n");
|
||||
if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !");
|
||||
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die("You must install swoole version >= 4.5.0 !");
|
||||
if (version_compare(PHP_VERSION, "7.2") == -1) die("PHP >= 7.2 required.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -216,7 +216,7 @@ class Context implements ContextInterface
|
||||
* @throws WaitTimeoutException
|
||||
*/
|
||||
public function getArgs($mode, $prompt_msg) {
|
||||
$arg = ctx()->getCache("match");
|
||||
$arg = ctx()->getCache("match") ?? [];
|
||||
switch ($mode) {
|
||||
case ZM_MATCH_ALL:
|
||||
$p = $arg;
|
||||
|
||||
17
src/ZM/Entity/MatchResult.php
Normal file
17
src/ZM/Entity/MatchResult.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Entity;
|
||||
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class MatchResult
|
||||
{
|
||||
/** @var bool */
|
||||
public $status = false;
|
||||
/** @var CQCommand|null */
|
||||
public $object = null;
|
||||
/** @var array */
|
||||
public $match = [];
|
||||
}
|
||||
@ -152,6 +152,7 @@ class EventDispatcher
|
||||
if ($before_result) {
|
||||
try {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
$q_o->_running_annotation = $v;
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
|
||||
$this->store = $q_o->$q_f(...$params);
|
||||
} catch (Exception $e) {
|
||||
@ -188,6 +189,7 @@ class EventDispatcher
|
||||
return false;
|
||||
} else {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
$q_o->_running_annotation = $v;
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
|
||||
$this->store = $q_o->$q_f(...$params);
|
||||
$this->status = self::STATUS_NORMAL;
|
||||
|
||||
@ -17,13 +17,13 @@ use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Timer;
|
||||
use Throwable;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnPipeMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
@ -49,9 +49,9 @@ use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\HttpUtil;
|
||||
use ZM\Utils\ProcessManager;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class ServerEventHandler
|
||||
@ -83,7 +83,7 @@ class ServerEventHandler
|
||||
Process::signal(SIGINT, function () use ($r) {
|
||||
if (zm_atomic("_int_is_reload")->get() === 1) {
|
||||
zm_atomic("_int_is_reload")->set(0);
|
||||
ZMUtil::reload();
|
||||
\server()->reload();
|
||||
} else {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted(SIGINT) on Master.");
|
||||
@ -133,22 +133,31 @@ class ServerEventHandler
|
||||
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
|
||||
LightCache::savePersistence();
|
||||
}
|
||||
Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
}
|
||||
|
||||
/**
|
||||
* @SwooleHandler("WorkerStart")
|
||||
* @param Server $server
|
||||
* @param $worker_id
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onWorkerStart(Server $server, $worker_id) {
|
||||
//if (ZMBuf::atomic("stop_signal")->get() != 0) return;
|
||||
|
||||
Process::signal(SIGINT, function () use ($worker_id, $server) {
|
||||
Timer::clearAll();
|
||||
ProcessManager::resumeAllWorkerCoroutines();
|
||||
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[Coroutine::getCid()]);
|
||||
if ($server->taskworker === false) {
|
||||
Process::signal(SIGUSR1, function() use ($worker_id){
|
||||
Timer::clearAll();
|
||||
ProcessManager::resumeAllWorkerCoroutines();
|
||||
});
|
||||
zm_atomic("_#worker_".$worker_id)->set($server->worker_pid);
|
||||
try {
|
||||
register_shutdown_function(function () use ($server) {
|
||||
$error = error_get_last();
|
||||
@ -456,8 +465,12 @@ class ServerEventHandler
|
||||
}
|
||||
});
|
||||
try {
|
||||
if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
|
||||
if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
$onebot_status = $obb_onebot["status"];
|
||||
if ($conn->getName() === 'qq' && $onebot_status === true) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", $request->fd);
|
||||
}
|
||||
}
|
||||
@ -472,7 +485,6 @@ class ServerEventHandler
|
||||
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
//EventHandler::callSwooleEvent("open", $server, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -503,8 +515,11 @@ class ServerEventHandler
|
||||
}
|
||||
});
|
||||
try {
|
||||
if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
|
||||
if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
if ($conn->getName() === 'qq' && $obb_onebot["status"] === true) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", -1);
|
||||
}
|
||||
}
|
||||
@ -528,70 +543,27 @@ class ServerEventHandler
|
||||
* @param $src_worker_id
|
||||
* @param $data
|
||||
* @throws Exception
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
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 "getWorkerCache":
|
||||
$r = WorkerCache::get($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "setWorkerCache":
|
||||
$r = WorkerCache::set($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "unsetWorkerCache":
|
||||
$r = WorkerCache::unset($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "hasKeyWorkerCache":
|
||||
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "asyncAddWorkerCache":
|
||||
WorkerCache::add($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSubWorkerCache":
|
||||
WorkerCache::sub($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSetWorkerCache":
|
||||
WorkerCache::set($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncUnsetWorkerCache":
|
||||
WorkerCache::unset($data["key"], true);
|
||||
break;
|
||||
case "addWorkerCache":
|
||||
$r = WorkerCache::add($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "subWorkerCache":
|
||||
$r = WorkerCache::sub($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "returnWorkerCache":
|
||||
WorkerCache::$transfer[$data["cid"]] = $data["value"];
|
||||
zm_resume($data["cid"]);
|
||||
break;
|
||||
default:
|
||||
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
|
||||
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
|
||||
return $v->action == $data["action"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($data);
|
||||
break;
|
||||
ProcessManager::workerAction($src_worker_id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SwooleHandler("beforeReload")
|
||||
*/
|
||||
public function onBeforeReload() {
|
||||
for($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
$pid = zm_atomic("_#worker_".$i)->get();
|
||||
Process::kill($pid, SIGUSR1);
|
||||
}
|
||||
|
||||
Console::info(Console::setColor("Reloading server...", "gold"));
|
||||
usleep(800 * 1000);
|
||||
LightCacheInside::unset("wait_api", "wait_api");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -693,18 +665,19 @@ class ServerEventHandler
|
||||
}
|
||||
|
||||
//加载插件
|
||||
$plugins = ZMConfig::get("global", "modules") ?? [];
|
||||
if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
|
||||
if ($plugins["onebot"]) {
|
||||
if ($obb_onebot["status"]) {
|
||||
$obj = new OnSwooleEvent();
|
||||
$obj->class = QQBot::class;
|
||||
$obj->method = 'handle';
|
||||
$obj->method = 'handleByEvent';
|
||||
$obj->type = 'message';
|
||||
$obj->level = $plugins["onebot"]["message_level"] ?? 99999;
|
||||
$obj->level = $obb_onebot["message_level"] ?? 99999;
|
||||
$obj->rule = 'connectIsQQ()';
|
||||
EventManager::addEvent(OnSwooleEvent::class, $obj);
|
||||
if ($plugins["onebot"]["single_bot_mode"]) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", -1);
|
||||
} else {
|
||||
LightCacheInside::set("connect", "conn_fd", -2);
|
||||
|
||||
@ -5,7 +5,9 @@ namespace ZM;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Swoole\Server\Port;
|
||||
use ZM\Annotation\Swoole\OnSetup;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
@ -23,6 +25,7 @@ use Swoole\Runtime;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class Framework
|
||||
@ -35,11 +38,16 @@ class Framework
|
||||
* @var Server
|
||||
*/
|
||||
public static $server;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static $loaded_files = [];
|
||||
/**
|
||||
* @var array|bool|mixed|null
|
||||
*/
|
||||
private $server_set;
|
||||
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
public function __construct($args = []) {
|
||||
$tty_width = $this->getTtyWidth();
|
||||
|
||||
@ -54,7 +62,6 @@ class Framework
|
||||
//定义常量
|
||||
include_once "global_defines.php";
|
||||
|
||||
ZMAtomic::init();
|
||||
try {
|
||||
$sw = ZMConfig::get("global");
|
||||
if (!is_dir($sw["zm_data"])) mkdir($sw["zm_data"]);
|
||||
@ -86,18 +93,23 @@ class Framework
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
$this->server_set = ZMConfig::get("global", "swoole");
|
||||
$this->parseCliArgs(self::$argv);
|
||||
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
|
||||
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
|
||||
|
||||
$this->parseCliArgs(self::$argv, $add_port);
|
||||
$worker = $this->server_set["worker_num"] ?? swoole_cpu_num();
|
||||
define("ZM_WORKER_NUM", $worker);
|
||||
ZMAtomic::init();
|
||||
// 打印初始信息
|
||||
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
|
||||
if (!isset(ZMConfig::get("global", "swoole")["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
else $out["worker"] = ZMConfig::get("global", "swoole")["worker_num"];
|
||||
if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
else $out["worker"] = $this->server_set["worker_num"];
|
||||
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
|
||||
$out["log_level"] = Console::getLevel();
|
||||
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
|
||||
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
|
||||
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
|
||||
$out["task_worker"] = ZMConfig::get("global", "swoole")["task_worker_num"];
|
||||
if (isset($this->server_set["task_worker_num"])) {
|
||||
$out["task_worker"] = $this->server_set["task_worker_num"];
|
||||
}
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
|
||||
$conf = ZMConfig::get("global", "sql_config");
|
||||
@ -114,12 +126,88 @@ class Framework
|
||||
$out["php_version"] = PHP_VERSION;
|
||||
$out["swoole_version"] = SWOOLE_VERSION;
|
||||
}
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal");
|
||||
$out["terminal"] = $conf["host"] . ":" . $conf["port"];
|
||||
}
|
||||
|
||||
$out["working_dir"] = DataProvider::getWorkingDir();
|
||||
self::printProps($out, $tty_width, $args["log-theme"] === null);
|
||||
|
||||
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal") ?? [
|
||||
'status' => true,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 20002,
|
||||
'token' => ''
|
||||
];
|
||||
$welcome_msg = Console::setColor("Welcome! You can use `help` for usage.", "green");
|
||||
/** @var Port $port */
|
||||
$port = self::$server->listen($conf["host"], $conf["port"], SWOOLE_SOCK_TCP);
|
||||
$port->set([
|
||||
'open_http_protocol' => false
|
||||
]);
|
||||
$port->on('connect', function (?\Swoole\Server $serv, $fd) use ($port, $welcome_msg, $conf) {
|
||||
ManagerGM::pushConnect($fd, "terminal");
|
||||
$serv->send($fd, file_get_contents(working_dir() . "/config/motd.txt"));
|
||||
if (!empty($conf["token"])) {
|
||||
$serv->send($fd, "Please input token: ");
|
||||
} else {
|
||||
$serv->send($fd, $welcome_msg . "\n>>> ");
|
||||
}
|
||||
});
|
||||
|
||||
$port->on('receive', function ($serv, $fd, $reactor_id, $data) use ($welcome_msg, $conf) {
|
||||
ob_start();
|
||||
try {
|
||||
$arr = LightCacheInside::get("light_array", "input_token") ?? [];
|
||||
if (empty($arr[$fd] ?? '')) {
|
||||
if ($conf["token"] != '') {
|
||||
$token = trim($data);
|
||||
if ($token === $conf["token"]) {
|
||||
SpinLock::transaction("input_token", function () use ($fd, $token) {
|
||||
$arr = LightCacheInside::get("light_array", "input_token");
|
||||
$arr[$fd] = $token;
|
||||
LightCacheInside::set("light_array", "input_token", $arr);
|
||||
});
|
||||
$serv->send($fd, Console::setColor("Auth success!!\n", "green"));
|
||||
$serv->send($fd, $welcome_msg . "\n>>> ");
|
||||
} else {
|
||||
$serv->send($fd, Console::setColor("Auth failed!!\n", "red"));
|
||||
$serv->close($fd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (trim($data) == "exit" || trim($data) == "q") {
|
||||
$serv->send($fd, Console::setColor("Bye!\n", "blue"));
|
||||
$serv->close($fd);
|
||||
return;
|
||||
}
|
||||
Terminal::executeCommand(trim($data));
|
||||
} catch (Exception $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
} catch (Error $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
|
||||
$r = ob_get_clean();
|
||||
if (!empty($r)) $serv->send($fd, $r);
|
||||
if (!in_array(trim($data), ['r', 'reload', 'stop'])) $serv->send($fd, ">>> ");
|
||||
});
|
||||
|
||||
$port->on('close', function ($serv, $fd) {
|
||||
ManagerGM::popConnect($fd);
|
||||
//echo "Client: Close.\n";
|
||||
});
|
||||
}
|
||||
|
||||
self::$server->set($this->server_set);
|
||||
Console::setServer(self::$server);
|
||||
self::printMotd($tty_width);
|
||||
@ -195,9 +283,9 @@ class Framework
|
||||
}
|
||||
|
||||
public function start() {
|
||||
self::$loaded_files = get_included_files();
|
||||
self::$server->start();
|
||||
zm_atomic("server_is_stopped")->set(1);
|
||||
Console::setLevel(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,14 +331,29 @@ class Framework
|
||||
/**
|
||||
* 解析命令行的 $argv 参数们
|
||||
* @param $args
|
||||
* @throws Exception
|
||||
* @param $add_port
|
||||
*/
|
||||
private function parseCliArgs($args) {
|
||||
private function parseCliArgs($args, &$add_port) {
|
||||
$coroutine_mode = true;
|
||||
global $terminal_id;
|
||||
$terminal_id = uuidgen();
|
||||
foreach ($args as $x => $y) {
|
||||
switch ($x) {
|
||||
case 'worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set["worker_num"] = intval($y);
|
||||
} else {
|
||||
Console::warning("Invalid worker num! Turn to default value (".($this->server_set["worker_num"] ?? swoole_cpu_num()).")");
|
||||
}
|
||||
break;
|
||||
case 'task-worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set["task_worker_num"] = intval($y);
|
||||
$this->server_set["task_enable_coroutine"] = true;
|
||||
} else {
|
||||
Console::warning("Invalid worker num! Turn to default value (0)");
|
||||
}
|
||||
break;
|
||||
case 'disable-coroutine':
|
||||
if ($y) {
|
||||
$coroutine_mode = false;
|
||||
@ -296,6 +399,9 @@ class Framework
|
||||
Console::$theme = $y;
|
||||
}
|
||||
break;
|
||||
case 'remote-terminal':
|
||||
$add_port = true;
|
||||
break;
|
||||
case 'show-php-ver':
|
||||
default:
|
||||
//Console::info("Calculating ".$x);
|
||||
@ -304,6 +410,7 @@ class Framework
|
||||
}
|
||||
}
|
||||
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
|
||||
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
|
||||
}
|
||||
|
||||
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
|
||||
|
||||
@ -15,6 +15,8 @@ use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Utils\CoMessage;
|
||||
use ZM\Utils\MessageUtil;
|
||||
use ZM\Utils\SingletonTrait;
|
||||
|
||||
/**
|
||||
* Class QQBot
|
||||
@ -22,20 +24,28 @@ use ZM\Utils\CoMessage;
|
||||
*/
|
||||
class QQBot
|
||||
{
|
||||
/**
|
||||
* @throws InterruptException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle() {
|
||||
try {
|
||||
use SingletonTrait;
|
||||
|
||||
public function handleByEvent() {
|
||||
$data = json_decode(context()->getFrame()->data, true);
|
||||
$this->handle($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param int $level
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public function handle($data, $level = 0) {
|
||||
try {
|
||||
if ($level > 10) return;
|
||||
set_coroutine_params(["data" => $data]);
|
||||
if (isset($data["post_type"])) {
|
||||
//echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
|
||||
ctx()->setCache("level", 0);
|
||||
ctx()->setCache("level", $level);
|
||||
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
|
||||
if ($data["post_type"] != "meta_event") {
|
||||
$r = $this->dispatchBeforeEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
$r = $this->dispatchBeforeEvents($data, "pre"); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
if ($r->store === "block") EventDispatcher::interrupt();
|
||||
}
|
||||
//Console::warning("最上数据包:".json_encode($data));
|
||||
@ -43,6 +53,10 @@ class QQBot
|
||||
if (isset($data["echo"]) || isset($data["post_type"])) {
|
||||
if (CoMessage::resumeByWS()) EventDispatcher::interrupt();
|
||||
}
|
||||
if (($data["post_type"] ?? "meta_event") != "meta_event") {
|
||||
$r = $this->dispatchBeforeEvents($data, "post"); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
if ($r->store === "block") EventDispatcher::interrupt();
|
||||
}
|
||||
if (isset($data["post_type"])) $this->dispatchEvents($data);
|
||||
else $this->dispatchAPIResponse($data);
|
||||
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
|
||||
@ -52,14 +66,21 @@ class QQBot
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param $time
|
||||
* @return EventDispatcher
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dispatchBeforeEvents($data): EventDispatcher {
|
||||
public function dispatchBeforeEvents($data, $time): EventDispatcher {
|
||||
$before = new EventDispatcher(CQBefore::class);
|
||||
if ($time === "pre") {
|
||||
$before->setRuleFunction(function($v) use ($data){
|
||||
return $v->cq_event == $data["post_type"];
|
||||
return $v->level >= 200 && $v->cq_event == $data["post_type"];
|
||||
});
|
||||
} else {
|
||||
$before->setRuleFunction(function($v) use ($data){
|
||||
return $v->level < 200 && $v->cq_event == $data["post_type"];
|
||||
});
|
||||
}
|
||||
$before->setReturnFunction(function ($result) {
|
||||
if (!$result) EventDispatcher::interrupt("block");
|
||||
});
|
||||
@ -76,58 +97,20 @@ class QQBot
|
||||
//Console::warning("最xia数据包:".json_encode($data));
|
||||
switch ($data["post_type"]) {
|
||||
case "message":
|
||||
$word = explodeMsg(str_replace("\r", "", context()->getMessage()));
|
||||
if (empty($word)) $word = [""];
|
||||
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);
|
||||
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
|
||||
});
|
||||
$dispatcher->dispatchEvents();
|
||||
if ($dispatcher->status == EventDispatcher::STATUS_INTERRUPTED) EventDispatcher::interrupt();
|
||||
zm_dump(ctx()->getData());
|
||||
$s = MessageUtil::matchCommand(ctx()->getMessage(), ctx()->getData());
|
||||
if ($s->status !== false) {
|
||||
if (!empty($s->match)) ctx()->setCache("match", $s->match);
|
||||
$dispatcher->dispatchEvent($s->object, null);
|
||||
if (is_string($dispatcher->store)) ctx()->reply($dispatcher->store);
|
||||
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
|
||||
}
|
||||
|
||||
//分发CQMessage事件
|
||||
$msg_dispatcher = new EventDispatcher(CQMessage::class);
|
||||
|
||||
@ -7,7 +7,6 @@ namespace ZM\Store;
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
use ZM\Annotation\Swoole\OnSave;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\ZMException;
|
||||
@ -182,17 +181,12 @@ class LightCache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param false $only_worker
|
||||
* 这个只能在唯一一个工作进程中执行
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function savePersistence($only_worker = false) {
|
||||
// 下面将OnSave激活一下
|
||||
if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) {
|
||||
public static function savePersistence() {
|
||||
$dispatcher = new EventDispatcher(OnSave::class);
|
||||
$dispatcher->dispatchEvents();
|
||||
}
|
||||
|
||||
if($only_worker) return;
|
||||
|
||||
if (self::$kv_table === null) return;
|
||||
$r = [];
|
||||
@ -207,8 +201,7 @@ class LightCache
|
||||
$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\"!");
|
||||
}
|
||||
|
||||
|
||||
Console::verbose("Saved.");
|
||||
}
|
||||
|
||||
private static function checkExpire($key) {
|
||||
|
||||
@ -18,6 +18,7 @@ class LightCacheInside
|
||||
self::createTable("wait_api", 3, 65536);
|
||||
self::createTable("connect", 3, 64); //用于存单机器人模式下的机器人fd的
|
||||
self::createTable("static_route", 64, 256);//用于存储
|
||||
self::createTable("light_array", 8, 512, 0.6);
|
||||
} catch (ZMException $e) {
|
||||
return false;
|
||||
} //用于存协程等待的状态内容的
|
||||
@ -59,10 +60,11 @@ class LightCacheInside
|
||||
* @param $name
|
||||
* @param $size
|
||||
* @param $str_size
|
||||
* @param int $conflict_proportion
|
||||
* @throws ZMException
|
||||
*/
|
||||
private static function createTable($name, $size, $str_size) {
|
||||
self::$kv_table[$name] = new Table($size, 0);
|
||||
private static function createTable($name, $size, $str_size, $conflict_proportion = 0) {
|
||||
self::$kv_table[$name] = new Table($size, $conflict_proportion);
|
||||
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
|
||||
$r = self::$kv_table[$name]->create();
|
||||
if ($r === false) throw new ZMException("内存不足,创建静态表失败!");
|
||||
|
||||
@ -32,6 +32,10 @@ class ZMAtomic
|
||||
self::$atomics["wait_msg_id"] = new Atomic(0);
|
||||
self::$atomics["_event_id"] = new Atomic(0);
|
||||
self::$atomics["server_is_stopped"] = new Atomic(0);
|
||||
for($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
self::$atomics["_#worker_".$i] = new Atomic(0);
|
||||
}
|
||||
echo ("初始化工作进程数量:".ZM_WORKER_NUM.PHP_EOL);
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
self::$atomics["_tmp_" . $i] = new Atomic(0);
|
||||
}
|
||||
|
||||
@ -4,9 +4,12 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\MatchResult;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Requests\ZMRequest;
|
||||
|
||||
class MessageUtil
|
||||
@ -55,6 +58,10 @@ class MessageUtil
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isAtMe($msg, $me_id) {
|
||||
return strpos($msg, CQ::at($me_id)) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过本地地址返回图片的 CQ 码
|
||||
* type == 0 : 返回图片的 base64 CQ 码
|
||||
@ -76,4 +83,80 @@ class MessageUtil
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割字符,将用户消息通过空格或换行分割为数组
|
||||
* @param $msg
|
||||
* @return array|string[]
|
||||
*/
|
||||
public static function splitCommand($msg) {
|
||||
$word = explodeMsg(str_replace("\r", "", $msg));
|
||||
if (empty($word)) $word = [""];
|
||||
if (count(explode("\n", $word[0])) >= 2) {
|
||||
$enter = explode("\n", $msg);
|
||||
$first = split_explode(" ", array_shift($enter));
|
||||
$word = array_merge($first, $enter);
|
||||
foreach ($word as $k => $v) {
|
||||
$word[$k] = trim($word[$k]);
|
||||
}
|
||||
}
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $msg
|
||||
* @param $obj
|
||||
* @return MatchResult
|
||||
*/
|
||||
public static function matchCommand($msg, $obj) {
|
||||
$ls = EventManager::$events[CQCommand::class] ?? [];
|
||||
$word = self::splitCommand($msg);
|
||||
$matched = new MatchResult();
|
||||
foreach ($ls as $k => $v) {
|
||||
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue;
|
||||
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) &&
|
||||
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) &&
|
||||
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $obj["message_type"]))
|
||||
) {
|
||||
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
|
||||
array_shift($word);
|
||||
$matched->match = $word;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->start_with != "" && mb_substr($msg, 0, mb_strlen($v->start_with)) === $v->start_with) {
|
||||
$matched->match = [mb_substr($msg, mb_strlen($v->start_with))];
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->end_with != "" && mb_substr($msg, 0 - mb_strlen($v->end_with)) === $v->end_with) {
|
||||
$matched->match = [substr($msg, 0, strripos($msg, $v->end_with))];
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->keyword != "" && mb_strpos($msg, $v->keyword) !== false) {
|
||||
$matched->match = explode($v->keyword, $msg);
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->pattern != "") {
|
||||
$match = matchArgs($v->pattern, $msg);
|
||||
if ($match !== false) {
|
||||
$matched->match = $match;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
}
|
||||
} elseif ($v->regex != "") {
|
||||
if (preg_match("/" . $v->regex . "/u", $msg, $word2) != 0) {
|
||||
$matched->match = $word2;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $matched;
|
||||
}
|
||||
}
|
||||
@ -4,14 +4,110 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Co;
|
||||
use ZM\Annotation\Swoole\OnPipeMessageEvent;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\WorkerCache;
|
||||
|
||||
class ProcessManager
|
||||
{
|
||||
public static function runOnTask($param, $timeout = 0.5, $dst_worker_id = -1) {
|
||||
return server()->taskwait([
|
||||
"action" => "runMethod",
|
||||
"class" => $param["class"],
|
||||
"method" => $param["method"],
|
||||
"params" => $param["params"]
|
||||
], $timeout, $dst_worker_id);
|
||||
public static function workerAction($src_worker_id, $data) {
|
||||
$server = server();
|
||||
switch ($data["action"] ?? '') {
|
||||
case "eval":
|
||||
eval($data["data"]);
|
||||
break;
|
||||
case "call_static":
|
||||
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
|
||||
break;
|
||||
case "save_persistence":
|
||||
LightCache::savePersistence();
|
||||
break;
|
||||
case "resume_ws_message":
|
||||
$obj = $data["data"];
|
||||
Co::resume($obj["coroutine"]);
|
||||
break;
|
||||
case "getWorkerCache":
|
||||
$r = WorkerCache::get($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "setWorkerCache":
|
||||
$r = WorkerCache::set($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "unsetWorkerCache":
|
||||
$r = WorkerCache::unset($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "hasKeyWorkerCache":
|
||||
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "asyncAddWorkerCache":
|
||||
WorkerCache::add($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSubWorkerCache":
|
||||
WorkerCache::sub($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSetWorkerCache":
|
||||
WorkerCache::set($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncUnsetWorkerCache":
|
||||
WorkerCache::unset($data["key"], true);
|
||||
break;
|
||||
case "addWorkerCache":
|
||||
$r = WorkerCache::add($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "subWorkerCache":
|
||||
$r = WorkerCache::sub($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "returnWorkerCache":
|
||||
WorkerCache::$transfer[$data["cid"]] = $data["value"];
|
||||
zm_resume($data["cid"]);
|
||||
break;
|
||||
default:
|
||||
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
|
||||
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
|
||||
return $v->action == $data["action"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendActionToWorker($worker_id, $action, $data) {
|
||||
$obj = ["action" => $action, "data" => $data];
|
||||
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
|
||||
Console::warning("Cannot send worker action from master or manager process!");
|
||||
return;
|
||||
}
|
||||
if (server()->worker_id == $worker_id) {
|
||||
self::workerAction($worker_id, $obj);
|
||||
} else {
|
||||
server()->sendMessage(json_encode($obj), $worker_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function resumeAllWorkerCoroutines() {
|
||||
if (server()->worker_id === -1) {
|
||||
Console::warning("Cannot call '".__FUNCTION__."' in non-worker process!");
|
||||
return;
|
||||
}
|
||||
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {
|
||||
if (($v["result"] ?? false) === null && isset($v["coroutine"], $v["worker_id"])) {
|
||||
if (server()->worker_id == $v["worker_id"]) Co::resume($v["coroutine"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,22 +6,38 @@ namespace ZM\Utils;
|
||||
|
||||
use Exception;
|
||||
use Psy\Shell;
|
||||
use Swoole\Event;
|
||||
use ZM\Annotation\Command\TerminalCommand;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Framework;
|
||||
|
||||
class Terminal
|
||||
{
|
||||
/**
|
||||
* @param string $cmd
|
||||
* @param $resource
|
||||
* @return bool
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection PhpUnused
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function executeCommand(string $cmd, $resource) {
|
||||
public static function executeCommand(string $cmd) {
|
||||
$it = explodeMsg($cmd);
|
||||
switch ($it[0] ?? '') {
|
||||
case 'help':
|
||||
$help[] = "exit | q:\t断开远程终端";
|
||||
$help[] = "logtest:\t输出所有可以打印的log等级示例消息,用于测试Console";
|
||||
$help[] = "call:\t\t用于执行不需要参数的动态函数,比如 `call \Module\Example\Hello hitokoto`";
|
||||
$help[] = "level:\t\t设置log等级,例如 `level 0|1|2|3|4`";
|
||||
$help[] = "bc:\t\teval执行代码,但输入必须是将代码base64之后的,如 `bc em1faW5mbygn5L2g5aW9Jyk7`";
|
||||
$help[] = "stop:\t\t停止服务器";
|
||||
$help[] = "reload | r:\t热重启用户编写的模块代码";
|
||||
foreach((EventManager::$events[TerminalCommand::class] ?? []) as $v) {
|
||||
$help[]=$v->command.":\t\t".(empty($v->description) ? "<用户自定义指令>" : $v->description);
|
||||
}
|
||||
echo implode("\n", $help) . PHP_EOL;
|
||||
return true;
|
||||
case 'logtest':
|
||||
Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)");
|
||||
Console::error("This is error msg. (0)");
|
||||
@ -35,7 +51,8 @@ class Terminal
|
||||
$class_name = $it[1];
|
||||
$function_name = $it[2];
|
||||
$class = new $class_name([]);
|
||||
$class->$function_name();
|
||||
$r = $class->$function_name();
|
||||
if (is_string($r)) Console::success($r);
|
||||
return true;
|
||||
case 'psysh':
|
||||
if (Framework::$argv["disable-coroutine"]) {
|
||||
@ -43,6 +60,11 @@ class Terminal
|
||||
} else
|
||||
Console::error("Only \"--disable-coroutine\" mode can use psysh!!!");
|
||||
return true;
|
||||
case 'level':
|
||||
$level = intval(is_numeric($it[1] ?? 99) ? ($it[1] ?? 99) : 99);
|
||||
if ($level > 4 || $level < 0) Console::warning("Usage: 'level 0|1|2|3|4'");
|
||||
else Console::setLevel($level) || Console::success("Success!!");
|
||||
break;
|
||||
case 'bc':
|
||||
$code = base64_decode($it[1] ?? '', true);
|
||||
try {
|
||||
@ -57,7 +79,6 @@ class Terminal
|
||||
Console::log($it[2], $it[1]);
|
||||
return true;
|
||||
case 'stop':
|
||||
Event::del($resource);
|
||||
ZMUtil::stop();
|
||||
return false;
|
||||
case 'reload':
|
||||
@ -67,8 +88,35 @@ class Terminal
|
||||
case '':
|
||||
return true;
|
||||
default:
|
||||
$dispatcher = new EventDispatcher(TerminalCommand::class);
|
||||
$dispatcher->setRuleFunction(function ($v) use ($it) {
|
||||
/** @var TerminalCommand $v */
|
||||
return $v->command == $it[0];
|
||||
});
|
||||
$dispatcher->setReturnFunction(function () {
|
||||
EventDispatcher::interrupt('none');
|
||||
});
|
||||
$dispatcher->dispatchEvents($it);
|
||||
if ($dispatcher->store !== 'none') {
|
||||
Console::info("Command not found: " . $cmd);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function log($type, $log_msg) {
|
||||
ob_start();
|
||||
if (!in_array($type, ["log", "info", "debug", "success", "warning", "error", "verbose"])) {
|
||||
ob_get_clean();
|
||||
return;
|
||||
}
|
||||
Console::$type($log_msg);
|
||||
$r = ob_get_clean();
|
||||
$all = ManagerGM::getAllByName("terminal");
|
||||
foreach ($all as $k => $v) {
|
||||
server()->send($v->getFd(), "\r" . $r);
|
||||
server()->send($v->getFd(), ">>> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,13 +4,10 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Co;
|
||||
use Exception;
|
||||
use Swoole\Event;
|
||||
use Swoole\Timer;
|
||||
use Swoole\Process;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Store\ZMBuf;
|
||||
@ -24,39 +21,21 @@ class ZMUtil
|
||||
if (SpinLock::tryLock("_stop_signal") === false) return;
|
||||
Console::warning(Console::setColor("Stopping server...", "red"));
|
||||
if (Console::getLevel() >= 4) Console::trace();
|
||||
LightCache::savePersistence();
|
||||
if (ZMBuf::$terminal !== null)
|
||||
Event::del(ZMBuf::$terminal);
|
||||
ZMAtomic::get("stop_signal")->set(1);
|
||||
try {
|
||||
LightCache::set('stop', 'OK');
|
||||
} catch (Exception $e) {
|
||||
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
if (Process::kill(zm_atomic("_#worker_" . $i)->get(), 0))
|
||||
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1);
|
||||
}
|
||||
server()->shutdown();
|
||||
server()->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $delay
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function reload($delay = 800) {
|
||||
if (server()->worker_id !== -1) {
|
||||
Console::info(server()->worker_id);
|
||||
public static function reload() {
|
||||
zm_atomic("_int_is_reload")->set(1);
|
||||
system("kill -INT " . intval(server()->master_pid));
|
||||
return;
|
||||
}
|
||||
Console::info(Console::setColor("Reloading server...", "gold"));
|
||||
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"]);
|
||||
}
|
||||
LightCacheInside::unset("wait_api", "wait_api");
|
||||
LightCache::savePersistence();
|
||||
//DataProvider::saveBuffer();
|
||||
Timer::clearAll();
|
||||
server()->reload();
|
||||
}
|
||||
|
||||
public static function getModInstance($class) {
|
||||
@ -69,6 +48,22 @@ class ZMUtil
|
||||
}
|
||||
|
||||
public static function sendActionToWorker($target_id, $action, $data) {
|
||||
Console::verbose($action . ": " . $data);
|
||||
server()->sendMessage(json_encode(["action" => $action, "data" => $data]), $target_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在工作进程中返回可以通过reload重新加载的php文件列表
|
||||
* @return string[]|string[][]
|
||||
*/
|
||||
public static function getReloadableFiles() {
|
||||
return array_map(
|
||||
function ($x) {
|
||||
return str_replace(DataProvider::getWorkingDir() . "/", "", $x);
|
||||
}, array_diff(
|
||||
get_included_files(),
|
||||
Framework::$loaded_files
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
use Swoole\Atomic;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\WebSocket\Server;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
use ZM\API\ZMRobot;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
@ -243,8 +244,6 @@ function ctx(): ?ContextInterface {
|
||||
}
|
||||
}
|
||||
|
||||
function zm_debug($msg) { Console::debug($msg); }
|
||||
|
||||
function onebot_target_id_name($message_type): string {
|
||||
return ($message_type == "group" ? "group_id" : "user_id");
|
||||
}
|
||||
@ -255,13 +254,21 @@ function zm_sleep($s = 1): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
function zm_exec($cmd): array { return System::exec($cmd); }
|
||||
function zm_exec($cmd): array {
|
||||
return System::exec($cmd);
|
||||
}
|
||||
|
||||
function zm_cid() { return Co::getCid(); }
|
||||
function zm_cid() {
|
||||
return Co::getCid();
|
||||
}
|
||||
|
||||
function zm_yield() { Co::yield(); }
|
||||
function zm_yield() {
|
||||
Co::yield();
|
||||
}
|
||||
|
||||
function zm_resume(int $cid) { Co::resume($cid); }
|
||||
function zm_resume(int $cid) {
|
||||
Co::resume($cid);
|
||||
}
|
||||
|
||||
function zm_timer_after($ms, callable $callable) {
|
||||
Swoole\Timer::after($ms, function () use ($callable) {
|
||||
@ -287,10 +294,14 @@ function zm_timer_tick($ms, callable $callable) {
|
||||
if (zm_cid() === -1) {
|
||||
return go(function () use ($ms, $callable) {
|
||||
Console::debug("Adding extra timer tick of " . $ms . " ms");
|
||||
Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
|
||||
Swoole\Timer::tick($ms, function () use ($callable) {
|
||||
call_with_catch($callable);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
|
||||
return Swoole\Timer::tick($ms, function () use ($callable) {
|
||||
call_with_catch($callable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,3 +365,32 @@ function working_dir() {
|
||||
elseif (LOAD_MODE == 2) return realpath('.');
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @noinspection PhpMissingReturnTypeInspection */
|
||||
function zm_dump($var, ...$moreVars) {
|
||||
VarDumper::dump($var);
|
||||
|
||||
foreach ($moreVars as $v) {
|
||||
VarDumper::dump($v);
|
||||
}
|
||||
|
||||
if (1 < func_num_args()) {
|
||||
return func_get_args();
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
function zm_info($obj) { Console::info($obj); }
|
||||
|
||||
function zm_warning($obj) { Console::warning($obj); }
|
||||
|
||||
function zm_success($obj) { Console::success($obj); }
|
||||
|
||||
function zm_debug($obj) { Console::debug($obj); }
|
||||
|
||||
function zm_verbose($obj) { Console::verbose($obj); }
|
||||
|
||||
function zm_error($obj) { Console::error($obj); }
|
||||
|
||||
function zm_config($name, $key = null) { return ZMConfig::get($name, $key); }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user