mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-22 08:05:34 +08:00
renew docs (#205)
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
# 自定义注解
|
||||
|
||||
TODO:师傅,莫催,快肝完了!
|
||||
@@ -1,119 +0,0 @@
|
||||
# 事件分发器(进阶)
|
||||
|
||||
事件分发器是以上所有注解事件执行函数的一个分发器,如果你在上一章已经学会了如何创建自定义注解,那么本章就来说明如何用内置的事件分发器进行分发自定义事件。
|
||||
|
||||
如果你不需要了解或自定义有关事件分发的功能,此处可无需阅读。
|
||||
|
||||
## 属性
|
||||
|
||||
- 类名:`ZM\Event\EventDispatcher`
|
||||
|
||||
## 方法
|
||||
|
||||
### EventDispatcher::interrupt()
|
||||
|
||||
阻断当前正在运行的事件,只能在事件内部被调用的函数中实现。
|
||||
|
||||
### __construct()
|
||||
|
||||
构造方法。
|
||||
|
||||
```php
|
||||
EventDispatcher::__construct(string $class = '')
|
||||
```
|
||||
|
||||
初始化一个事件分发器,可进行一系列设置,对事件分发做限定。
|
||||
|
||||
#### 参数
|
||||
|
||||
`$class`:设置要分发的事件对应的注解类名,支持自定义注解(例如 `CQMessage::class`)
|
||||
|
||||
### setRuleFunction()
|
||||
|
||||
设置函数触发规则判定的函数(就是在执行事件函数前执行的规则判定)
|
||||
|
||||
```php
|
||||
setRuleFunction(callable $rule = null)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
`$rule`:支持回调或闭包。闭包的参数为执行对应事件函数所绑定的注解事件对象。
|
||||
|
||||
```php
|
||||
$dispatcher = new EventDispatcher(CustomEvent::class);
|
||||
$dispatcher->setRuleFunction(function($obj) {
|
||||
return $obj->name == "zhamao" ? true : false;
|
||||
});
|
||||
```
|
||||
|
||||
上方的 `$obj` 就是 CustomEvent 类的实例,参数绑定为注解中对应的参数。
|
||||
|
||||
### setResultFunction()
|
||||
|
||||
设置事件函数返回值处理的回调函数。
|
||||
|
||||
```php
|
||||
setReturnFunction(callable $return_func)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
`$return_func`:设置事件函数返回值处理的回调函数,回调参数绑定为对应单独事件函数的返回值。
|
||||
|
||||
```php
|
||||
$dispatcher = new EventDispatcher(CustomEvent::class);
|
||||
$dispatcher->setReturnFunction(function($return) {
|
||||
if (is_string($return)) Console::info("函数返回了 ".$return);
|
||||
});
|
||||
```
|
||||
|
||||
### dispatchEvents()
|
||||
|
||||
开始分发事件。
|
||||
|
||||
```php
|
||||
dispatchEvents(...$params)
|
||||
```
|
||||
|
||||
#### 参数
|
||||
|
||||
自定义参数,这里填入的参数将被填入被分发的函数参数中。
|
||||
|
||||
```php
|
||||
$dispatcher->dispatchEvents("foo", "bar");
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
class Test {
|
||||
/**
|
||||
* @CustomEvent("zhamao")
|
||||
*/
|
||||
public function test($arg1, $arg2) {
|
||||
echo "$arg1: $arg2"; //将输出 "foo: bar"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 机制
|
||||
|
||||
事件分发器的机制说简单不简单,说复杂也不复杂,它和中间件有着非常大的关系,因为它会自动检测和识别所要执行的函数有没有中间件,并且根据顺序进行执行。
|
||||
|
||||
在炸毛框架内部,一个完整的事件流程和中间件的关系如下图:
|
||||
|
||||

|
||||
|
||||
对于同一事件的优先级和响应顺序,优先级的关系如下图:
|
||||
|
||||

|
||||
|
||||
对于事件内单个事件被调用的单个函数下如果存在多个中间件,中间件模型和事件的关系如下图:
|
||||
|
||||

|
||||
|
||||
## 实战例子
|
||||
|
||||
我们假设 CustomEvent 是我们的自定义注解。还没写完,这部分太复杂了,而且举例子也不好举例,这块应该也不用着急更新。
|
||||
|
||||
TODO:待完成
|
||||
@@ -1,433 +0,0 @@
|
||||
# 框架核心注解事件
|
||||
|
||||
框架核心注解事件区别于机器人和路由注解事件,这里框架注解事件都是**直接**或封装调用 Swoole 的回调事件的,所以对一些比较底层或者基础的操作都在这里做,例如收到 HTTP 或 WebSocket 连接后执行的事件函数。
|
||||
|
||||
## OnOpenEvent()
|
||||
|
||||
当有 WebSocket 连接接入框架时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------- |
|
||||
| 名称 | `@OnOpenEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnOpenEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnOpenEvent("foo")
|
||||
@OnOpenEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnCloseEvent()
|
||||
|
||||
当有 WebSocket 连接断开框架时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------- |
|
||||
| 名称 | `@OnCloseEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接断开框架时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnCloseEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnCloseEvent("foo")
|
||||
@OnCloseEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnRequestEvent()
|
||||
|
||||
当 HTTP 请求接入时,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------- |
|
||||
| 名称 | `@OnRequestEvent` |
|
||||
| 触发前提 | 当 HTTP 请求接入时,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnRequestEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | --------------------------------------------- | ------------------------ | ---------------- |
|
||||
| rule | `string`,必须是可执行且返回 bool 的 PHP 代码 | 前置条件 | 空,rule 为 true |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
## OnMessageEvent()
|
||||
|
||||
当有 WebSocket 连接接入框架后发送过来消息,触发注解事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 名称 | `@OnMessageEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架后发送过来消息,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnMessageEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | -------- | ------------------------------------------------------------ | ---- |
|
||||
| connect_type | `string` | 限定连接的类型,通过炸毛框架支持的方式指定传入类型,详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client) | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnMessageEvent("foo")
|
||||
@OnMessageEvent(connect_type="default")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnPipeMessageEvent()
|
||||
|
||||
当有 其他 Worker 进程通信发来指令,激活响应。(2.2.0 版本可用)
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 名称 | `@OnPipeMessageEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架后发送过来消息,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnPipeMessageEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------- | ------------ | ---- |
|
||||
| action | `string` | 限定动作名称 | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnPipeMessageEvent("foo")
|
||||
@OnPipeMessageEvent(action="bar")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$data`: 数组,内容如下:
|
||||
|
||||
```php
|
||||
[
|
||||
"action" => "你的上面的名称",
|
||||
... //其他自己发送时随便定义,带什么都行
|
||||
]
|
||||
```
|
||||
|
||||
## OnSwooleEvent()
|
||||
|
||||
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接,HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。**请尽量使用上面几个新的注解**。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------ |
|
||||
| 名称 | `@OnSwooleEvent` |
|
||||
| 触发前提 | 当参数指定的 `type` 对应的事件被触发后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnSwooleEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------------------------------------------------------- | ----------------------------------------------- | ---------------- |
|
||||
| type | `string`,支持填入 `open`,`request`,`close`,`message` | 限定事件的类型,**必填** | |
|
||||
| rule | `string`,必须是可执行且返回 bool 的 PHP 代码 | 例如判断连接是否为 QQ 机器人(`connectIsQQ()`) | 空,rule 为 true |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnStart()
|
||||
|
||||
在框架加载后执行的注解事件,用于初始化 Worker 进程,此注解事件会在 Worker 进程中执行,且可以指定在哪个 Worker 进程中执行。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------ |
|
||||
| 名称 | `@OnStart` |
|
||||
| 触发前提 | 在框架加载后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnStart` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0,范围是 0~{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | |
|
||||
|
||||
## OnTick()
|
||||
|
||||
在框架加载后创建毫秒计时器。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------- |
|
||||
| 名称 | `@OnTick` |
|
||||
| 触发前提 | 在框架加载后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnTick` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| tick_ms | `int`,**必填**,间隔的毫秒数,例如 1 秒间隔为 `1000`,范围大于 0,小于 86400000。 | | |
|
||||
| worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0,范围是 0~{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | |
|
||||
|
||||
## OnTask()
|
||||
|
||||
定义一个在工作进程中运行的任务函数。详情见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------- |
|
||||
| 名称 | `@OnTask` |
|
||||
| 触发前提 | 在框架加载后激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnTask` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 有,返回 Worker 进程的结果 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------- | ------------------------------------------------------------ | ------------ | ---- |
|
||||
| task_name | `string`,**必填**,任务函数的名称,不建议重复。 | | |
|
||||
| rule | 设置触发前提,PHP 代码,返回 bool 值即可,参考 OnRequestEvent | 限定是否执行 | 空 |
|
||||
|
||||
## OnSetup()
|
||||
|
||||
在框架加载前执行的代码。此部分代码是在主进程执行的,不可在此事件中使用任何协程相关的功能。
|
||||
|
||||
比如我们要改变所有进程的 ini 设置,这时使用 `@OnStart(-1)` 这样只设置了 Worker 进程的内容,而主进程和管理进程无法被覆盖到。如果需要设置全局的一些配置,务必在此 `@OnSetup` 注解下执行。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------ |
|
||||
| 名称 | `@OnSetup` |
|
||||
| 触发前提 | 在框架加载前激活 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnSetup` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
无。
|
||||
|
||||
## TerminalCommand()
|
||||
|
||||
添加一个远程终端的自定义命令。(2.4.0 版本起可用)
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | --------------------------------------- |
|
||||
| 名称 | `@TerminalCommand` |
|
||||
| 触发前提 | 连接到远程终端可触发 |
|
||||
| 命名空间 | `ZM\Annotation\Command\TerminalCommand` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 注解参数
|
||||
|
||||
| 参数名称 | 参数范围 | 默认 |
|
||||
| ----------- | ------------------------------ | ---- |
|
||||
| command | `string`,**必填**,命令字符串 | |
|
||||
| alias | `string`,可选,命令的别名 | |
|
||||
| description | `string`,要显示的帮助文本 | 空 |
|
||||
|
||||
## 示例1(机器人连接框架后输出信息)
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
class Hello {
|
||||
/**
|
||||
* 在机器人客户端连接框架后向终端输出信息
|
||||
* @OnOpenEvent("qq")
|
||||
* @param $conn
|
||||
*/
|
||||
public function onConnect(ConnectionObject $conn) {
|
||||
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里的 Console 是终端输出组件,详情见组件一栏对应的文档查询。
|
||||
|
||||
## 示例2(阻断 Chrome 访问框架时多访问一次的问题)
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Event\EventDispatcher;
|
||||
class Hello {
|
||||
/**
|
||||
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
|
||||
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
|
||||
*/
|
||||
public function onRequest() {
|
||||
EventDispatcher::interrupt();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 EventDispatcher 为事件分发器,interrupt 是通用阻断方法,如果你平常只使用阻断,则只需掌握这一个方法即可,`EventDispatcher::interrupt()` 在所有事件内可用。
|
||||
|
||||
## 示例3(接收 WS 客户端发来的数据)
|
||||
|
||||
见 [接入 WebSocket 客户端](/advanced/connect-ws-client)。
|
||||
|
||||
## 示例4(使用 OnStart 给所有 Worker 进程写入缓存提速)
|
||||
|
||||
如果你有一些数据存到了文件、数据库中,且是只读不写的,那么就可以使用此方法将这个文件或者数据库的内容读入 Worker 进程的内存中进行使用来提速。
|
||||
|
||||
假设我们有一个大文件 json,里面存着一份题库,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"0": {
|
||||
"question": "法的调整对象是( )。",
|
||||
"answer": {
|
||||
"A": "行为关系",
|
||||
"B": "思想关系",
|
||||
"C": "利益关系",
|
||||
"D": "各种社会资源"
|
||||
},
|
||||
"key": "A",
|
||||
"answer_type": 0
|
||||
},
|
||||
"1": {
|
||||
"question": "法律与其他社会规范的区别在于( )。",
|
||||
"answer": {
|
||||
"A": "是调整人们行为的规范",
|
||||
"B": "有约束力",
|
||||
"C": "由国家强制力保证执行",
|
||||
"D": "规定制裁措施"
|
||||
},
|
||||
"key": "C",
|
||||
"answer_type": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
那么我们可以使用 OnStart 来实现一个,将此文件读取到每个 Worker 进程中,并且快速取用的功能(以下做了一个简单的查题功能):
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Console\Console;
|
||||
class Hello {
|
||||
public static $tiku = [];
|
||||
/**
|
||||
* @OnStart(-1)
|
||||
*/
|
||||
public function onStart() { // 注意,此函数将会在每个 Worker 执行一次
|
||||
$file = file_get_contents("tiku.json"); //从文件读取json
|
||||
$json = json_decode($file, true); //json解析
|
||||
Hello::$tiku = $json; //将解析后的数组以静态变量的方式存到每个 Worker 的内存中
|
||||
Console::success("加载题库完成!");
|
||||
}
|
||||
/**
|
||||
* @CQCommand("找题")
|
||||
*/
|
||||
public function findQuestion() {
|
||||
$tiku_id = ctx()->getNumArg("请输入题目的序号");
|
||||
if(!isset(Hello::$tiku[$tiku_id])) return "题目id为".$tiku_id."的题目不存在!";
|
||||
$timu = Hello::$tiku[$tiku_id];
|
||||
$msg = "题目名称:".$timu["question"];
|
||||
foreach($timu["answer"] as $k => $v) {
|
||||
$msg .= "\n".$k.". ".$v;
|
||||
}
|
||||
$msg .= "\n正确答案:".$timu["key"];
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
终端效果:(我们假设运行框架的电脑是四核 CPU)
|
||||
|
||||
```log
|
||||
[14:28:00] [S] [#0] 加载题库完成!
|
||||
[14:28:00] [S] [#2] 加载题库完成!
|
||||
[14:28:00] [S] [#1] 加载题库完成!
|
||||
[14:28:00] [S] [#3] 加载题库完成!
|
||||
```
|
||||
|
||||
聊天效果:
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'找题 1'},
|
||||
{type:1,content:'题目名称:法律与其他社会规范的区别在于( )。\nA. 是调整人们行为的规范\nB. 有约束力\nC. 由国家强制力保证执行\nD. 规定制裁措施\n正确答案:C'},
|
||||
]"></chat-box>
|
||||
|
||||
## 示例5(创建每分钟自动执行的爬虫)
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnTick(tick_ms=60000,worker_id=0)
|
||||
*/
|
||||
public function onCrawl() {
|
||||
$data = Foo::bar(); //这里是你自己写的要爬的接口等等一系列操作
|
||||
LightCache::set("your_data_key_name", $data); //将爬虫数据存入 LightCache 轻量缓存
|
||||
}
|
||||
```
|
||||
|
||||
## 示例6(创建一个远程终端命令并调试框架)
|
||||
|
||||
> 开个坑,以后填。(__填坑标记__)
|
||||
>
|
||||
@@ -1,89 +0,0 @@
|
||||
# 事件和注解
|
||||
|
||||
## 注解事件概念
|
||||
|
||||
我们知道事件,是一个底层的 event loop 收到消息后调用对应的各类方法的一个模型,比如给机器人发送消息后框架要做的就是指定到一个你定义的函数上,处理你的业务逻辑代码。比如在默认模块中,提供了 **你好** 的回复:**
|
||||
你好啊,我是由炸毛框架构建的机器人!**。这项简单回复的任务就是一个事件的触发到响应的全过程。
|
||||
|
||||
**注解**(Annotation)又称标注,Java 最早在 2004 年的 JDK 5 中引入的一种注释机制。目前 PHP 官方版本并未提供内置元注解和注解概念,但我们通过 `ReflectionClass` 反射类解析 PHP
|
||||
代码注释从而实现了自己的一套注解机制。如果你没有写过 Java,并且不了解注解是什么,你可以理解为对 function 或 class 的一个修饰,因为传统的 PHP
|
||||
代码逻辑我们都知道,不能简单给原先存在的函数贴标签,就比如,你不能在原本的 PHP 代码中给函数贴上一个可以影响它一生并且改变它行为的标签,而有了注解,就相当于有了给函数贴标签的机会。
|
||||
|
||||
在常见框架如 Spring,Swoft 等代码结构里面,注解更是其核心的存在。
|
||||
|
||||
在炸毛框架中,我们所有事件的绑定均采用这一方式进行调用模块内各个方法。包括 Swoole 自身的框架启动事件、WebSocket 连接握手事件、HTTP 请求事件等等,也包括 CQHTTP 发来的事件,如`message`,`notice`
|
||||
,`request` 等。
|
||||
|
||||
## 如何使用注解
|
||||
|
||||
就像我们日常开发写注释一样,只需在类、方法或成员变量上方按规则添加注释即可,这里以默认自带的 `Hello` 模块类为例子:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand(match="你好")
|
||||
* @return string
|
||||
*/
|
||||
public function hello(){
|
||||
return "你好啊,我是由炸毛框架构建的机器人!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中 `@CQCommand()` 就是一个基本的注解应用。注意需引入相关注解(Annotation)类,**且必须** 以 `/**` 开始并以 `*/` 结束,否则会导致无法解析!上方 `@return` 为 IDE 自动生成的
|
||||
PHPDoc,不需要管。
|
||||
|
||||
有什么用?大有妙用!这个例子内注解类的用途是收到 QQ 消息后如果消息第一个词匹配到 `你好` 的话,框架就会自动处理,最终执行调用此 `hello()` 方法。注意 `CQCommand`
|
||||
和其他任何后面讲到的注解类一样,需先 `use ZM\Annotation\` 下的对应注解类,否则也不能正常使用。
|
||||
|
||||
### 基本语法
|
||||
|
||||
先 use!先 use!先 use!重要的事情说三遍!`use ZM\Annotation\xxxx;`
|
||||
|
||||
**必须** 以 `/**` 开始并以 `*/` 结束。
|
||||
|
||||
```
|
||||
@注解类名(参数名1="参数1的值"[,参数名2="参数2的值"])
|
||||
```
|
||||
|
||||
对于只使用或只有一个参数的注解类,`@注解类名("参数的值")` 可以省略参数名。
|
||||
|
||||
对于没有参数的注解类,`@参数名()` 直接使用即可。
|
||||
|
||||
### PHP8 原生 Attribute 使用
|
||||
|
||||
在 2.7.0 版本更新后,框架已经完全支持使用原生 Attribute 替代此前的 Annotation。如果你使用 PHP8.0 或以上版本,即可使用 Attribute 以取得最佳的原生编程体验。两者的参数和效果完全一致。
|
||||
|
||||
以 `@CQCommand` 为例,在 PHP8 中,你可以使用以下代码达成相同的效果。
|
||||
|
||||
```php
|
||||
#[CQCommand('帮助', alias=['帮助列表', '菜单'])]
|
||||
public function help() {
|
||||
```
|
||||
|
||||
由于两者几乎完全一致,文档将不会加以区分,统称为注解。
|
||||
|
||||
> 关于原生注解的更多信息,请查阅[官方文档](https://www.php.net/manual/zh/language.attributes.php)。
|
||||
|
||||
## 注解和事件的关系
|
||||
|
||||
在炸毛框架里,注解常常被当作事件分发的一个重要角色,但注解本身又不是事件,更恰当的说,是注解代表了事件。
|
||||
|
||||
机器人开发过程中常见的 `@CQCommand`,或者是 HTTP 服务器路由绑定 `@RequestMapping` 都是相当于由对应注解代表了事件,而 `@Middleware`,`@Closed`
|
||||
等这类注解显然不代表任何事件,只能当作这个函数或类的修饰属性而已。代表了事件的注解,我们称之为**注解事件**,它会在某种事件达成条件后触发注解下方的函数本身。
|
||||
|
||||
值得注意的是,注解事件本身概念是我凭空捏造的,我不好解释所以只能创造这么一个词来代指这一抽象的概念,硬要解释的话,大致就好比一个社区里有一个卖牛奶的,有几家人订阅了每日上门送牛奶的服务,只要你打了“给我配送牛奶”的注解,他就会上门。而它送的不止一种奶,可以给你个性化定制,比如让卖牛奶的给你带包糖带瓶水,而描述这个的注解就只能做一个之前注解的修饰。假设你只写了带包糖的注解,没有写给我配送牛奶的注解,那他永远也不会给你送牛奶和糖过来。
|
||||
|
||||
## 阻断事件
|
||||
|
||||
由于炸毛框架内的注解事件统一由一个通用的事件分发器进行分发,所以你在任何注解事件内都可以用通用的方式阻断当前正在运行的事件。
|
||||
|
||||
首先就是要记得先 use 事件分发器的类:`use ZM\Event\EventDispatcher;`。
|
||||
|
||||
```php
|
||||
EventDispatcher::interrupt();
|
||||
EventDispatcher::interrupt($data); // 也可以带返回值,自定义注解事件时有用。
|
||||
```
|
||||
@@ -1,220 +0,0 @@
|
||||
# 中间件注解
|
||||
|
||||
对于 `@RequestMapping` 等注解绑定的事件函数,还支持中间件,可以完成 Session 会话、认证、日志记录等功能。中间件是用于控制 `请求到达` 和 `响应请求` 的整个流程的。从一定意义上来说相当于切面编程(AOP)。
|
||||
|
||||
在炸毛框架中,中间件最直白的意思就是注解事件执行前、执行后、执行过程中可进行插入代码但不破坏原有代码。
|
||||
|
||||
```
|
||||
@中间件1
|
||||
@带条件的注解1
|
||||
function 我的方法() {
|
||||
blablabla...
|
||||
}
|
||||
//插入中间件,下面是执行流程
|
||||
-> 判断注解1的执行条件是否为true
|
||||
-> 中间件1的前置插入代码
|
||||
-> 我的方法
|
||||
-> 中间件1的后置插入代码
|
||||
X -> 我的方法有异常时执行中间件1的异常处理
|
||||
|
||||
//不插入中间件,下面是执行流程
|
||||
-> 判断注解1的执行条件是否为true
|
||||
-> 我的方法
|
||||
X -> 有异常则直接跳到最外层被框架捕获
|
||||
```
|
||||
|
||||
中间件和事件分发器是紧密相连的,炸毛框架的内部分发器在分发注解事件的过程中会判断将要执行的事件是否含有中间件,框架内部执行流程图见下一章:事件分发器。
|
||||
|
||||
## 定义中间件
|
||||
|
||||
下方就是一个可以在终端打印路由函数运行的总时间的中间件,只需给中间件标明里面的 `@MiddlewareClass` 到中间件的类上就可以了。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Middleware;
|
||||
|
||||
use Exception;
|
||||
use ZM\Annotation\Http\HandleAfter;
|
||||
use ZM\Annotation\Http\HandleBefore;
|
||||
use ZM\Annotation\Http\HandleException;
|
||||
use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
|
||||
/**
|
||||
* @MiddlewareClass("timer")
|
||||
*/
|
||||
class TimerMiddleware implements MiddlewareInterface
|
||||
{
|
||||
private $starttime;
|
||||
|
||||
/**
|
||||
* @HandleBefore()
|
||||
* @return bool
|
||||
*/
|
||||
public function onBefore() {
|
||||
$this->starttime = microtime(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleAfter()
|
||||
*/
|
||||
public function onAfter() {
|
||||
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleException(\Exception::class)
|
||||
* @param Exception $e
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onException(Exception $e) {
|
||||
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
技术要素:
|
||||
|
||||
1. 将需要声明为中间件的 class 类标上注解 `@MiddlewareClass`,并带有参数,参数为中间件名称,字符串即可。
|
||||
2. 使用 `@MiddlewareClass` 的需要先 use:`use ZM\Annotation\Http\MiddlewareClass;`。
|
||||
3. 类成员中声明执行前插入、执行后插入和异常捕获函数也需要注解,分别是 `@HandleBefore`,`@HandleAfter`,`@HandleException`,都在 `ZM\Annotation\Http` 命名空间下。
|
||||
4. `@HandleBefore` 类似 `@CQBefore`,需要返回 bool 类型值,如果不返回,默认为 true。当为 true 时,则不会阻断执行事件函数本身。
|
||||
5. 中间件内的函数不可被绑定为注解事件。
|
||||
6. `@HandleException` 可以写多个,但其中的参数只能写想要捕获的异常的类全称,例如 `\Exception::class` 返回的就是 `\\Exception`,`\ZM\Exception\InterruptException::class` 返回的是 `ZM\\Exception\\InterruptException`,举的这两个例子这样写都是可以的。
|
||||
7. 如果 `@HandleException` 有多个的话,则会按照声明顺序依次让其捕获,看其是否为要被捕获的错误的类或父类。例如在最后一个 `@HandleException` 捕获 `\Throwable` 则最终此中间件会捕获所有异常。
|
||||
8. 中间件内可以正常使用和注解事件执行的内容同一上下文,例如 `@RequestMapping` 下你可以使用 `ctx()->getRequest()`,`@CQMessage` 可以使用 `ctx()->getMessage()` 等,以此类推。
|
||||
|
||||
## 使用中间件
|
||||
|
||||
如上图,我们举了一个非常简单的例子,打印出函数执行的时间。我们假设一个需要耗时较长的函数:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testTime")
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
public function testTime() {
|
||||
zm_sleep(3); //等待3秒再返回
|
||||
return "OK!";
|
||||
}
|
||||
```
|
||||
|
||||
在执行后,你的执行结果可能为:
|
||||
|
||||
```
|
||||
[11:18:56] [I] [#0] Using 3000.07 ms
|
||||
```
|
||||
|
||||
或者,我们也可以将中间件注解写到类上:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
class Hello {
|
||||
/**
|
||||
* @RequestMapping("/test/ping")
|
||||
*/
|
||||
public function ping(){
|
||||
return "pong";
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/test/ping2")
|
||||
*/
|
||||
public function ping2(){
|
||||
return "pong2";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果等同于给此类下每个注解事件写一个 `@Middleware`。
|
||||
|
||||
## 使用多个中间件
|
||||
|
||||
多个使用中间件可以同时生效多个流程的中间件。这里要注意,多个中间件中,`@HandleBefore` 方法中如果返回了 `false`,则不会执行接下来的中间件和事件本身要触发的函数,直接跳到最后此中间件的 `@HandleAfter` 方法。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
* @Middleware("timer1")
|
||||
* @Middleware("timer2")
|
||||
*/
|
||||
public function hello() { return "成功执行!"; }
|
||||
```
|
||||
|
||||
## 使用中间件捕获异常
|
||||
|
||||
通常情况下,如果用户定义的函数内抛出了异常(包括 `message` 等事件),会返回到框架基层去返回默认定义的内容。如果想自己捕获可以使用 `try/catch` ,但不方便复用,多处使用的话就需要重复写代码。这里可以使用中间件的异常处理方便地捕获错误。这个函数写到中间件类里即可
|
||||
|
||||
```php
|
||||
/**
|
||||
* @HandleException(\Exception::class)
|
||||
* @param Exception|null $e
|
||||
*/
|
||||
public function onThrowing(?Exception $e) {
|
||||
ctx()->getResponse()->endWithStatus(500, "Error on this.");
|
||||
}
|
||||
```
|
||||
|
||||
这里的 `@HandleException` 中的参数为要捕获的类名,注意这里面的类名的命名空间需要写全称,不能上面 use 再使用,否则会无法找到异常类。
|
||||
|
||||
`ctx()` 为获取当前协程空间绑定的 `request` 和 `response` 对象。
|
||||
|
||||
## 中间件参数
|
||||
|
||||
中间件也可以接收额外的参数。例如,你需要在执行对应操作前验证对方是否具有指定的权限,你可以建立一个 `EnsureUserHasPermission` 中间件,并接收权限名称作为参数。
|
||||
|
||||
中间件参数可以在中间件中使用 `$this->middleware->params` 获取:
|
||||
|
||||
```php
|
||||
#[MiddlewareClass('has_permission')]
|
||||
class EnsureUserHasPermission implement MiddlewareInterface
|
||||
{
|
||||
#[OnBefore]
|
||||
public function handle()
|
||||
{
|
||||
return $user->hasPermission($this->middleware->params[0]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你可以直接在中间件注解中向中间件传递参数:
|
||||
|
||||
```php
|
||||
#[Middleware('has_permission', ['can_execute_command'])]
|
||||
public function doSomething()
|
||||
{
|
||||
```
|
||||
|
||||
## 中间件加载错误处理策略
|
||||
|
||||
中间件在某些情况下可能会产生普通 PHP 异常以外的异常,不能被框架的正常错误流程捕获,所以这里额外说明了中间件异常处理的几种策略。
|
||||
|
||||
中间件异常处理策略可以在 2.5.2 版本之后通过 `global.php` 中的 `runtime` 下 `middleware_error_policy` 设置。
|
||||
|
||||
- **0**: 无论被运行事件中间件是否存在,都不抛出异常,继续执行事件。
|
||||
- **1**: 在框架启动时如果某事件被注解了一个不存在的中间件,则不抛出异常,在执行期间才检测是否存在此中间件,并抛出异常。
|
||||
- **2**: 严格的中间件检查,在框架启动时就检测所有被注解了中间件的注解事件。
|
||||
|
||||
假设我们有一个路由注解 `@RequestMapping("/test")`,同时注解了一个不存在的中间件,如下:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
* @Middleware("foo")
|
||||
*/
|
||||
public function testRoute() {
|
||||
return "I am testing middleware";
|
||||
}
|
||||
```
|
||||
|
||||
配置项为 0,此中间件类不存在的话,则会报告 warning,并直接执行此函数。
|
||||
|
||||
配置项为 1,在访问此路由执行此函数时会抛出异常,中断此次事件。
|
||||
|
||||
配置项为 2,在框架启动时抛出致命异常。
|
||||
@@ -1,354 +0,0 @@
|
||||
# 机器人注解事件
|
||||
|
||||
QQ 机器人事件是指 CQHTTP 插件发来的 Event 事件,被框架处理后触发到单个类中方法的事件。
|
||||
|
||||
为了便于开发,这里的注解类对应 CQHTTP 插件返回的 `post_type` 类型,对号入座即可。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
在使用注解绑定事件过程中,如果无 **必需** 参数,可一个参数也不写,效果就是此事件任何情况下都会调用此方法。例如:`@CQMessage()`
|
||||
|
||||
:::
|
||||
|
||||
事件是用户需要从 OneBot 被动接收的数据,有以下几个大类:
|
||||
|
||||
- [消息事件](#cqmessage),包括私聊消息、群消息等,被 [`@CQCommand`](#cqcommand),`@CQMessage` 注解处理。
|
||||
|
||||
- [通知事件](#cqnotice),包括群成员变动、好友变动等,被 `@CQNotice` 注解事件处理。
|
||||
|
||||
- [请求事件](#cqrequest),包括加群请求、加好友请求等,被 `@CQRequest` 注解事件处理。
|
||||
|
||||
- [元事件](#cqmetaevent),包括 OneBot 生命周期、心跳等,被 `@CQMetaEvent` 注解事件处理。
|
||||
|
||||
## 注解事件参照表
|
||||
|
||||
| 注解名称 | 类所在命名全称 | 作用 |
|
||||
| ------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------ |
|
||||
| [`@CQBefore`](/event/robot-annotations/#cqbefore) | `\ZM\Annotation\CQBefore` | OneBot 各类事件前触发的,可当作事件过滤器使用 |
|
||||
| [`@CQAfter`](/event/robot-annotations/#cqafter) | `\ZM\Annotation\CQAfter` | OneBot 各类事件后触发的 |
|
||||
| [`@CQMessage`](/event/robot-annotations/#cqmessage) | `\ZM\Annotation\CQMessage` | OneBot 中消息类事件的触发(机器人消息)事件 |
|
||||
| [`@CQCommand`](/event/robot-annotations/#cqcommand) | `\ZM\Annotation\CQCommand` | OneBot 中消息类事件的触发(机器人消息)事件,但是被封装为指令型的,无需自己切割命令式 |
|
||||
| [`@CQNotice`](/event/robot-annotations/#cqnotice) | `\ZM\Annotation\CQNotice` | OneBot 中通知类事件的触发(机器人消息)事件 |
|
||||
| [`@CQRequest`](/event/robot-annotations/#cqrequest) | `\ZM\Annotation\CQRequest` | OneBot 中请求类事件的触发(机器人消息)事件,一般带有请求信息,可联动相关响应的 API 完成功能编写 |
|
||||
| [`@CQMetaEvent`](/event/robot-annotations/#cqmetaevent) | `\ZM\Annotation\CQMetaEvent` | OneBot 中涉及 OneBot 实现本身的一些和机器人事件无关的元事件,比如 WS 连接的心跳包 |
|
||||
|
||||
## CQMessage()
|
||||
|
||||
QQ 收到消息后触发的事件对应注解。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---- | ----------- |
|
||||
| 名称 | `@CQMessage` |
|
||||
| 触发前提 | 当 `post_type` 为 `message` 时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQMessage` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------- | -------------------------------------- | ---- |
|
||||
| message_type | `string`,支持填入 `private`,`group` | 限定消息事件的来源类型,如私聊或群消息 | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定消息发送用户 ID(QQ 号) | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定消息发送来源群 ID(QQ 群号) | 空 |
|
||||
| message | `string` | 限定消息内容文本 | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
下面这个例子的注释用途就是:
|
||||
|
||||
- 在用户 QQ 为 `123456` 的用户私聊给机器人发消息后机器人回复内容。
|
||||
- 用户发送文字为 `hello` 时返回 `你好啊,xxx` 的消息。
|
||||
|
||||
代码
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQMessage(message_type="private",user_id=123456)
|
||||
*/
|
||||
public function test() {
|
||||
return "你和机器人私聊发送了这些文本:".ctx()->getMessage();
|
||||
}
|
||||
/**
|
||||
* @CQMessage(message="hello")
|
||||
*/
|
||||
public function hello() {
|
||||
return "你好啊,".ctx()->getUserId();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'假设我是私聊机器人'},
|
||||
{type:1,content:'你和机器人私聊发送了这些文本:假设我是私聊机器人'},
|
||||
{type:2,content:'假设我现在切到群里,在群里发hello'},
|
||||
{type:0,content:'hello'},
|
||||
{type:1,content:'你好啊,123456'},
|
||||
]"></chat-box>
|
||||
|
||||
## CQCommand()
|
||||
|
||||
此注解是对 `@CQMessage` 类别的再封装,是命令解析格式处理消息的利器。例如,你想写一个疫情上报,指令是 `疫情 城市名称`,那么此方式来解析用户消息会更加方便。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | -------------------------------------------------------- |
|
||||
| 名称 | `@CQCommand` |
|
||||
| 触发前提 | 当根据参数规则匹配到用户命令式消息时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQCommand` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 当方法返回字符串时,效果等同于执行 `ctx()->reply("xxx")` |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------- | ------------------------------------------------------ | ---- |
|
||||
| match | `string` | 匹配第一个词的命令式消息,如 `天气 北京` 中的 `天气` | 空 |
|
||||
| pattern | `string` | 根据 * 号通配符进行模式匹配用户消息,如 `查询*天气` | 空 |
|
||||
| regex | `string`,限定正则表达式 | 匹配正则表达式匹配到的用户消息 | 空 |
|
||||
| start_with | `string` | 匹配消息开头相匹配的消息,如 `我叫炸毛`,这里写 `我叫` | 空 |
|
||||
| end_with | `string` | 匹配消息结尾相匹配的消息,以 `start_with` 类推 | 空 |
|
||||
| keyword | `string` | 匹配消息中有相关关键词的消息 | 空 |
|
||||
| alias | `array[string]` | `match` 匹配到命令的别名,数组形式 | `{}` |
|
||||
| message_type | `string`,支持填入 `private`,`group` | 限定消息事件的来源类型,同 `@CQMessage` | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定消息发送用户 ID,同 `@CQMessage` | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定消息发送来源群 ID,同 `@CQMessage` | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
::: warning 注意
|
||||
|
||||
在 `@CQCommand` 注解事件中,从 `match` 到 `keyword` 六个参数中,必须且只能定义一个,`alias` 目前只能和 `match` 参数同时使用;
|
||||
|
||||
框架内部对于同一条消息事件,优先处理 `@CQCommand` 注解事件,如果未匹配到任何注解事件,则才会继续执行 `@CQMessage` 注解事件。
|
||||
|
||||
:::
|
||||
|
||||
- 参数 `match` 匹配模式是:遇到空格、换行就会切分,比如 `点歌 xxx yyy` 会被分割为 `[点歌,xxx,yyy]`,然后抽取第一个词做为命令去匹配,剩下的为参数。
|
||||
- 参数 `pattern` 匹配模式是:\* 号位置变成参数,比如 `从*到*的随机数`,我们输入 `从1到9的随机数`,成功匹配,参数列表:`[1,9]`。
|
||||
- 参数 `regex` 匹配模式为 PHP 标准的 pcre 正则表达式,比如 `([01][0-9][2][0-3]):[0-5][0-9]` 用来匹配 `22:45`。
|
||||
- 参数 `start_with`, `end_with` 和 `keyword` 都是根据消息内容开头、结尾或者内容包含是否匹配来匹配,这里就不多说了,你懂的。
|
||||
- 参数 `alias` 用的时候一般是这样:`@CQCommand(match="你好",alias={"你好啊","你是谁"})`,用以扩充同义词下命令的适配广度。
|
||||
|
||||
### 用法
|
||||
|
||||
我们以参数 `match` 写一个简单的 demo:
|
||||
|
||||
代码
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand(match="疫情",alias={"COVID"})
|
||||
*/
|
||||
public function virus(){
|
||||
$city = ctx()->getNextArg("请输入城市名称");
|
||||
return "城市 ".$city." 的疫情状况如下:"."{这里假装是疫情接口返回的数据}";
|
||||
}
|
||||
/**
|
||||
* 如果选择使用 match 参数的话,可以省略 `match=`
|
||||
* @CQCommand("掷硬币")
|
||||
*/
|
||||
public function randChoice() {
|
||||
return "你看到的是:" . (mt_rand(0,1) ? "正面" : "反面");
|
||||
}
|
||||
/**
|
||||
* @CQCommand(pattern="*把*翻译成*")
|
||||
*/
|
||||
public function translate() {
|
||||
ctx()->getNextArg(); // 为什么需要单独调用一次呢?看下面例子就知道啦
|
||||
$text = ctx()->getNextArg(); // 获取第二个星号匹配的内容
|
||||
$target = ctx()->getNextArg(); // 获取第三个星号匹配的内容
|
||||
// 这里 FakeTranslateAPI 是假设我们对接了一个翻译的 API,开发时请替换为自己的接口。
|
||||
return "翻译结果:" . FakeTranslateAPI::translate($text, $target);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'疫情 北京'},
|
||||
{type:1,content:'城市 北京 的疫情状况如下:blablablabla'},
|
||||
{type:0,content:'COVID 香港'},
|
||||
{type:1,content:'城市 香港 的疫情状况如下:blablablabla'},
|
||||
{type:0,content:'掷硬币'},
|
||||
{type:1,content:'你看到的是:正面'},
|
||||
{type:0,content:'我想把我爱你翻译成英语'},
|
||||
{type:1,content:'翻译结果:I love you!'},
|
||||
]"></chat-box>
|
||||
|
||||
## CQNotice()
|
||||
|
||||
通知事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| 名称 | `@CQNotice` |
|
||||
| 触发前提 | 当 `post_type` 为 `notice` 时触发(通知类事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQNotice` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ----------- | ------------------------------------ | ------------------------------------------------------------ | ---- |
|
||||
| notice_type | `string`,支持填入 onebot 标准的内容 | 限定通知事件的类型,见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定通知事件用户 ID(QQ 号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| group_id | `int64` 或 `string` | 限定通知事件群 ID(QQ 群号),同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| operator_id | `int64` 或 `string` | 限定操作者 QQ 号,同上见 [OneBot - 通知事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/notice.md) | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQRequest()
|
||||
|
||||
请求事件。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------ |
|
||||
| 名称 | `@CQRequest` |
|
||||
| 触发前提 | 当 `post_type` 为 `request` 时触发(通知类事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQRequest` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| ------------ | ------------------------------------ | ------------------------------------------------------------ | ---- |
|
||||
| request_type | `string`,支持填入 onebot 标准的内容 | 限定请求事件的类型,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| user_id | `int64` 或 `string` | 限定请求事件当事人用户 ID(QQ 号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| sub_type | `string` | 限定请求事件来源群 ID(QQ 群号),见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| comment | `string` | 限定验证消息内容,见 [OneBot - 请求事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/request.md) | 空 |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQMetaEvent()
|
||||
|
||||
元事件,元事件不属于用户交互的一部分,消息、通知、请求三大类事件是与聊天软件直接相关的、机器人真实接收到的事件,除了这些,OneBot 自己还会产生一类事件,这里称之为「元事件」,例如生命周期事件、心跳事件等,这类事件与兼容 OneBot 的客户端和炸毛框架本身的运行状态有关,而与聊天软件无关。元事件的上报方式和普通事件完全一样。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------- |
|
||||
| 名称 | `@CQMetaEvent` |
|
||||
| 触发前提 | 当 `post_type` 为 `meta_event` 时触发(元事件上报时) |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQMetaEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无作用 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| --------------- | ------------------ | ------------------------------------------------------------ | ---- |
|
||||
| meta_event_type | `string`,**必需** | 限定元事件的类型,见 [OneBot - 元事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/meta.md) | |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
TODO:先放着,有时间再更。
|
||||
|
||||
## CQBefore()
|
||||
|
||||
所有机器人事件的前置注解事件,一般用作消息过滤、全局日志、全局替换等。
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------------ |
|
||||
| 名称 | `@CQBefore` |
|
||||
| 触发前提 | 当 `post_type` 等于参数 `cq_event` 时触发 |
|
||||
| 命名空间 | `ZM\Annotation\CQ\CQBefore` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 仅可返回 `bool`,如果为 `false`,则阻断 `cq_event` 类的所有事件防止被执行 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | ------------------------------------------------------------ | ------------------------ | ---- |
|
||||
| cq_event | `string`,**必需**,支持 `message`,`notice`,`request`,`meta_event` | 限定机器人时间的类型 | |
|
||||
| level | `int` | 事件优先级(越大越靠前) | 20 |
|
||||
|
||||
### 用法
|
||||
|
||||
代码
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
class Test {
|
||||
/**
|
||||
* @CQBefore("message")
|
||||
*/
|
||||
public function filter(){
|
||||
// 可用于敏感词,如政治相关的词语不响应其他模块
|
||||
if(mb_strpos(ctx()->getMessage(), "谷歌") !== false) return false;
|
||||
else return true;
|
||||
}
|
||||
/**
|
||||
* @CQCommand("百科")
|
||||
*/
|
||||
public function wiki() {
|
||||
$content = ctx()->getNextArg("请说你要查百科的内容");
|
||||
// 这里假设你对接了一个查百科的接口
|
||||
return "已搜到匹配 $content 的如下结果:".FakeAPI::searchWiki($content);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'百科 北京'},
|
||||
{type:1,content:'已搜到匹配 北京 的如下结果:blablabla'},
|
||||
{type:0,content:'百科 谷歌被封'},
|
||||
{type:2,content:'机器人没有任何回复'},
|
||||
]"></chat-box>
|
||||
|
||||
::: warning 注意
|
||||
|
||||
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
|
||||
|
||||
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
|
||||
|
||||
使用 `ctx()->waitMessage()` 时等待用户输入下一条消息功能和 CQBefore 配合过滤消息时需注意,见 [FAQ - CQBefore 过滤不了 waitMessage](/FAQ/wait-message-cqbefore/)
|
||||
|
||||
:::
|
||||
|
||||
## CQAfter()
|
||||
|
||||
同上。只是在以上所有事件都调用后才会调用的。
|
||||
|
||||
## CQAPIResponse()
|
||||
|
||||
TODO:还没写完,先放着,有时间再更。
|
||||
@@ -1,237 +0,0 @@
|
||||
# 路由注解事件
|
||||
|
||||
炸毛框架提供了一个简易但是高效易用的 HTTP 路由注解,你可以使用路由功能来开发任何 Web 应用微服务、API 接口、中间件等。
|
||||
|
||||
::: tip 开发提示
|
||||
|
||||
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不啰嗦。
|
||||
|
||||
:::
|
||||
|
||||
## 控制器和路由
|
||||
|
||||
Controller 和 Route 为路由注解事件的核心注解事件,其中 Controller 的注解事件为 `@Controller`,Route 的注解事件为 `@RequestMapping`。
|
||||
|
||||
### Controller()
|
||||
|
||||
#### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------- |
|
||||
| 名称 | `@Controller` |
|
||||
| 触发前提 | 当路由 url 匹配到时进入触发 |
|
||||
| 命名空间 | `ZM\Annotation\Http\Controller` |
|
||||
| 适用位置 | 类 |
|
||||
| 返回值处理 | 对类注解修饰,无返回值 |
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------------- | ------------ | ---- |
|
||||
| prefix | `string`,必需 | 控制器的 url | 空 |
|
||||
|
||||
### RequestMapping()
|
||||
|
||||
#### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ----------------------------------------------------------- |
|
||||
| 名称 | `@RequestMapping` |
|
||||
| 触发前提 | 当路由 url 匹配到时进入触发 |
|
||||
| 命名空间 | `ZM\Annotation\Http\RequestMapping` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 返回类型是 `string` 时,自动调用 HTTP 响应并返回 200 状态码 |
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------------- | ----------------------------------------------------------- | -------------------------- | ------------------------------------------ |
|
||||
| route | `string`,必需 | 控制器的 url | 空 |
|
||||
| name | `string` | 路由的名称 | 空 |
|
||||
| request_method | `array`,限定 `RequestMethod::GET` 等常量 | 限制激活路由的 HTTP 方法 | `[RequestMethod::GET,RequestMethod::POST]` |
|
||||
| params | `array`,当路由中含有如 `{id}` 类似的动态路由时,会动态改变 | 动态参数的路由参数值的绑定 | `[]` |
|
||||
|
||||
#### 函数调用参数
|
||||
|
||||
- `$param`:如果路由中存在变量(动态路由),则会把动态路由所匹配的参数放入 `$param` 数组中。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping(route="/test/{ass}")
|
||||
*/
|
||||
public function testName($param) {
|
||||
return "Your name is ".($param["ass"] ?? "unknown");
|
||||
}
|
||||
```
|
||||
|
||||
### 路由示例
|
||||
|
||||
代码
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\Http\Controller;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
/**
|
||||
* @Controller("/api")
|
||||
*/
|
||||
class Hello {
|
||||
/**
|
||||
* @RequestMapping("/index")
|
||||
*/
|
||||
public function index(){
|
||||
ctx()->getResponse()->end("This is API index page"); // 使用上下文获取响应对象
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/ping")
|
||||
*/
|
||||
public function ping(){
|
||||
return "pong"; // 直接返回字符串
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
::: tip 效果描述
|
||||
|
||||
当访问浏览器的 `http://localhost:20001/api/index` 时,浏览器会返回 `This is API index page`,当访问 `/api/ping` 的 url 时,浏览器会返回 `pong`。
|
||||
|
||||
```
|
||||
/ -> 无任何路由
|
||||
/api/index -> Hello->index
|
||||
/api/ping -> Hello->ping
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: tip 提示
|
||||
|
||||
当 `@Controller` 为 `/` 的时候,效果和不写是一样的,`@RequestMapping` 为 `/` 或 `/index/inside` 等多级路由也是可以的。
|
||||
|
||||
:::
|
||||
|
||||
### 绑定参数
|
||||
|
||||
在 `@RequestMapping` 中,不仅可以写静态的路由地址,也可以写绑定的参数。例如:`@RequestMapping(route="/index/{name}")`,则访问 `/index/xxx` 的时候,你在函数方法内可以这样获取此参数:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/index/{name}")
|
||||
*/
|
||||
public function index($arg) {
|
||||
return "Your param 'name' is ".$arg["name"];
|
||||
}
|
||||
```
|
||||
|
||||
## 获取请求参数 GET / POST
|
||||
|
||||
炸毛框架支持获取外部 HTTP 请求进来的 GET 和 POST 请求,通过获取 HTTP 请求对象 [Request](/advanced/inside-class/) 即可。对象具体属性和方法点这个链接进去就行。
|
||||
|
||||
### 示例
|
||||
|
||||
#### 获取 GET
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$get = ctx()->getRequest()->get;
|
||||
if(isset($get["name"])) return "hello, ".$get["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取 POST(x-www-form-urlencoded)
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$post = ctx()->getRequest()->post;
|
||||
if(isset($post["name"])) return "hello, ".$post["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取 JSON POST
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/testUrl")
|
||||
*/
|
||||
public function testUrl() {
|
||||
$post = ctx()->getRequest()->rawContent();
|
||||
$json = json_decode($post, true);
|
||||
if ($json === null) return "Invalid json data!";
|
||||
if(isset($json["name"])) return "hello, ".$json["name"];
|
||||
else return "Unknown name!!";
|
||||
}
|
||||
```
|
||||
|
||||
## 设置路由请求方式
|
||||
|
||||
如果想要设置允许请求控制器的 HTTP 请求方式,可以使用方法在控制器中的 `@RequestMapping` 注解配置 `method` 参数,可以是 `GET`,`POST`,`PUT`, `PATCH`,`DELETE`,`OPTIONS`,`HEAD` 中的一个或多个。
|
||||
|
||||
- 限定 HTTP 方法:`@RequestMapping(method="GET")`,`@RequestMapping(method={"GET","POST"})`
|
||||
|
||||
## 静态文件服务器
|
||||
|
||||
框架支持了静态文件的访问。如需使用,则需要先到配置文件中配置相应的 `static_file_server` 参数中 `status` 为 `true`。
|
||||
|
||||
框架分为两种静态文件服务器,一种是全局的静态文件服务器,比如框架部署在 `http://127.0.0.1:20001/` 上通过 HTTP 访问,如果没有访问到 `@RequestMapping` 注解事件注册的路由地址,则会通过 url 自动查找静态文件服务器设置的根路径下面的文件,如果都不存在则会返回 404。
|
||||
|
||||
### 配置全局静态文件服务器
|
||||
|
||||
我们假设在你写的框架应用的根目录下,有如下文件和内容:
|
||||
|
||||
```
|
||||
resources/html/hello.html (下面是内容)
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
框架文档内容太多了,写不完!!!
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
然后在 `global.php` 配置文件中静态文件服务器参数为:
|
||||
|
||||
```php
|
||||
/** 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => true,
|
||||
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html'
|
||||
]
|
||||
];
|
||||
```
|
||||
|
||||
最终,我们通过 `vendor/bin/start server` 等方式,启动框架后,浏览器访问 `http://127.0.0.1:20001/hello.html` 即可获取内容。
|
||||
|
||||
### 配置局部静态文件服务器
|
||||
|
||||
所涉及的类的命名空间:`use ZM\Http\StaticFileHandler;`
|
||||
|
||||
局部静态文件服务器一般用于,比如机器人要发送图片,或者给其他 HTTP 服务提供文件下载的接口时可用。我们假设写了一个图片收集的一个静态文件夹区域,将其中一个子路由当作图片静态目录:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/images/{filename}")
|
||||
* @param $param
|
||||
* @return StaticFileHandler
|
||||
*/
|
||||
public function staticImage($param) {
|
||||
Console::info("[下载图片] " . $param["filename"]);
|
||||
return new StaticFileHandler($param["filename"], "/path/to/your/image_dir/");
|
||||
}
|
||||
```
|
||||
|
||||
这样当用户访问 `http://框架地址/images/aaa.jpg` 就可以快速地调用此路由下的局部文件服务器功能了。
|
||||
Reference in New Issue
Block a user