update docs

This commit is contained in:
crazywhalecc
2021-10-17 15:37:55 +08:00
parent 9fb45dd683
commit 8d9485c02e
14 changed files with 71 additions and 20 deletions

View File

@@ -0,0 +1,177 @@
# Console 控制台
Console 类所在命名空间:`\ZM\Console\Console`
Console 类为框架的终端输出管理类。
## 设置 Log 输出等级
**输出等级** 控制了输出到命令行的内容的重要性。在框架的输出中,消息有以下几种不同等级的类别
- **error** / **log**: 0
- **warning**: 1
- **info** / **success**: 2
- **verbose**: 3
- **debug**: 4
输出等级设置后显示的消息类别为小于等于当前 log 的。假设你将 log 等级设置为 3你可以看到除 debug 外的所有 log 内容。
通过配置文件 `global.php` 中的 `init_atomics -> info_level` 的数值你可以更改框架的默认 log 等级(默认为 2
你也可以在启动框架的命令行中添加参数来切换 log 等级:
```bash
vendor/bin/start server --log-error # 以 error 等级启动框架
vendor/bin/start server --log-warning # 以 warning 等级启动框架
vendor/bin/start server --log-info # 以 info 等级启动框架
vendor/bin/start server --log-verbose # 以 verbose 等级启动框架
vendor/bin/start server --log-debug # 以 debug 等级启动框架
```
## 使用 Log 输出内容
作为模块开发者的你,你可以主动调用框架内的 Console 类输出信息到终端。
### Console::log()
输出 0 级别的普通 log。
- 参数:`$msg, $color`,分别为内容和字体颜色。
> 此 log 不会被 info_level 所限制,无论如何也会输出到终端。
### Console::error()
输出 error 级别的红色醒目 log。一般此 log 为框架内部出现不可忍受的错误比如内存不足、PHP fatal error 等错误。
- 参数:`$msg`
> 此 log 不会被 info_level 所限制,无论如何也会输出到终端。
### Console::warning()
输出 warning 级别的 log。
!!! warning 注意
框架内出现的用户态异常,比如无法发送 API、无法连接数据库等错误都是 warning 错误,不会导致框架崩溃或功能错误的异常情况建议都使用 warning 输出而不是 error。
### Console::info()
输出 info 级别的 log。
### Console::success()
输出 success 级别的log。
### Console::verbose()
输出 verbose 级别的 log。
### Console::debug()
输出 debug 级别的 log。
### Console::stackTrace()
输出栈追踪信息。
### Console::setColor()
返回:彩色的字符串。
- **string**: 要变颜色的字符串
- **color**: 要变的颜色。支持 `red``green``yellow``reset``blue``gray``gold``pink``lightblue``lightlightblue`
```php
Console::log("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)");
Console::stackTrace();
$str = Console::setColor("I am gold color.", "gold");
```
## 终端交互命令
炸毛框架支持从终端输入命令来进行一些操作,例如重启框架、停止框架、执行函数等。
!!! warning 注意
在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。
### reload
重新加载除 `src/Framework/` 下的所有模块。
- 别名:`r`
### stop
停止框架。
### logtest
输出各种等级的 log 示例文本。
### call
执行对应类的成员方法。下面是例子:
```bash
call \ZM\Utils\ZMUtil reload
```
### bc
直接执行 PHP 代码,输入格式为 base64。
```bash
bc XEZyYW1ld29ya1xDb25zb2xlOjp3YXJuaW5nKCJoZWxsbyB3YXJuaW5nISIpOw==
# 代码内容:\ZM\Console\Console::warning("hello warning!");
# 终端输出:[19:14:32] [W] hello warning!
```
### echo
输出文本
```bash
echo hello
```
### color
按照颜色输出文本
```bash
color green 我是绿色的字
```
## MOTD
在 1.4 版本开始,框架支持启动时的 motd 内容修改。
文件位置:`config/motd.txt`
其中,默认的 `Zhamao` 字样的 MOTD 是使用 **figlet** 命令生成的,`figlet "Zhamao"`,你也可以针对自己的机器人名称或品牌进行生成。
## 设置输出主题
Console 组件支持为多种不同的终端设置不同的主题,比如有些人喜欢使用白色的终端,但是白色终端下 info 的颜色很浅,看不到,还有人使用不能显示颜色的黑白终端.....
```bash
vendor/bin/start server --log-theme={主题名}
```
现有支持的主题有:`default``white-term``no-color`
```bash
vendor/bin/start server --log-theme=white-term # 如果用的是白色终端,这个主题更友好
vendor/bin/start server --log-theme=no-color # 如果不想让 log 带有任何颜色,使用无色主题
```

View File

@@ -0,0 +1,425 @@
# 上下文
上下文作为整个框架中最重要的内容之一,请务必理解和完整地阅读此部分!
一个上下文描述了一个事件和所关联的对象的环境。例如:你在处理 HTTP 请求的 `@RequestMapping` 绑定的事件中,你需要获取请求的 HTTP 头和 Cookie再比如你在处理 QQ 机器人发来的命令 `@CQCommand("随机数")` 的时候,在这个方法内,你需要获取发来的人的 QQ 号码。以上我们将处理以上运行环境的对象叫做上下文。
由于 Swoole 的协程加持,我们利用了协程 ID 绑定对象来进行构造上下文。
以默认的机器人收发消息为例,通过对默认模块的了解,我们可以知道,在绑定 `@CQCommand` 等类似事件后,你可以用上下文获取发来这条消息的人的 QQ 号码:
```php
/**
* @CQCommand("你好")
*/
public function hello() {
$user_id = ctx()->getUserId();
ctx()->reply("你好啊,".$user_id.",很高兴认识你!");
}
```
`context()` 就是获取上下文对象的全局函数,它还有简写:`ctx()`
当然,上下文中的方法不是每个都能在任何时候使用的。例如 `getUserId()` 你不能在 `@RequestMapping` 注解的函数中使用,因为它不是机器人消息的上下文。下面说明上下文对象的方法中,每个都会说明每个方法可以在哪些事件中使用:
## getServer() - 获取 Server 对象
获取 Swoole WebSocker Server 对象。此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server)。
可以使用的事件:`@OnMessageEvent()``@OnOpenEvent()``@OnCloseEvent()``@OnStart()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
## getFrame() - 获取 WS 数据帧
获取 `\Swoole\Websocket\Frame` 对象,此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
可以使用的事件:`@OnMessageEvent()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等,
## getFd() - 返回 fd 值
获取当前连入 Swoole 服务器的连接文件描述符 ID。返回 int。一般代表连接号可用来绑定对应链接。
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnOpenEvent()``@OnCloseEvent()`
!!! tip "提示"
值得注意的是,由于机器人客户端和炸毛框架的连接是通过 WebSocket 进行的,而 WebSocket 是长连接,所以同一个机器人一次连接下收发消息所用的连接是同一个,所以 Fd 也是相同的。同理,炸毛框架的内部来区分多个机器人也是通过这一 Fd 进行判定的。
=== "代码"
```php
/**
* @CQCommand("测试fd")
*/
public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()."机器人QQ是".ctx()->getRobotId());
}
```
=== "效果"
<chat-box>
^ 假设我们和连接55555的机器人的私聊
) 测试fd
( 当前机器人连接的fd是1机器人QQ是55555
^ 假设切到了另一个机器人66666的私聊
) 测试fd
( 当前机器人连接的fd是2机器人QQ是66666
</chat-box>
## getData() - 获取事件完整数据
返回 CQHTTP 事件上报的原始数据包,已经被解析成数组,可以直接操作。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
```php
/**
* @CQMessage(user_id=123456)
*/
public function onMessage() {
$data = ctx()->getData();
ctx()->reply("消息类型是:" . $data["message_type"]);
}
```
<chat-box>
^ 假设我是QQ为123456的用户私聊发消息
) 哈咯!!
( 消息类型是private
</chat-box>
## getRequest() - HTTP 请求对象
返回 `\Swoole\Http\Request` 对象,可在 `@RequestMapping` 中使用,获取 Cookie请求头GET 参数什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httprequest)。
可以使用的事件:`@RequestMapping()``@OnRequestEvent()``@OnOpenEvent()`。
## getResponse() - HTTP 响应对象
返回 `\Swoole\Http\Response` 对象的增强版,可在 HTTP 请求相关的事件中使用,返回内容和设置 Cookie 什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httpresponse)。
可以使用的事件:`@RequestMapping()``@OnRequestEvent()`。
下面是使用以上两个功能的组合示例:
```php
/**
* @RequestMapping("/ping")
*/
public function ping() {
$name = ctx()->getRequest()->get["name"] ?? "unknown";
ctx()->getResponse()->end("Hello ".$name."!");
}
```
## getConnection() - WS 连接对象
返回此上下文相关联的 WebSocket 连接对象。详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client)。
可以使用的事件:所有 **getFrame()** 可以使用的都可以使用。
## getCid() - 上下文 ID
返回当前上下文所绑定的协程 ID此 ID 和 `\Co::getCid()` 返回值一样。
## getRobot() - 获取机器人 API 对象
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](../bot/robot-api.md)。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
```php
ctx()->getRobot()->sendPrivateMsg(123456, "发送私聊消息");
```
<chat-box>
^ 正在和机器人聊天
( 发送私聊消息
</chat-box>
## getMessage() - 获取消息
获取 data 数据中的 `message` 消息,用于快速获取用户消息事件的消息内容。
可以使用的事件:`@CQCommand()``@CQMessage``@CQBefore("message")``@CQAfter("message")`
=== "代码"
```php
/**
* @CQMessage(group_id=33333)
*/
public function groupRepeat() {
ctx()->reply(ctx()->getMessage());
}
```
=== "效果"
<chat-box>
^ 现在在群33333内机器人已经成了复读机
) 来世还做复读机!!!
( 来世还做复读机!!!
) 你不许复读!
( 你不许复读!
</chat-box>
## getUserId() - 获取用户 QQ 号
获取发消息的用户的 QQ 号码。
可以使用的事件:所有 **含有** `user_id` 上报参数的 OneBot 事件。
```php
/**
* @CQCommand("whoami")
*/
public function whoami() {
ctx()->reply("你是".ctx()->getUserId()); //返回你是123456
}
```
## getGroupId() - 获取 QQ 群号
获取发消息来自的 QQ 群号。
可以使用的事件:所有含有 `group_id` 上报参数的 OneBot 事件。
## getMessageType() - 消息类型
获取消息类型,同参数 `message_type`。
可以使用的事件:所有 `post_type` 为 `message` 的响应事件,如 `@CQMessage``@CQCommand`。
## getRobotId() - 机器人 QQ 号
获取事件上报的机器人自己的 QQ 号码。
可以使用的事件:所有 OneBot 发来的事件:`@CQCommand()``@CQNotice()` 等。
## setMessage() - 设置消息
与 `getMessage()` 对应,用于更改上下文中保存的事件信息,可以用于消息变更和过滤。
## setUserId() - 设置用户 ID
与上同理,更改 `user_id`。
## setGroupId() - 设置群号
与上同理。
## setMessageType() - 设置类型
与上同理,修改消息类型。
## setData() - 设置数据包
与上同理,与 `getData()` 对应,用于更改上下文中的 `data`。
## getCache() - 上下文缓存
获取保存在上下文中的临时缓存变量。当相关联的事件结束后,数据会从内存中被释放。用于同一事件的多个函数中的信息传递。
- 参数:`$key`,缓存变量的键名
- 返回:`mixed`,存入缓存的变量值。
```php
$a = ctx()->getCache("block_continue");
// 如果变量不存在,则返回 null
```
## setCache() - 上下文缓存
与 `getCache()` 对应,是设置内容的。
```php
ctx()->setCache("abc", "asdasd");
$result = ctx()->getCache("abc"); // asdasd
```
## reply() - 快速回复
快速回复当前用户消息内容。
- 参数1`$msg`,字符串,你要回复的消息内容
- 参数2`$yield = false`,可选,当为 `true` 时,会协程等待后返回 **消息回复** 的结果,包括 API 状态码、消息 `message_id` 等。
```php
$r = ctx()->reply("我又好了。");
if($r["retcode"] == 0) Console::success("消息发送成功!");
```
## finalReply() - 快速回复
快速回复用户消息,并阻止其他模块接下来继续处理此事件。
参数同 `reply()`。
## waitMessage() - 等待用户消息
- 参数:`waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "")`
- 用途:等待用户输入消息
`$prompt` 参数为回复用户的文本内容,`$timeout` 是等待用户回复的超时时间(秒)`$timeout_prompt` 是超时后回复用户的文本。
这个功能可以让开发机器人的代码逻辑和实际贴合,避免回调地狱、拼接参数、上下文脱节等问题,比如下方的示例,可以仅仅用两行代码实现一个问答式的对话过程。
用法示例:
```php
/**
* @CQCommand("自我介绍")
*/
function yourName(){
$r = ctx()->waitMessage("你叫啥名字呀?", 600, "你都10分钟不理我了嘤嘤嘤");
ctx()->finalReply("好的,可爱的机器人记住你叫 ".$r." 啦!以后多聊天哦!");
}
```
<chat-box>
) 自我介绍
( 你叫啥名字呀?
) jerry
( 好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!
) 自我介绍
( 你叫啥名字呀?
^ 10分钟没理机器人
( 你都10分钟不理我了嘤嘤嘤
</chat-box>
## getArgs() - 自动获取参数
为 `waitMessage()` 的封装,目的是让机器人的回复更加智能化。最好的例子就是在框架自带的默认示例中“随机数”的例子,我们假设要写一个随机数功能,但是用户从来都是不思考就使用机器人的。抛开人工智能,我们能做的就是“专家系统”,同时让我们写的代码尽可能适配用户所说的每一句话:
- 随机数 1 100
- 随机数(一般不知道怎么用这个功能的人都会只说一个关键词)
- 从2到9的随机数
所以,在匹配第一和第二种情况时候,我们不需要重复写代码,而第一种的话用户已经将参数给你的时候,你不需要再次使用 `waitMessage()` 方式进行等待询问,只需要取到使用就好了。`getArgs()` 就是做这个的。
定义:`getArgs($mode, $prompt_msg)`
`$mode`:获取模式,有三种:
- `ZM_MATCH_ALL`:效果等同于 `getFullArg()`,获取全部的内容,把空格也当作一部分
- `ZM_MATCH_NUMBER`:效果等同于 `getNumArg()`,获取下一个数字参数
- `ZM_MATCH_FIRST`:效果等同于 `getNextArg()`,获取下一个参数
`$prompt_msg`:字符串,指定如果参数缺失时询问用户的内容。
```php
/**
* @CQCommand("test")
*/
public function argTest1() {
$s = ctx()->getArgs(ZM_MATCH_FIRST, "请输入你要传入的参数内容");
return "参数内容:".$s;
}
```
<chat-box>
) test
( 请输入你要传入的参数内容
) test2
( 参数内容test2
</chat-box>
`getArgs()` 也有三层封装,在使用过程中避免麻烦的话,推荐使用下面这几种 `get*Arg()` 方式。
## getFullArg()
获取关键词后的整个字符串参数,包括空格,如果不存在则询问。
典型例子:`复读机 你好 你好`,获取参数时会将 `你好 你好` 当作一个参数来获取。
```php
/**
* @CQCommand("test")
*/
public function argTest1() {
$s = ctx()->getFullArg("请输入你要传入的参数内容");
return "参数内容:".$s;
}
```
<chat-box>
) test abc def argtest
( 参数内容abc def argtest
) test
( 请输入你要传入的参数内容
) abc def
( 参数内容abc def
</chat-box>
## getNextArg()
获取下一个参数分隔符可以是空格tab。
```php
/**
* @CQCommand("test")
*/
public function argTest1() {
$s = ctx()->getNextArg("请输入你要传入的参数内容");
return "参数内容:".$s;
}
```
<chat-box>
) test abc def argtest
( 参数内容abc
) test
( 请输入你要传入的参数内容
) abc
( 参数内容abc
</chat-box>
## getNumArg()
> 2.1.5 版本起可用。
获取下一个数字型参数,如果 `is_numeric()` 为 true 则获取成功,如果没有符合的则询问用户。
```php
/**
* @CQCommand("test")
*/
public function argTest1() {
$s = ctx()->getNextArg("请输入你要传入的数字内容");
return "数字参数内容:".$s;
}
```
<chat-box>
) test abc 334 argtest
( 数字参数内容334
) test abc
( 请输入你要传入的数字内容
) 998
( 参数内容998
</chat-box>
## copy()
获取整个上下文的所有内容的数组形式。
```php
$arr = ctx()->copy();
dump($arr);
```
## getOption() - 获取匹配参数内容
```php
/**
* @CQCommand("test")
*/
public function argTest1() {
return "参数内容:".implode(", ", ctx()->getOption());
}
```
<chat-box>
) test abc 334 argtest
( 参数内容abc, 334, argtest
</chat-box>

View File

@@ -0,0 +1,63 @@
# 协程池
首先要声明的一点是,协程池这个概念是我自己编的。
因为协程的特点,它是单线程下运行的,所以在一个进程内同时实际上只会有一个协程的代码在执行逻辑,但是后面的 IO 操作、协程挂起等待的操作都是同时去做的,比如数据库的大数据读取、写入需要耗时几秒甚至几十秒,这时用基于协程的 MySQL 连接池就完全不是问题。
但是就拿 MySQL 举例,我们 MySQL 使用的是 TCP 连接,而无论是 MySQL 还是 TCP 连接,最大数量都是有限的。我们即使设置了允许最大协程数量非常大,比如上百万,但是也不能让数据库连接池一个池支持上百万的连接。
这时假设高并发进来了怎么办呢?这时就需要框架提出的一个折中方案:协程池了。
顾名思义,协程池是一个容纳协程的区域,而协程里又容纳着各种各样需要阻塞调用被协程调用的 IO 操作,协程池用作限制协程的数量。
```php
use ZM\Utils\CoroutinePool;
use ZM\DB\DB;
// 传统写法,一旦高并发则可能导致 Too many connections
go(funuction(){
DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]);
});
// 协程池写法
CoroutinePool::go(function(){
DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]);
}, "foo");
```
参数:`go(callable $func, $name = "default")`
`$name` 为协程池对应的名字,你可以设置多个协程池,用来支持不同的需要限制并发 IO 数量的地方,例如 Redis 和 MySQL 设置不同的名字。`$func` 可为闭包或可调用的方法名称或数组。
## 配置
默认情况下,直接调用 `CoroutinePool::go()` 时,协程池大小为 30也就是如果有 30 个协程进入了挂起状态(比如数据库在执行查询语句),那么更多的协程执行时就会阻塞并以协程等待的方式等待,直到现有的 30 个协程中的一部分完成了它的工作。
## 方法
### CoroutinePool::go()
将协程放入协程池运行。
如果不写 `$name` 参数,则使用的是默认协程池。
### CoroutinePool::defaultSize()
设置默认协程池的大小(默认 30
```php
CoroutinePool::defaultSize(64);
for($i = 0; $i < 1000; ++$i) {
CoroutinePool::go(function(){
DB::rawQuery("SELECT * FROM users");
});
}
```
### CoroutinePool::setSize()
定义:`setSize($name, int $size)`
`$name` 为字符串,是你要用的协程池的名称。
`$size` 为大小,最大不可超过 Swoole 配置文件中指定的最大协程数量。

View File

@@ -0,0 +1,324 @@
# 全局方法
全局方法就是 PHP 的全局函数,任意位置都可以调用,无需使用 use 字样。
## getClassPath()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L24)
根据加载的用户编写的代码类名来获取类所在的文件路径。
=== "src/Module/Example/Hello.php"
```php
<?php
namespace Module\Example;
class Hello { ... }
```
=== "src/Module/Example/Start.php"
```php
<?php
namespace Module\Example;
use ZM\Annotation\Swoole\OnStart;
class Start {
/**
* @OnStart()
*/
public function onStart() {
Console::info("Path: ".getClassPath(Hello::class));
}
}
```
=== "输出结果"
```
[11:12:02] [I] [#0] Path: /mnt/d/project/zhamao-framework/src/Module/Example/Hello.ph
```
## explodeMsg()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L39)
切割字符串的函数支持多空格换行tab。
定义:`explodeMsg($msg, $ban_comma = false)`
```php
$s = explodeMsg("你好啊 你好你好\n我还有多个空格 哈哈哈");
echo json_encode($s, 128|256); // ["你好啊","你好你好","我还有多个空格","哈哈哈"]
```
## unicode_decode()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L54)
Unicode 解码,一般用于被转义的 Unicode 转回来。
```php
echo unicode_decode("\u4f60\u597d"); // 你好
```
## matchPattern()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L91)
根据星号匹配字符串(非正则表达式)。
匹配示例:
- `你今天*了吗` -> 你今天喝水了吗
- `*的天气怎么样` -> 德州的天气怎么样
- `把*翻译成*` -> 把茶翻译成英语
定义:`matchPattern($pattern, $context)`
`$pattern` 为匹配模式,例如 `你今天*了吗`。
`$context` 为要判断是否匹配的内容。
返回值:`bool`,当为 true 时代表规则是匹配的false 代表不匹配。
```php
matchPattern("*是个啥?", "996是个啥"); // true
matchPattern("我想听*唱歌", "你想听谁唱歌"); // false
matchPattern("*把*翻译成*", "请把你好翻译成阿拉伯语"); // true
```
## split_explode()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L103)
和 `explodeMsg()` 类似,用作分割字符串,不过此函数加入了对 `中文|数字` 两者的分割,也就是说中文和数字之间也会被分割。
定义:`split_explode($del, $str, $divide_en = false)`
```php
split_explode(" ", "前进20 急啊急啊"); // ["前进","20","急啊急啊"]
```
`$del` 和 `explode()` 的第一个参数作用相同,作为初期分割的标志。
`$str` 表示待分割的内容。
`$divide_en` 表示是否分割中文和英文,如果为是,则中文和英文之间也会被分割开。
## matchArgs()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L135)
`matchPattern()` 的扩展,如果 `matchPattern()` 格式的字符串和模式匹配成功,则通过星号位置来提取星号匹配到的内容,参数同 `matchPattern()`。
```php
$r = matchArgs("把*翻译成*", "把日语翻译成英语"); // ["日语","英语"]
```
## connectIsQQ()
判断当前 WebSocket 连接是否为 OneBot 标准的机器人客户端。
## connectIsDefault()
判断连接是否是未定义类型的 WebSocket 连接。
## connectIs()
判断连接是否是对应类型的 WebSocket 连接。
```php
connectIs("your_another_type_connect");
```
## set_coroutine_params()
设置当前上下文中的一些变量。
```php
set_coroutine_params(["data" => [
"post_type" => "message",
...
]]);
```
## ctx()
别名:`context()`,获取当前协程的上下文,见 [上下文](/component/context/)。
## zm_sleep()
协程版 `sleep()` 函数。
定义:`zm_sleep($s = 1)`
`$s`睡眠的时间可支持小数。例如0.001 代表 1 毫秒)
为什么不用 PHP 自带的 sleep 呢?因为炸毛框架是基于协程的,协程版 sleep 需要使用 Swoole 自带的 sleep。此函数做了一个简单的封装。
```php
zm_sleep(5);
zm_sleep(0.05);
```
## zm_exec()
执行系统命令,替代 PHP 的 `exec()`。
定义:`zm_exec($cmd)`
返回值:
```php
array(
'code' => 0, // 进程退出的状态码
'signal' => 0, // 信号
'output' => 'hello world', // 输出内容
);
```
```php
$result = zm_exec("echo 'hello world'")["output"];
```
## zm_cid()
获取当前协程的 ID效果等同于 `\Swoole\Coroutine::getCid()`。
## zm_yield()
挂起当前协程,直到手动恢复,效果等同于 `\Swoole\Coroutine::yield()`。
## zm_resume()
恢复继续执行协程,效果等同于 `\Swoole\Coroutine::resume()`。
```php
$r = 0;
function test() {
echo "hello-1\n";
global $r;
$r = zm_cid();
zm_yield();
echo "hello-2\n";
}
go("test");
echo "hello-3\n";
zm_resume($r);
```
输出结果:
```
hello-1
hello-3
hello-2
```
## server()
获取 Swoole Server 对象进行操作,效果等同于 `\ZM\Framework::$server`。
```php
echo server()->worker_id.PHP_EOL; // 0
```
## bot()
返回 ZMRobot 操作机器人 API 的对象。
对于默认的模式,如果框架连接了多个机器人实例,则会随机返回一个机器人的 API 实例。如果使用了单例模式,则返回单例模式的机器人 API 实例。
```php
bot()->sendPrivateMsg(123456, "你好啊!!");
// 等同于 ZMRobot::getRandom()->sendPrivateMsg(123456, "你好啊!!");
```
## zm_atomic()
获取计时器,效果同 `\ZM\Store\ZMAtomic::get($name)`。
定义:`zm_atmoic($name)`
## uuidgen()
> 2.2.5 版本起可用。
生成一个随机的 uuid支持大写或小写。
定义:`uuidgen($uppercase = false)`
当 `$uppercase` 为 `true` 时,返回的 uuid 中字母都是大写。
## working_dir()
> 2.2.6 版本起可用。
获取框架运行的工作目录。例如你是从 `/root/framework-starter/` 目录启动的框架,`vendor/bin/start server`,那么 `working_dir()` 返回的就是 `/root/framework-starter`。(注意,返回的目录最后没有斜杠,请自行添加。)
## getAllFdByConnectType()
获取同类型的所有连接的描述符 ID。
定义:`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);
```
![](https://static.zhamao.me/images/docs/ba026ca11332b1a4ad68a549165230e6.png)
## 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)`。

View File

@@ -0,0 +1,37 @@
# 远程终端
框架在 2.3 版本时删除了本地终端(就是框架启动后可以在终端输入一些参数),因为框架的多进程模式会导致终端输入错乱,所以暂时取消掉了。
而远程终端应运而生,为的是弥补这一功能。与之前不同的是,远程终端使用 nc 连接,无需任何其他组件和客户端,而且功能更丰富,支持自定义命令。
## 启用
有两种开启方式:
- 永久开启:全局配置文件中找到 `remote_terminal``status`,改为 true启动框架即可。
- 临时开启:启动框架时加上参数 `--remote-terminal`。例如:`vendor/bin/start server --remote-terminal`
## 配置
在一般情况下,框架为了安全,直接按照默认配置,会监听 `127.0.0.1:20002` 端口,不可以远程访问,只能使用本机的 nc 连接,效果如下:
本地主机:
![img.png](https://static.zhamao.me/images/docs/3432551c08b34ca10aaf19f3f82aedeb.png)
从别的主机:
![img.png](https://static.zhamao.me/images/docs/6f35f2745d66c7e186da75b6f09248c2.png)
如果将 `host` 改为 `0.0.0.0` 或对应监听地址,即可指向性访问。
但是,如果你又想远程连接,又想保证安全,那么可以设置一个 token 参数,来保证连接时需要输入 token 才能使用远程终端。
假设我们的 token 是 `iAMTokEn`
![img.png](https://static.zhamao.me/images/docs/e502af4c0fd9359615548303cacb70dd.png
)
## 使用
默认情况下,使用 `nc` 命令即可。
```bash
nc <your-host> <your-port> -vvv
# nc 127.0.0.1 20002 -vvv
```
输入 help 即可查看内置的常用指令:
![img.png](https://static.zhamao.me/images/docs/7b74aa2b487c86482097ec7692c66e08.png
)

View File

@@ -0,0 +1,43 @@
# 单例类SingletonTrait
单例类,顾名思义,就是让用户声明的类拥有单例的特性,而这一组件引入的方式也最直接。它是一个 PHP 的 `trait`
我们传统写单例类的方式很手动,比如下面这样:
```php
<?php
class Foo {
public $test = 0;
private static $instance;
public static function getInstance() {
if (null === self::$instance) {
self::$instance = new Foo();
}
return self::$instance;
}
}
Foo::getInstance()->test = 4;
$obj = Foo::getInstance()->test;
var_dump($obj); // 4
```
这就要求我们每个需要声明为单例的类都写一个成员静态方法和成员静态变量。
框架使用了 PHP Trait 来快速让一个类支持这一特性:
```php
<?php
use ZM\Utils\SingletonTrait;
class Foo {
use SingletonTrait;
public $test = 0;
}
Foo::getInstance()->test = 5;
var_dump(Foo::getInstance()->test);
```
只需要在类中使用:`use \ZM\Utils\SingletonTrait;` 一句话即可。

View 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");
```

View File

@@ -0,0 +1,67 @@
# ZMUtil 杂项工具类
调用前先 use`use ZM\Utils\ZMUtil;`
## ZMUtil::stop()
停止框架运行。
## ZMUtil::reload()
重载框架,这会断开所有到框架的连接和重载所有 `src/` 目录下的用户源码并重新加载所有 Worker 进程。
## ZMUtil::getModInstance()
根据类名称拿到此类的单例(前提是目标的类的构造函数为空)。
```php
class ASD{
public $test = 0;
}
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)。