mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-18 13:14:52 +08:00
renew docs (#205)
This commit is contained in:
parent
aa28675016
commit
81a5e48b26
@ -26,12 +26,7 @@ module.exports = {
|
||||
activeHeaderLinks: false,
|
||||
nav: [
|
||||
{ text: '指南', link: '/guide/' },
|
||||
{ text: '事件和注解', link: '/event/' },
|
||||
{ text: '组件', link: '/component/' },
|
||||
{ text: '进阶', link: '/advanced/' },
|
||||
{ text: 'API', link: '/doxy/', target: '_blank' },
|
||||
{ text: 'FAQ', link: '/faq/' },
|
||||
{ text: '更新日志', link: '/update/v2/' },
|
||||
{ text: '炸毛框架 v1', link: 'https://docs-v1.zhamao.xin/' }
|
||||
],
|
||||
sidebar: {
|
||||
@ -43,147 +38,12 @@ module.exports = {
|
||||
children: [
|
||||
'',
|
||||
'installation',
|
||||
'quickstart-robot',
|
||||
'quickstart-http',
|
||||
'onebot-choose',
|
||||
'basic-config',
|
||||
'write-module',
|
||||
'register-event',
|
||||
'upgrade',
|
||||
'errcode'
|
||||
'configuration',
|
||||
'structure',
|
||||
'get_started',
|
||||
]
|
||||
}
|
||||
],
|
||||
'/event/': [
|
||||
{
|
||||
title: '事件和注解',
|
||||
collapsable: false,
|
||||
sidebarDepth: 1,
|
||||
children: [
|
||||
'',
|
||||
'robot-annotations',
|
||||
'route-annotations',
|
||||
'framework-annotations',
|
||||
'middleware',
|
||||
'custom-annotations',
|
||||
'event-dispatcher'
|
||||
]
|
||||
}
|
||||
],
|
||||
'/component/': [
|
||||
'',
|
||||
{
|
||||
title: '聊天机器人组件',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'bot/robot-api-12',
|
||||
'bot/robot-api',
|
||||
'bot/cqcode',
|
||||
'bot/message-util',
|
||||
'bot/access-token',
|
||||
'bot/turing-api',
|
||||
'bot/help-generator.md',
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '存储组件',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'store/light-cache',
|
||||
'store/mysql',
|
||||
'store/mysql-db',
|
||||
'store/redis',
|
||||
'store/atomics',
|
||||
'store/spin-lock',
|
||||
'store/data-provider'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '通用组件',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'common/context',
|
||||
'common/coroutine-pool',
|
||||
'common/singleton-trait',
|
||||
'common/zmutil',
|
||||
'common/global-functions',
|
||||
'common/console',
|
||||
'common/task-worker',
|
||||
'common/remote-terminal',
|
||||
'common/event-tracer'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'HTTP 组件',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'http/zmrequest',
|
||||
'http/route-manager'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '模块/插件管理',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'module/module-pack',
|
||||
'module/module-unpack'
|
||||
]
|
||||
}
|
||||
],
|
||||
'/advanced/': [
|
||||
'',
|
||||
{
|
||||
title: '框架高级开发',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'framework-structure',
|
||||
'custom-start',
|
||||
'manually-install',
|
||||
'inside-class',
|
||||
'multi-process',
|
||||
'task-worker'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '开发实战教程',
|
||||
collapsable: true,
|
||||
sidebarDepth: 2,
|
||||
children: [
|
||||
'connect-ws-client',
|
||||
'example/admin',
|
||||
'example/integrate-qingyunke-chatbot',
|
||||
'example/weather-bot'
|
||||
]
|
||||
},
|
||||
],
|
||||
'/faq/': [
|
||||
'',
|
||||
'to-v2',
|
||||
'usual-question',
|
||||
'address-already-in-use',
|
||||
'display-deadlock',
|
||||
'light-cache-wrong',
|
||||
'wait-message-cqbefore'
|
||||
],
|
||||
'/update/': [
|
||||
{
|
||||
title: '更新日志',
|
||||
collapsable: true,
|
||||
sidebarDepth: 0,
|
||||
children: [
|
||||
'v2',
|
||||
'v1',
|
||||
'build-update'
|
||||
]
|
||||
},
|
||||
'config'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
# 进阶开发
|
||||
在本章,下面的部分将详细说明一些具体的案例和自定义框架的操作。
|
||||
|
||||
> 更多进阶教程敬请期待....(或者你可以选择提 Issue 到框架 GitHub,有需求就写入文档)
|
||||
@ -1,144 +0,0 @@
|
||||
# 接入 WebSocket 客户端
|
||||
|
||||
炸毛框架其实从本质上讲,就是一个 HTTP + WebSocket 服务器,所以框架也支持对接其他任何 HTTP 客户端和 WebSocket 客户端,实际上炸毛框架非常适合用 WebSocket 做在线的 IM 聊天通讯,也可以方便地进行 WS 通信。这里主要说明如何对接一个自定义的 WebSocket 客户端。
|
||||
|
||||
## 类型指定
|
||||
|
||||
由于 WebSocket 连接都具有同样的性质,没有状态,所以在建立 WebSocket 连接的时候,需要客户端表明自己的身份和类型。指定客户端连接类型的方式有两种:
|
||||
|
||||
- `GET` 参数传递,在连接的时候,加上 GET 参数 `type` 即可。比如 js 中 WebSocket 建立时地址写:`ws://127.0.0.1:20001/?type=foo`,这时传入的连接就是 `foo` 类型。
|
||||
- `Header` 传递,用户需要在建立连接时指定 HTTP 的头部信息 `X-Client-Role`,例如 `X-Client-Role: foo`,这时传入的连接就是 `foo` 类型。
|
||||
|
||||
以上两种方式,`Header` 方式比 `GET` 方式优先级要高,如果两者均没有指定,框架会将此连接当作 `default` 类型接入。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
对于对接 OneBot 标准的机器人客户端,只要符合 OneBot 标准,即 `X-Client-Role` 会自动带上 `universal`、`qq` 等字样,就会自动标记为 `qq` 类型。
|
||||
|
||||
:::
|
||||
|
||||
## 逻辑编写
|
||||
|
||||
传入连接后,我们就能通过注解事件绑定来做我们自己想做的事情了!比如下方是传入类型为 foo 连接要做的事情
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Console\Console;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
class Hello {
|
||||
/**
|
||||
* @OnOpenEvent("foo")
|
||||
*/
|
||||
public function onFooConnect(ConnectionObject $conn) {
|
||||
Console::info($conn->getName()." 已连接!");
|
||||
}
|
||||
```
|
||||
|
||||
以上作用就是在终端输出 `foo 已连接!` 这个提示的。关于 `ConnectionObject` 对象,见下方。
|
||||
|
||||
## WS 连接对象
|
||||
|
||||
对于每一个 WebSocket 连接,框架内都有一个专属的操作类,有获取类型名称、保存链接参数和属性以及获取文件标识符等功能。
|
||||
|
||||
### getFd()
|
||||
|
||||
获取文件标示符,用于发送消息、接收消息等。这个参数获取的 `fd` 是 Swoole 指定的,用于发送信息等。
|
||||
|
||||
```php
|
||||
$fd = $conn->getFd();
|
||||
server()->send($fd, "hello world");
|
||||
```
|
||||
|
||||
> WebSocket 是全双工的,所以发送和接收其实是互不干扰的,你可以不仅仅在 WebSocket 相关的上下文中,还可以比如在 HTTP 或者机器人上下文中给别的 WebSocket 客户端发请求。
|
||||
|
||||
### getName()
|
||||
|
||||
获取连接对象绑定的连接类型,例如上方提到的 `foo`、`default` 等。
|
||||
|
||||
```php
|
||||
Console::info("当前连接类型:".$conn->getName()); //当前连接类型:foo
|
||||
```
|
||||
|
||||
### setName()
|
||||
|
||||
改变连接对象绑定的连接类型,例如从 `foo` 改为 `bar`。
|
||||
|
||||
```php
|
||||
$s = $conn->getName(); // foo
|
||||
$conn->setName("bar");
|
||||
$s = $conn->getName(); // bar
|
||||
```
|
||||
|
||||
### getOptions()
|
||||
|
||||
获取此连接存储的所有参数,以数组形式。存储内容见下方 `setOption()`。
|
||||
|
||||
格式:`["参数1" => {参数1的值}, "参数2" => {参数2的值}]`
|
||||
|
||||
### getOption()
|
||||
|
||||
获取此连接存储的参数,获取指定名称的,此方法拥有一个参数 `$key`,指定即可获取。
|
||||
|
||||
如果没有对应参数,则返回 `null`。
|
||||
|
||||
我们在前面的机器人部分知道,框架主要是用于机器人的连接,那么机器人客户端在连接后,比如我们想知道这个机器人的 WS 连接对应的是哪个 QQ 号的机器人,我们就可以用 `getOption("connect_id")` 来获取。这个 `connect_id` 是 OneBot 标准的客户端接入后自动填入的一个参数。例如,我们想在机器人接入后打出接入机器人的 QQ 号:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnOpenEvent("qq")
|
||||
*/
|
||||
public function onQQConnect($conn) {
|
||||
Console::success("机器人 ".$conn->getOption("connect_id")." 已连接!"); // 机器人 123456 已连接!
|
||||
}
|
||||
```
|
||||
|
||||
### setOption()
|
||||
|
||||
设置连接存储的参数。参数:`setOption($key, $value)`。`$key` 限定为 `connect_id` 一种。(因为目前有了 LightCache,所以这里暂时不提供别的 key 设定)
|
||||
|
||||
```php
|
||||
$conn->setOption("connect_id", "asdasdasd"); // $value 最长长度为 29
|
||||
```
|
||||
|
||||
## 发送到 WebSocket 客户端
|
||||
|
||||
很简单,从上面获取到 `fd` 后使用下面的方式就可以了~
|
||||
|
||||
```php
|
||||
server()->push($conn->getFd(), "hello"); // 第二个为 string 类型的参数
|
||||
```
|
||||
|
||||
## 从客户端接收
|
||||
|
||||
接收消息必须从 `@OnMessageEvent` 注解事件下接收,使用上下文 `ctx()->getFrame()` 获取消息帧。
|
||||
|
||||
从这里获取的 `Frame` 对象,见 [Swoole 文档 - Frame](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
|
||||
|
||||
Frame 对象有四个参数:
|
||||
|
||||
- `$frame->fd`:获取发来帧的 fd
|
||||
- `$frame->data`:数据本体
|
||||
- `$frame->opcode`:数据类型 int 值,见 [Swoole 文档 - 数据帧类型](https://wiki.swoole.com/#/websocket_server?id=%e6%95%b0%e6%8d%ae%e5%b8%a7%e7%b1%bb%e5%9e%8b)
|
||||
- `$frame->finish`:是否发送完毕,bool
|
||||
|
||||
下面以接收一个 json 字符串为例,并进行后续的解析:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnMessageEvent("foo")
|
||||
*/
|
||||
public function onMessage() {
|
||||
$frame = ctx()->getFrame();
|
||||
$json_str = $frame->data; // 假设传入的是 {"key1":"value1","k2":"v2"}
|
||||
$json = json_decode($json_str, true);
|
||||
Console::info("key1 的值是:" . $json["key1"]);
|
||||
}
|
||||
```
|
||||
|
||||
## 关闭连接
|
||||
|
||||
```php
|
||||
server()->close($conn->getFd());
|
||||
```
|
||||
@ -1,136 +0,0 @@
|
||||
# 框架高级启动
|
||||
|
||||
## 框架下载方式
|
||||
|
||||
从前面的几章中,我们了解到框架有多种下载到本地的方式。
|
||||
|
||||
- Composer 依赖模式
|
||||
- Starter 从模板创建模式(等同于 Composer 模式)
|
||||
- 源码模式
|
||||
- Phar Composer 依赖模式
|
||||
- Phar 源码模式
|
||||
|
||||
### Composer 依赖模式
|
||||
|
||||
从 Composer 依赖加载框架是一种拉取框架的方式,这种方式的优点在于,你可以直观地感受到是如何使用框架从零开始一个完整的项目的过程。
|
||||
|
||||
从 Composer 依赖的启动步骤:
|
||||
|
||||
```bash
|
||||
mkdir my-bot # 新建一个空的文件夹
|
||||
cd my-bot/
|
||||
composer require zhamao/framework # 从 composer 拉取后会自动部署 autoload 和 composer.json 等内容
|
||||
|
||||
# 使用命令初始化框架
|
||||
vendor/bin/start init
|
||||
|
||||
# 启动框架
|
||||
vendor/bin/start server
|
||||
```
|
||||
|
||||
注意:使用 `init` 命令时,会给当前目录解压以下文件:
|
||||
|
||||
```php
|
||||
$extract_files = [
|
||||
"/config/global.php", // 全局配置文件
|
||||
"/.gitignore", // git 排除文件
|
||||
"/config/file_header.json", // HTTP 文件头
|
||||
"/config/console_color.json", // 终端颜色主题文件
|
||||
"/config/motd.txt", // 框架启动时自定义的 motd
|
||||
"/src/Module/Example/Hello.php", // 框架自带的示例模块
|
||||
"/src/Module/Middleware/TimerMiddleware.php", // 框架自带的函数运行时间监控中间件
|
||||
"/src/Custom/global_function.php" // 用户可在这里自定义编写自己的全局函数
|
||||
];
|
||||
```
|
||||
|
||||
经过 init 解压这些文件后,你的框架就能正常运行且开始编写代码了!
|
||||
|
||||
### Starter 模板模式
|
||||
|
||||
从模板新建其实原理和 Composer 依赖模式完全一样,只不过,这个过程是使用模板仓库新建的项目,使用 Composer 自带的 `create-project` 方式创建的。starter 也是一个 GitHub 项目,见 [地址](https://github.com/zhamao-robot/zhamao-framework-starter)。
|
||||
|
||||
```bash
|
||||
composer create-project zhamao/framework-starter my-bot/ # my-bot 是你自定义的文件夹名称,和上方相同
|
||||
cd my-bot
|
||||
vendor/bin/start server # 启动框架
|
||||
```
|
||||
|
||||
Starter 模式相当于直接从 GitHub 拉取 `zhamao-framework-starter` 项目,然后执行 `composer update`。
|
||||
|
||||
那和 Composer 依赖模式有什么区别呢?没区别!构建出来的框架和文件是一模一样的!使用 Composer 依赖模式,使用 `init` 命令后,文件会和 `zhamao-framework-starter` 仓库拉取回来的模板一模一样!(或者换句话说,这个仓库就是使用 `init` 命令生成的文件的)
|
||||
|
||||
那使用哪种好呢?看你自己!如果你想给你自己的已有项目套上炸毛框架,那么就推荐使用 Composer 依赖模式,如果是从 0 开始编写框架模块,则推荐使用模板模式。
|
||||
|
||||
### 源码模式
|
||||
|
||||
源码模式和以上两种方案都不一样,源码模式允许你对框架本身进行一系列修改,框架本体就可以直接运行。
|
||||
|
||||
Composer 依赖模式(以及模板模式)和源码模式的区别是:
|
||||
|
||||
- 依赖模式和模板模式是通过 library 方式引入框架的,框架本身会放在 composer 的 `vendor/` 目录下,从 composer 引入的 library 相当于子集,vendor 目录下的文件最好不要手动修改(应该都知道吧),所以框架本身也只是加载了进来。
|
||||
- 源码模式相当于直接从框架源码目录运行框架和模块,框架源码都在 `src/ZM` 目录下,默认的示例模块都在 `src/Module` 下,是同级目录。而此时的 `vendor/` 目录只包含了框架依赖的外部组件,例如注解解析器和 psysh 等。
|
||||
|
||||
源码模式可以方便地调试和修改框架本身,拉取方式很简单,用 `git clone` 或从 GitHub 下载最新版的源码包解压即可。
|
||||
|
||||
```bash
|
||||
git clone https://github.com/zhamao-robot/zhamao-framework.git
|
||||
cd zhamao-framework/
|
||||
bin/start server # 第一次运行时会提示一个“框架源码模式需要在autoload文件中添加Module目录为自动加载”
|
||||
composer update # 更新 autoload 文件,应用刚才上一步添加的 `src/Module` 文件夹下的模块自动加载
|
||||
bin/start server # 通过源码模式启动框架
|
||||
```
|
||||
|
||||
## 框架启动参数
|
||||
|
||||
框架启动时可以根据实际情况指定启动参数。
|
||||
|
||||
- `--debug-mode`:启用调试模式,调试模式的作用是关闭一键协程化和终端交互,减少 Swoole 本身对代码逻辑的干扰(比如执行 `shell_exec()` 报错的话可以开启这个进行调试)。
|
||||
- `--log-{mode}`:设置 log 等级。支持 `--log-debug`,`--log-verbose`,`--log-info`,`--log-warning`,`--log-error`。
|
||||
- `--log-theme`:设置终端信息的主题。这个选项适用于多种终端信息显示的兼容,例如白色终端和不支持颜色的终端。详见 [Console - 主题设置](/component/console/#_2)。
|
||||
- `--disable-coroutine`:关闭一键协程化。
|
||||
- `--remote-terminal`:开启 nc 远程终端,配置文件使用全局中的 `remote_terminal` 项。也可以在全局配置中常开启(status 设置为 true)。
|
||||
- `--daemon`:以守护进程方式运行框架,此参数将直接在输出 motd 后将进程挂到 init 下运行,后台常驻。
|
||||
- `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展:inotify。使用 pecl 就可以安装:`pecl install inotify`。(注:不支持 WSL 和 macOS)
|
||||
- `--env`:设置运行环境,设置运行环境后将优先加载指定环境的配置文件,支持 `--env=production`,`--env=staging`,`--env=development`,见 [基本配置](/guide/basic-config/#_2)。
|
||||
- `--worker-num`:指定运行的工作进程数量(并不是越多越好,框架默认为 CPU 核心数),例如 `--worker-num=8`。
|
||||
- `--task-worker-num`:启用 TaskWorker 进程并指定数量。
|
||||
- `--show-php-ver`:在启动时显示 Swoole 和 PHP 的版本。
|
||||
|
||||
## 守护进程操作命令
|
||||
|
||||
守护进程在 2.2.0 版本开始,可以使用命令行快速操作,如重启、停止、查看状态等。
|
||||
|
||||
注意,这里的守护进程操作命令是指 **使用 `--daemon` 方式启动的框架**,如使用 Docker、screen、tmux、systemd 等方式挂后台跑则此命令不可用!
|
||||
|
||||
```bash
|
||||
vendor/bin/start daemon:status # 查看守护进程的状态
|
||||
vendor/bin/start daemon:reload # 重载框架
|
||||
vendor/bin/start daemon:stop # 停止运行守护进程的框架
|
||||
```
|
||||
|
||||
## 独立启动其他组件
|
||||
|
||||
框架默认不止启动框架的 `server` 命令,还有 `init` 命令和 `simple-http-server` 命令。`init` 命令在上方 Composer 依赖模式中提到过,就是初始化各个文件的。
|
||||
|
||||
### 独立 HTTP 文件服务器
|
||||
|
||||
如果你只需要一个静态文件服务器,类似 Nginx,那么框架也支持。
|
||||
|
||||
```bash
|
||||
vendor/bin/start simple-http-server your-web-dir/ --host=0.0.0.0 --port=8080
|
||||
```
|
||||
|
||||
- `your-web-dir` 是必填的参数。
|
||||
- `--host` 和 `--port` 是可选参数,如果不填,则默认使用 `global.php` 配置文件中的配置。
|
||||
|
||||
### 检查配置是否更新
|
||||
|
||||
默认情况下(非源码模式),你可以使用命令 `vendor/bin/start check:config` 来检查你的配置文件是否需要更新部分段落。
|
||||
|
||||
### systemd 配置文件生成器
|
||||
|
||||
框架支持生成 systemd 配置文件 `zhamao.service`,生成后将文件放入 `/etc/systemd/system` 后输入 `systemctl enable zhamao.service` 即可。
|
||||
|
||||
命令:`vendor/bin/start systemd:generate`
|
||||
|
||||
注意,systemd 启动的守护进程模式和使用参数 `--daemon` 不一样,请勿同时混用,直接使用上述命令生成的配置文件即可正常使用!
|
||||
@ -1,229 +0,0 @@
|
||||
# 编写管理员专属功能
|
||||
|
||||
众所周知,如果大家使用炸毛框架来开发聊天机器人的话,会比较方便。但是有些地方你一定会感觉还是欠缺了点,比如下面这样,你想编写一个只能由机器人管理员,也就是你自己,才能触发的功能:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand(match="禁言",message_type="group")
|
||||
*/
|
||||
public function banSomeone() {
|
||||
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
|
||||
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
|
||||
$cq = CQ::getCQ($r1);
|
||||
if ($cq !== null) {
|
||||
if ($cq["type"] != "at") return "请at或者输入正确的QQ号!";
|
||||
$r1 = $cq["params"]["qq"];
|
||||
}
|
||||
// 群内禁言用户
|
||||
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
|
||||
return "禁言成功!";
|
||||
}
|
||||
```
|
||||
|
||||
这时候,如果只是自己有绝对的权利,可以将自己的 QQ 号写死在注解 `@CQCommand` 中,并限定 `user_id`(假设我的 QQ 号码为 123456):
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand(match="禁言",message_type="group",user_id=123456)
|
||||
*/
|
||||
```
|
||||
|
||||
但是,随着时间的推移,你的机器人伙伴群可能越来越大,这个命令可能不止需要绝对的你来使用,你还要将机器人的部分权利下发给更多的伙伴,怎么办呢?注解里面只能写死的。
|
||||
|
||||
答案很简单,这时候我们就需要用到框架提供的中间件(Middleware)。中间件说白了就是在事件执行前、后、过程中抛出的异常对其进行阻断和插入代码,比如我们上方在触发禁言这个注解事件前首先要判断执行这个命令的是不是钦定的管理员。
|
||||
|
||||
## 第一步:定义中间件
|
||||
|
||||
首先,我们需要定义一个中间件。在框架默认提供的脚手架中,包含了一个叫 `TimerMiddleware.php` 的示例中间件,这个示例中间件的目的是非常简单的,就是判断这个注解事件运行了多长时间。假设你有一个机器人功能,这个功能下的代码需要执行很长时间,可以使用这一注解轻松将事件执行的时间打印到终端上。
|
||||
|
||||
关于中间件的有关说明,见 [中间件](/event/middleware)。
|
||||
|
||||
下面我们假设你已经阅读过中间件注解的文档了,我们着手编写一个判断指令执行者是否是指定的管理员 QQ 的中间件。为了省事和让大家方便地复现,我先在脚手架下的目录 `src/Module/Middleware/` 下新建 PHP 类文件 `AdminMiddleware.php`(和 `TimerMiddleware.php` 在同一个目录)。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Middleware;
|
||||
|
||||
use ZM\Annotation\Http\HandleBefore;
|
||||
use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
use ZM\Store\LightCache;
|
||||
|
||||
/**
|
||||
* Class AdminMiddleware
|
||||
* 示例中间件:用于动态管理一些管理员指令的中间件
|
||||
* @package Module\Middleware
|
||||
* @MiddlewareClass("admin")
|
||||
*/
|
||||
class AdminMiddleware implements MiddlewareInterface
|
||||
{
|
||||
/**
|
||||
* @HandleBefore()
|
||||
* @return bool
|
||||
* @throws ZMException
|
||||
*/
|
||||
public function onBefore(): bool {
|
||||
$r = ctx()->getUserId(); // 从上下文获取发消息的用户 QQ
|
||||
$admin_list = LightCache::get("admin_list") ?? []; // 从轻量缓存获取管理员列表
|
||||
return in_array($r, $admin_list); // 返回这个 QQ 是否在管理员列表中
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其中,`@MiddlewareClass("admin")` 的意思是,定义这个类为名字叫 `admin` 的中间件,同时,所有中间件的类**必须**带上 `implements MiddlewareInterface`,统一接口形式。
|
||||
|
||||
`@HandleBefore()` 代表的是,这个类下的这个函数(onBefore)被标注为这个中间件的 `onBefore` 事件,也就是说,如果有别的注解事件插入了这个 `admin` 中间件,那么执行对应注解事件前都要执行一下 `@HandleBefore` 所绑定的这个函数。而这个绑定的函数只能返回 `bool` 类型的值哦!
|
||||
|
||||
## 第二步:使用中间件
|
||||
|
||||
使用中间件很简单,在需要阻断的注解事件绑定的函数上再加一个注解就好了!我们以上方的禁言例子说明:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @Middleware("admin")
|
||||
* @CQCommand(match="禁言",message_type="group")
|
||||
*/
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'禁言 1234567 600'},
|
||||
{type:1,content:'禁言成功!'},
|
||||
{type:2,content:'假设我不在管理员名单里'},
|
||||
{type:0,content:'禁言 1234567 900'},
|
||||
{type:2,content:'机器人没有回复,因为中间件返回了 false,不继续执行'},
|
||||
]"></chat-box>
|
||||
|
||||
而这时候有朋友又要问了,如果我有一系列管理员命令,假设都在一个叫 `AdminFunc.php` 的模块类里,我是不是还得一个一个地给注解事件写 `@Middleware("admin")` 呢?当然不需要!如果你这个类所有的注解事件都是机器人的聊天事件(`@CQCommand`,`@CQMessage`)的话,可以直接给类注解这个中间件,效果等同于给每一个函数写一次中间件注解。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\Http\Middleware;
|
||||
|
||||
/**
|
||||
* Class AdminFunc
|
||||
* @package Module\Example
|
||||
* @Middleware("admin")
|
||||
*/
|
||||
class AdminFunc
|
||||
{
|
||||
// ...这里是你的一堆注解事件的函数
|
||||
}
|
||||
```
|
||||
|
||||
## 第三步:补全代码
|
||||
|
||||
上面我们讲到了,中间件里面使用了 `LightCache` 轻量缓存来储存临时的管理员列表,那么我们将这部分的代码完善吧!
|
||||
|
||||
**src/Module/Example/AdminFunc.php**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\Http\Middleware;
|
||||
use ZM\API\CQ;
|
||||
|
||||
/**
|
||||
* Class AdminFunc
|
||||
* @package Module\Example
|
||||
* @Middleware("admin")
|
||||
*/
|
||||
class AdminFunc
|
||||
{
|
||||
/**
|
||||
* @CQCommand(match="禁言",message_type="group")
|
||||
*/
|
||||
public function banSomeone() {
|
||||
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
|
||||
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
|
||||
$cq = CQ::getCQ($r1);
|
||||
if ($cq !== null) {
|
||||
if ($cq["type"] != "at") return "请at或者输入正确的QQ号!";
|
||||
$r1 = $cq["params"]["qq"];
|
||||
}
|
||||
// 群内禁言用户
|
||||
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
|
||||
return "禁言成功!";
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand(match="解除禁言",message_type="group")
|
||||
*/
|
||||
public function unbanSomeone() {
|
||||
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
|
||||
$cq = CQ::getCQ($r1);
|
||||
if ($cq !== null) {
|
||||
if ($cq["type"] != "at") return "请at或者输入正确的QQ号!";
|
||||
$r1 = $cq["params"]["qq"];
|
||||
}
|
||||
// 群内禁言用户
|
||||
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, 0);
|
||||
return "解除禁言成功!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**src/Module/Example/AdminManager.php**
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\Http\Middleware;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
|
||||
class AdminManager
|
||||
{
|
||||
/**
|
||||
* @OnStart()
|
||||
*/
|
||||
public function onStart() {
|
||||
if (!LightCache::isset("admin_list")) { //一次性代码,首次执行才会执行if
|
||||
LightCache::set("admin_list", [ // 框架启动时初始化管理员列表
|
||||
"123456",
|
||||
"234567"
|
||||
], -2); // 这里用 -2 的原因是将这一列表持久化保存,避免关闭框架后丢失
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand(match="添加管理员")
|
||||
* @Middleware("admin")
|
||||
*/
|
||||
public function addAdmin() { //只有管理员才能添加管理员
|
||||
$qq = ctx()->getNextArg("请输入要添加管理员的QQ(qq号码,不可at)");
|
||||
SpinLock::lock("admin_list"); //如果是多进程模式的话需要加锁
|
||||
$ls = LightCache::get("admin_list");
|
||||
if (!in_array($qq, $ls)) $ls[] = $qq;
|
||||
LightCache::set("admin_list", $ls, -2);
|
||||
SpinLock::unlock("admin_list"); //如果是多进程模式的话需要加锁
|
||||
return "成功添加 $qq 到管理员列表!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:2,content:'现在我是 123456'},
|
||||
{type:0,content:'禁言 13579 60'},
|
||||
{type:1,content:'禁言成功!'},
|
||||
{type:0,content:'解除禁言 13579'},
|
||||
{type:1,content:'解除禁言成功!'},
|
||||
{type:0,content:'添加管理员 98765'},
|
||||
{type:1,content:'成功添加 98765 到管理员列表!'},
|
||||
{type:2,content:'现在我是98765'},
|
||||
{type:0,content:'禁言 13579'},
|
||||
{type:1,content:'请输入禁言的时间(秒)'},
|
||||
{type:0,content:'120'},
|
||||
{type:1,content:'禁言成功!'},
|
||||
]"></chat-box>
|
||||
@ -1,85 +0,0 @@
|
||||
# 接入青云客智能聊天机器人API
|
||||
|
||||
作为一个群聊机器人,懂得聊天会让机器人增色不少,在大数据和 AI 热潮下,不少厂商都研发了自己的智能聊天 API,例如图灵机器人、腾讯智能闲聊等,大厂开发的 API 自然有着他人无可比拟的健壮性和可靠性,但是随之而来不菲的价格显然并不适合大众开发者。这时候一个免费、可用的智能聊天 API 便非常重要了,其中,青云客是少有的完全免费、无需注册的智能聊天 API,提供了包括智能聊天、歌词、天气查询、笑话等多种有用功能,且接入简单,非常适合新手开发者尝试。
|
||||
|
||||
## 结果演示
|
||||
|
||||

|
||||
|
||||
## 阅读接入指南
|
||||
|
||||
不管接入何种服务,阅读接入指南永远都是最优先、最重要的一步,所幸青云客的接入指南十分简单,简单来说归纳为以下:
|
||||
* 请求:GET https://api.qingyunke.com/api.php
|
||||
* 参数:
|
||||
* * `key` 目前固定为 `free`
|
||||
* * `appid` 目前固定为 `0`
|
||||
* * `msg` 关键词,需要经过 `urlencode`
|
||||
* 注意:返回结果中 `{br}` 代表换行
|
||||
|
||||
## 逻辑编写
|
||||
|
||||
阅读过后,我们便可以进行主要的编写工作了。
|
||||
|
||||
首先,为了机器人的性能考虑,也为了避免过分打扰群聊的聊天,我们希望机器人只有在主动触发(@AT 或者 关键词等)时才会进行智能聊天。
|
||||
|
||||
对于关键词匹配,我们可以使用 `@CQCommand`:
|
||||
|
||||
```php
|
||||
/**
|
||||
* 智能聊天
|
||||
*
|
||||
* @CQCommand(start_with="机器人")
|
||||
*/
|
||||
public function chat()
|
||||
{
|
||||
// 替换掉机器人前缀,并获取消息内容
|
||||
$msg = ctx()->getMessage();
|
||||
$msg = str_replace('机器人', '', $msg);
|
||||
if (empty(trim($msg))) {
|
||||
$msg = ctx()->getFullArg('怎么了?');
|
||||
}
|
||||
|
||||
Console::info('正在获取智能聊天回复:' . $msg);
|
||||
// 请求 API 获取回复
|
||||
$raw_data = file_get_contents('https://api.qingyunke.com/api.php?key=free&appid=0&msg=' . urlencode($msg));
|
||||
try {
|
||||
$data = json_decode($raw_data, true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\Exception $e) {
|
||||
$data = ['content' => '机器人解析异常,请稍后再试'];
|
||||
Console::warning('无法获取智能聊天回复:' . $e->getMessage());
|
||||
}
|
||||
if ($data['result'] !== 0) {
|
||||
$data = ['content' => '机器人服务异常,请稍后再试'];
|
||||
Console::warning('无法获取智能聊天回复:' . $raw_data);
|
||||
}
|
||||
Console::info('获取智能聊天回复完成:' . $data['content']);
|
||||
// 将 {br} 替换为换行
|
||||
$data['content'] = strtr($data['content'], ['{br}' => "\n"]);
|
||||
return $data['content'];
|
||||
}
|
||||
```
|
||||
|
||||
这样我们的命令便只会在用户发送以`机器人`开头的消息时才会触发。
|
||||
|
||||
同时,我们也希望在 @AT 机器人时也进行回复,此时可以使用 `@CQBefore` 方法进行折中:
|
||||
|
||||
```php
|
||||
/**
|
||||
* 将 AT 机器人的消息交由智能聊天处理
|
||||
*
|
||||
* @CQBefore("message")
|
||||
*/
|
||||
public function changeAt(): bool
|
||||
{
|
||||
// 判断此条消息是否 AT 了机器人
|
||||
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
|
||||
// 将 AT 本身从消息中去掉
|
||||
$msg = str_replace(CQ::at(ctx()->getRobotId()), '', ctx()->getMessage());
|
||||
ctx()->setMessage('机器人' . trim($msg));
|
||||
// 调用智能聊天
|
||||
ctx()->reply($this->chat());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
@ -1,113 +0,0 @@
|
||||
# 基于词性分析和魅族天气的天气查询机器人
|
||||
|
||||
本文将基于 [`jieba-php`](https://github.com/fukuball/jieba-php) 中文分词库以及 [魅族天气 API](https://github.com/shichunlei/-Api/blob/master/MeizuWeather.md) 开发一个天气查询机器人。
|
||||
|
||||
## 结果演示
|
||||
|
||||

|
||||
尾部的随机表情并非本教程的一部分。
|
||||
|
||||
## 逻辑编写
|
||||
|
||||
[jieba-php](https://github.com/fukuball/jieba-php) 是目前比较好用的中文分词库,虽然最近的维护并不活跃,但已足够我们的需求:
|
||||
|
||||
```shell
|
||||
composer require fukuball/jieba-php:dev-master
|
||||
```
|
||||
|
||||
以下代码使用了本文作者自行编写的天气查询库,需要进行引入:
|
||||
|
||||
```shell
|
||||
composer require sunxyw/weather
|
||||
```
|
||||
|
||||
您也可以将以下代码自行改写为直接调用魅族天气 API,详情请参阅[魅族天气 API 文档](https://github.com/shichunlei/-Api/blob/master/MeizuWeather.md)。
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Bot\Module\SmartChat;
|
||||
|
||||
use Fukuball\Jieba\Jieba;
|
||||
use Fukuball\Jieba\Posseg;
|
||||
use Sunxyw\Weather\Weather;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class WeatherReport
|
||||
{
|
||||
/**
|
||||
* 加载字典
|
||||
*
|
||||
* @OnStart(worker_id=-1)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initDictionary(): void
|
||||
{
|
||||
// 分词以及词性分析需要载入字典到内存
|
||||
ini_set('memory_limit', '600M');
|
||||
Jieba::init(['dict' => 'small']);
|
||||
Posseg::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询天气
|
||||
*
|
||||
* @CQCommand(keyword="天气")
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cmdQueryWeather(): string
|
||||
{
|
||||
// 分词并进行词性分析
|
||||
$seg_list = Posseg::cut(ctx()->getMessage());
|
||||
$tags = array_column($seg_list, 'tag');
|
||||
// 找出词性为 ns(地名)的单词
|
||||
$location_index = array_search('ns', $tags, true);
|
||||
$location = $seg_list[$location_index]['word'];
|
||||
|
||||
// 此处引入了本文作者自己写的天气库
|
||||
$w = new Weather();
|
||||
try {
|
||||
$report = $w->getWeather($location);
|
||||
} catch (\InvalidArgumentException) {
|
||||
return '城市输入错误';
|
||||
} catch (\JsonException $e) {
|
||||
Console::warning("天气查询失败:{$e->getMessage()}");
|
||||
return '天气查询失败';
|
||||
}
|
||||
|
||||
$template = <<<EOF
|
||||
%s天气:%s
|
||||
温度:%s℃
|
||||
湿度:%s%%
|
||||
风向:%s %s
|
||||
空气质量:%s
|
||||
------------------------------
|
||||
未来三天天气:
|
||||
%s:%s,日间%s℃,夜间%s℃,吹%s %s
|
||||
%s:%s,日间%s℃,夜间%s℃,吹%s %s
|
||||
%s:%s,日间%s℃,夜间%s℃,吹%s %s
|
||||
EOF;
|
||||
$args = [
|
||||
$report->getCity(),
|
||||
$report->getRealtime()['weather'],
|
||||
$report->getRealtime()['temperature'],
|
||||
$report->getRealtime()['humidity'],
|
||||
$report->getRealtime()['wind_direction'],
|
||||
$report->getRealtime()['wind_speed'],
|
||||
$report->getRealtime()['air_quality'],
|
||||
];
|
||||
foreach (array_slice($report->getForecastDaily(), 0, 3) as $forecast) {
|
||||
$args[] = $forecast['date'];
|
||||
$args[] = $forecast['weather'];
|
||||
$args[] = $forecast['temperature']['day'];
|
||||
$args[] = $forecast['temperature']['night'];
|
||||
$args[] = $forecast['wind_direction'];
|
||||
$args[] = $forecast['wind_speed'];
|
||||
}
|
||||
return vsprintf($template, ...$args);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -1,5 +0,0 @@
|
||||
# 框架剖析
|
||||
|
||||
## 框架运行总结构图
|
||||
|
||||

|
||||
@ -1,67 +0,0 @@
|
||||
# 内部类文件手册
|
||||
|
||||
这个章节写明了在框架使用过程中可能涉及到的框架内部或 Swoole、其他 composer 依赖组件的内部类,这里会根据类的命名空间一一说明。
|
||||
|
||||
## Swoole\Http\Request
|
||||
|
||||
此类是 Swoole 内部的一个类,一般在收到 HTTP 请求时,在 `@RequestMapping` 或 `@OnRequestEvent()` 两个注解下可用,用作获取 GET、POST参数,上传到后端的文件、Cookies 等。详见 [Swoole 文档 - Request](http://wiki.swoole.com/#/http_server?id=httprequest) 。
|
||||
|
||||
### 属性
|
||||
|
||||
- `$fd`:获取当前连接的文件描述符 ID。
|
||||
- `$header`:`HTTP` 请求的头部信息。类型为数组,所有 `key` 均为小写。
|
||||
- `$server`:`HTTP` 请求相关的服务器信息。
|
||||
- `$cookie`:获取 Cookies。
|
||||
- `$get`:获取 GET 参数。
|
||||
- `$post`:获取 POST 参数。
|
||||
- `$files`:获取上传的文件信息
|
||||
|
||||
### 方法
|
||||
|
||||
- `rawContent()`:获取 POST 包原始二进制内容,相当于原生 PHP 的 ` file_get_contents("php://input");` 。
|
||||
- `getData()`:获取完整的原始 `Http` 请求报文。包括 `Http Header` 和 `Http Body`
|
||||
|
||||
### 示例
|
||||
|
||||
```php
|
||||
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 ==> [
|
||||
"我叫顺溜",
|
||||
"我今年二十八"
|
||||
]
|
||||
*/
|
||||
```
|
||||
@ -1,3 +0,0 @@
|
||||
# 手动部署环境教程
|
||||
|
||||
TODO: 还没写
|
||||
@ -1,72 +0,0 @@
|
||||
# 框架多进程
|
||||
|
||||
首先对于多进程概念,对于传统 PHP 程序员可能比较陌生,唯一接触到的地方可能就是 php-fpm 等一些方式处理时间长的请求时开进程去执行。关于多进程,我觉得廖雪峰的 Python 多进程这段讲的不错:
|
||||
|
||||
> Unix/Linux 操作系统提供了一个`fork()`系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是`fork()`调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
|
||||
|
||||
这里面的重点在于,多进程的创建,是父进程的复制,然后两个进程接下来运行的代码和存的内容就分道扬镳了。
|
||||
|
||||
PHP 也是如此,框架的多进程又是怎么一回事呢?为什么要采用多进程呢?
|
||||
|
||||
## 作用
|
||||
|
||||
使用过框架的你一定知道,框架是以命令行方式运行 PHP 的,而命令行方式运行 PHP,就代表要常驻内存,就像 Python、Node.js 一样。而默认情况下,比如 Python 的 Flask 为单线程单进程模式,也就是说同时只能处理一个 Web 请求。但大部分情况下,比如 Node.js,提供的都是异步 I/O,这也就是说明它在 Web 处理请求上,可同时承接的 I/O 密集型请求会更多一些,这样在对一般的 Web 应用中 I/O 密集型场景非常有用,而且往往只需要单进程也可以承载上万的并发请求。
|
||||
|
||||
在炸毛框架中,因为框架基于 Swoole 构建,所以天然支持协程,而协程就是针对 I/O 操作进行一个调度,类似异步的 Node.js,所以针对项目中存在太多的 SQL 语句执行、文件读写的话,炸毛框架直接上手,无需做任何修改,也可以达到很好的性能。
|
||||
|
||||
**但是**,CPU 密集型的应用怎么办呢?假设我的 Web 应用有大量的排序、md5 运算怎么办呢?这样的阻塞,假设是一个超级大的 for 循环或者是要执行很长时间的 while 循环,CPU 一直在被占用。多进程就是针对 CPU 密集型的应用说 yes 的一个方案。
|
||||
|
||||

|
||||
|
||||
我们假设现在有 3 个请求同时访问,也就是说上面的流程需要执行 3 遍。而如果我们只有一个进程的话,最后一个请求需要等待的时间为 `2*3+5*3=21` 秒,非常耗时。
|
||||
|
||||
而如果有两个进程处理 3 个请求,则最后一个完成的请求就缩短了,`2+5+2+5=14` 秒。
|
||||
|
||||

|
||||
|
||||
所以如果要充分利用你的服务器或者个人电脑的多核 CPU 资源,就要设置多个进程来处理。一个进程只能在一个 CPU 上运行,而设置了多进程后,就可以让多核 CPU 充分运行多个进程,所以我们给框架设置多进程的推荐数值为等同于 CPU 的核心数。
|
||||
|
||||
## 为什么不是多线程
|
||||
|
||||
因为众所周知,PHP 对线程的支持比较不好,而 ZTS 版本的 PHP 又会影响传统的 Web 端 PHP 的性能,再加上 Linux 对线程的切换效率和多进程切换的效率差不多,多线程容易造成数据读写不安全等问题,故 Swoole 使用的是多进程模型。
|
||||
|
||||
## 框架进程模型
|
||||
|
||||

|
||||
|
||||
上图中,横向的时间片可以理解为并行执行,这些操作在多个 CPU 内可能同时在执行。
|
||||
|
||||
## 进程间隔离
|
||||
|
||||
众所周知,进程是程序在操作系统中的一个边界,和自己有关的一切变量、内容和代码都在自己的进程内,不同进程之间如果不使用管道等方式,是不可以互相访问的。而加上开始描述的,创建子进程是一个复制自身的过程,所以也就会有如下图的情况:
|
||||
|
||||

|
||||
|
||||
我们以静态类为例,设置一个进程中的全局变量。这里就会出现,同一个静态变量在多个进程中完全不同的值的结果。此后,我们将会在 Worker 进程中执行用户的代码,如果设置 Worker 数量仅为 1 的话,那么就简单许多了,你还是可以使用全局变量或静态类来存储你想要的内容而不用担心这种多个进程变量隔离的情况(因为用户的 Web 请求处理的代码只会在一个 Worker 进程中执行)。如果像上图一样设置了多个 Worker,则用户过来的比如 HTTP 请求就有可能出现在不同的 Worker 进程中,给全局变量设值就一定会造成不同步的问题。这时我们就不可以使用全局变量做数据同步(注意,我说的是数据同步)。
|
||||
|
||||
## 跨进程同步
|
||||
|
||||
跨进程同步方案中,框架给出了很多种解决方案。
|
||||
|
||||
- MySQL 数据库
|
||||
- Redis
|
||||
- LightCache 轻量缓存(共享内存)
|
||||
- WorkerCache 大缓存
|
||||
- ZMAtomic 跨进程原子计数器
|
||||
|
||||
下面的表格我将列出下方的特点和各自的优缺点:
|
||||
|
||||
| 类型 | 用途 | 优点 | 缺点 |
|
||||
| ----------- | --------------------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| MySQL | 大型的传统的关系式数据都可以用数据库,你懂的 | 就是数据库的优点 | 和数据库不在同一台服务器的话网络延迟会较大,数据获取效率不高 |
|
||||
| Redis | 传统的 key-value 数据库 | 数据无同步等问题,性能高 | 有网络通信延迟 |
|
||||
| LightCache | 框架封装的跨进程的 key-value 存储模型 | 性能强悍,无 I/O 和网络通信 | 需要提前分配最大内存大小,最大单个值长度大小,不灵活 |
|
||||
| WorkerCache | 框架封装的基于进程的 key-value 存储模型,类似 Redis | 无需提前分配最大内存大小,受限于 PHP memory_limit | 见 WorkerCache 的说明 |
|
||||
|
||||
::: tip WorkerCache 的说明
|
||||
|
||||
对于 WorkerCache 来说,其实是比较特殊的进程间通信。具体来说就是,WorkerCache 的原理就是将变量指定的存到一个进程中,如果是本进程读写的话直接相当于改一下全局变量,如果是其他进程读写的话,则依靠进程间通信。
|
||||
|
||||
所以缺点也显而易见,如果使用过程中不是命中了 WorkerCache 存储所在的进程的话,则一直会使用进程间通信,影响一定的效率。
|
||||
|
||||
:::
|
||||
@ -1,3 +0,0 @@
|
||||
# PHP 环境高级部署方式
|
||||
|
||||
TODO: 留个坑。
|
||||
@ -1,3 +0,0 @@
|
||||
# 使用 TaskWorker 进程处理密集运算
|
||||
|
||||
> TODO: 新开个坑,有时间补上。(__填坑标记__)
|
||||
@ -1,3 +0,0 @@
|
||||
# 框架组件
|
||||
|
||||
这里列举了框架内的你可能会用到的常用组件。
|
||||
@ -1,59 +0,0 @@
|
||||
# 接入安全验证 - Token
|
||||
|
||||
为了保障安全,框架支持给接入的 WebSocket 连接验证 Token,如果不设置 Token 同时又将框架的端口暴露在公网将会非常危险。
|
||||
|
||||
炸毛框架兼容 OneBot 标准的机器人客户端,所以自带一个 Token 验证器。
|
||||
|
||||
关于 Access Token 方面的标准规范,请参考下面内容:
|
||||
|
||||
- [OneBot - 鉴权](https://github.com/howmanybots/onebot/blob/master/v11/specs/communication/authorization.md)
|
||||
- [go-cqhttp - 配置](https://github.com/Mrs4s/go-cqhttp/blob/master/docs/config.md)
|
||||
|
||||
> 以 go-cqhttp 举例,如果要设置验证,则将 go-cqhttp 配置文件中的 `access_token` 项填入内容即可。
|
||||
|
||||
## 验证位置
|
||||
|
||||
框架对 Token 的验证是内置的,在事件 `open`(WebSocket 连接接入时)触发。
|
||||
|
||||
如果是兼容 OneBot 标准的客户端接入,则一切都是兼容的。
|
||||
|
||||
如果是自定义的其他 WebSocket 客户端也想接入框架,那么其他 WebSocket 客户端也需要进行相应的设置才能利用此 Token 验证。
|
||||
|
||||
如果验证成功(Token 符合要求)则分发事件 `@OnOpenEvent`,否则此事件不触发,同时断开 WebSocket 连接。
|
||||
|
||||
## 标准验证(字符串形式)
|
||||
|
||||
默认的情况下,在框架的全局配置文件 `global.php` 中,对配置项 `access_token` 填入与 OneBot 客户端相同的 `access_token` 即可实现鉴权。下面是一个最基本的和 go-cqhttp 设置鉴权配置:
|
||||
|
||||
go-cqhttp 的配置段:
|
||||
|
||||
```
|
||||
// 访问密钥, 强烈推荐在公网的服务器设置
|
||||
access_token: "emhhbWFvLXJvYm90"
|
||||
```
|
||||
|
||||
框架的配置文件配置段:
|
||||
|
||||
```php
|
||||
/** onebot连接约定的token */
|
||||
$config["access_token"] = 'emhhbWFvLXJvYm90';
|
||||
```
|
||||
|
||||
然后重启框架和 go-cqhttp 即可。(其他 OneBot 客户端同理)
|
||||
|
||||
## 自定义验证(Token 验证)
|
||||
|
||||
有些情况下,使用一个单一的字符串可能无法满足你对 Token 验证的安全需求,需要自定义一些判断模式才能满足,所以框架的 `access_token` 配置项支持动态的闭包函数自行编写判断逻辑,例如下面的一个例子,我可以让框架同时允许接入多个不同 token 的 WebSocket 连接:
|
||||
|
||||
```php
|
||||
/** onebot连接约定的token */
|
||||
$config["access_token"] = function($token){
|
||||
$allow = ['emhhbWFvLXJvYm90','aXMtdmVyeS1nb29k'];
|
||||
if (in_array($token, $allow)) return true;
|
||||
else return false;
|
||||
};
|
||||
```
|
||||
|
||||
## 自定义验证(open 事件)
|
||||
|
||||
当然,这里设置了自定义方式,其实你也可以在下一层的 `@OnOpenEvent` 注解事件中进行自定义内容和判断,具体见 `@OnOpenEvent` 的相关章节。
|
||||
@ -1,550 +0,0 @@
|
||||
# 多媒体消息 - CQ码
|
||||
|
||||
消息中的多媒体内容使用 CQ 码来表示,形如 `[CQ:face,id=178]`。其中,`[CQ:]` 是固定格式;`face` 是「功能名」,除了 `face` 还有许多不同的功能名;`id=178` 是「参数」,某些功能不需要参数,而另一些需要多个参数,当有多个参数时,参数间使用逗号分隔。
|
||||
|
||||
## 格式
|
||||
|
||||
一些 CQ 码的例子如下:
|
||||
|
||||
```
|
||||
[CQ:shake]
|
||||
[CQ:face,id=178]
|
||||
[CQ:share,title=标题,url=http://baidu.com]
|
||||
```
|
||||
|
||||
更多 CQ 码功能请参考 [消息段类型](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md)。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
CQ 码中不应有多余的空格,例如不应该使用 `[CQ:face, id=178]`。
|
||||
|
||||
CQ 码的参数值可以包含空格、换行、除 `[],&` 之外的特殊符号等。在解析时,应直接取 `[CQ:` 后、第一个 `,` 或 `]` 前的部分为功能名,第一个 `,` 之后到 `]` 之间的部分为参数,按 `,` 分割后,每个部分第一个 `=` 前的内容为参数名,之后的部分为参数值。例如 `[CQ:share,title=标题中有=等号,url=http://baidu.com]` 中,功能名为 `share`,`title` 参数值为 `标题中有=等号`,`url` 参数值为 `http://baidu.com`。
|
||||
|
||||
:::
|
||||
|
||||
## 转义
|
||||
|
||||
CQ 码中包含一些特殊字符:`[`、`]`、`,` 等,而 CQ 码又是可能混杂在纯文本内容之中的,因此消息中的纯文本内容需要对特殊字符进行转义,以避免歧义。具体的转义规则如下:
|
||||
|
||||
| 转义前 | 转义后 |
|
||||
| ------ | ------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
|
||||
另一方面,CQ 码内部的参数值也可能出现特殊字符,也是需要转义的。由于 `,`(半角逗号)在 CQ 码中用于分隔参数,因此除了上面的转义规则,还需要对 `,` 进行转义,如下:
|
||||
|
||||
| 转义前 | 转义后 |
|
||||
| ------ | ------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
| `,` | `,` |
|
||||
|
||||
例如,一个链接分享消息的 CQ 码可能如下:
|
||||
|
||||
```
|
||||
[CQ:share,title=震惊,小伙睡觉前居然...,url=http://baidu.com/?a=1&b=2]
|
||||
```
|
||||
|
||||
## 封装调用
|
||||
|
||||
框架提供了 CQ 码的封装,你可以在任何位置使用封装好的 CQ 码生成器。
|
||||
|
||||
生成器是一个静态类,里面的方法全部是静态调用,命名空间是:`ZM\API\CQ`。
|
||||
|
||||
例如,给用户发送图片这样写就好啦!只需要将添加图片的地方拼到回复用户的字符串里。如果只发图片,整个字符串里只能有 CQ 码。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand("发送图片")
|
||||
*/
|
||||
public function msgRecv() {
|
||||
return CQ::image("https://zhamao.xin/file/hello.jpg");
|
||||
// 相当于返回:"[CQ:image,file=https://zhamao.xin/file/hello.jpg]"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
效果:
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'发送图片'},
|
||||
{type:3,content:'https://zhamao.xin/file/hello.jpg'}
|
||||
]"></chat-box>
|
||||
|
||||
## CQ 码操作
|
||||
|
||||
### CQ::decode()
|
||||
|
||||
CQ 码字符反转义。
|
||||
|
||||
定义:`CQ::encode($msg, $is_content = false)`
|
||||
|
||||
当 `$is_content` 为 true 时,会将 `,` 转义为 `,`。
|
||||
|
||||
| 反转义前 | 反转义后 |
|
||||
| -------- | -------- |
|
||||
| `&` | `&` |
|
||||
| `[` | `[` |
|
||||
| `]` | `]` |
|
||||
| `,` | `,` |
|
||||
|
||||
```php
|
||||
$str = CQ::decode("[CQ:at,qq=我只是一条普通的文本]");
|
||||
// 转换为 "[CQ:at,qq=我只是一条普通的文本]"
|
||||
```
|
||||
|
||||
### CQ::encode()
|
||||
|
||||
转义 CQ 码的敏感符号,防止 酷Q 把不该解析为 CQ 码的消息内容当作 CQ 码处理。
|
||||
|
||||
```php
|
||||
$str = CQ::encode("[CQ:我只是一条普通的文本]");
|
||||
// $str: "[CQ:我只是一条普通的文本]"
|
||||
```
|
||||
|
||||
定义:`CQ::encode($msg, $is_content = false)`
|
||||
|
||||
当 `$is_content` 为 true 时,会将 `,` 转义为 `,`。
|
||||
|
||||
### CQ::escape()
|
||||
|
||||
同 `CQ::encode()`。
|
||||
|
||||
### CQ::removeCQ()
|
||||
|
||||
去除字符串中所有的 CQ 码。
|
||||
|
||||
```php
|
||||
$str = CQ::removeCQ("[CQ:at,qq=all]这是带表情的全体消息[CQ:face,id=8]");
|
||||
// $str: "这是带表情的全体消息"
|
||||
```
|
||||
|
||||
### CQ::getCQ()
|
||||
|
||||
解析 CQ 码。
|
||||
|
||||
- 定义:`getCQ($msg, $is_object = false)`
|
||||
- 参数 `$is_object` 为 true 时,返回一个 `\ZM\Entity\CQObject` 对象,此对象的属性和下表相同。(2.3.0+ 版本可用)
|
||||
- 返回:`数组 | CQObject | null`,见下表。
|
||||
|
||||
| 键名 | 说明 |
|
||||
| ------ | ------------------------------------------------------------ |
|
||||
| type | CQ码类型,比如 `[CQ:at]` 中的 `at` |
|
||||
| params | 参数列表,比如 `[CQ:image,file=123.jpg,url=http://a.com/a.jpg]`,params 为 `["file" => "123","url" => "http://a.com/a.jpg"]` |
|
||||
| start | 此 CQ 码在字符串中的起始位置 |
|
||||
| end | 此 CQ 码在字符串中的结束位置 |
|
||||
|
||||
### CQ::getAllCQ()
|
||||
|
||||
定义:`CQ::getAllCQ($msg, $is_object = false)`
|
||||
|
||||
参数 `$is_object` 为 true 时,返回一个 `\ZM\Entity\CQObject[]` 对象数组,此对象的属性和上面的表格内相同。(2.3.0+ 版本可用)
|
||||
|
||||
解析 CQ 码,和 `getCQ()` 的区别是,这个会将字符串中的所有 CQ 码都解析出来,并以同样上方解析出来的数组格式返回。
|
||||
|
||||
```php
|
||||
CQ::getAllCQ("[CQ:at,qq=123]你好啊[CQ:at,qq=456]");
|
||||
/*
|
||||
[
|
||||
[
|
||||
"type" => "at",
|
||||
"params" => [
|
||||
"qq" => "123",
|
||||
],
|
||||
"start" => 0,
|
||||
"end" => 13,
|
||||
],
|
||||
[
|
||||
"type" => "at",
|
||||
"params" => [
|
||||
"qq" => "456",
|
||||
],
|
||||
"start" => 17,
|
||||
"end" => 30,
|
||||
],
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
## CQ 码列表
|
||||
|
||||
### CQ::face() - 发送 QQ 表情
|
||||
|
||||
发送 QQ 原生表情。
|
||||
|
||||
定义:`CQ::face($id)`
|
||||
|
||||
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [QQ 对应表情ID表](https://static.zhamao.me/face_id.html)。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("打盹")
|
||||
*/
|
||||
public function faceTest() {
|
||||
ctx()->reply("正在打盹...");
|
||||
ctx()->reply(CQ::face(8));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'打盹'},
|
||||
{type:1,content:'正在打盹...'},
|
||||
{type:3,content:'https://docs-v1.zhamao.xin/face/8.gif'},
|
||||
]"></chat-box>
|
||||
|
||||
::: tip 提示
|
||||
|
||||
对于不断更新的 QQ 版本下,可能会持续扩充新的 QQ 表情,如果上表没有新的表情的话,也可以使用消息接收的方式,让机器人收到表情后解析出来对应的 id 然后再发送。
|
||||
|
||||
:::
|
||||
|
||||
### CQ::image() - 发送图片
|
||||
|
||||
发送图片。
|
||||
|
||||
定义:`image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 图片文件名 |
|
||||
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的图片文件名直接发送外,还支持:
|
||||
|
||||
- 绝对路径,例如 `file:///root/imagetest/1.png`,格式使用 [`file` URI](https://tools.ietf.org/html/rfc8089)
|
||||
- 网络 URL,例如 `http://i1.piimg.com/567571/fdd6e7b6d93f1ef0.jpg`
|
||||
- Base64 编码,例如
|
||||
|
||||
```
|
||||
base64://iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAIAAADJt1n/AAAAKElEQVQ4EWPk5+RmIBcwkasRpG9UM4mhNxpgowFGMARGEwnBIEJVAAAdBgBNAZf+QAAAAABJRU5ErkJggg==
|
||||
```
|
||||
|
||||
### CQ::record() - 发送语音
|
||||
|
||||
发送语音消息。
|
||||
|
||||
定义:`CQ::record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------- | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 音频文件名 |
|
||||
| `flash` | ✓ | ✓ | `false` | 图片类型,当参数为 true 时代表发送闪照 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的语音文件名直接发送外,还支持其它形式,参考上方发送图片。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("说你好")
|
||||
*/
|
||||
public function say() {
|
||||
ctx()->reply(CQ::record("https://zhamao.xin/file/hello.mp3"));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'说你好'},
|
||||
{type:1,content:'[语音消息,点击收听] 2\'\' )))'},
|
||||
]"></chat-box>
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::at() - 群里@某人或全体
|
||||
|
||||
在群里 at 某个人或全体成员(全体成员需要有管理员权限)。
|
||||
|
||||
定义:`CQ::at($qq)`
|
||||
|
||||
参数:`$qq` 参数必填,如果填的是 QQ 号,则是单独 at 某人,如果是 `all`,则是 at 全体成员。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("at测试")
|
||||
*/
|
||||
public function atTest() {
|
||||
ctx()->reply(CQ::at(627577391)." 你好啊!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'at测试'},
|
||||
{type:1,content:'@鲸鱼 你好啊!'},
|
||||
]"></chat-box>
|
||||
|
||||
### CQ::video() - 发送短视频
|
||||
|
||||
发送短视频。
|
||||
|
||||
定义:`CQ::video($file, $cache = true, $proxy = true, $timeout = -1)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 默认值 | 说明 |
|
||||
| --------- | ---- | ---- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | ✓ | ✓ | 必填 | 短视频文件名 |
|
||||
| `cache` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否使用已缓存的文件,默认 `true` |
|
||||
| `proxy` | | ✓ | `true` | 只在通过网络 URL 发送时有效,表示是否通过代理下载文件(需通过环境变量或配置文件配置代理),默认 `true` |
|
||||
| `timeout` | | ✓ | `-1` | 只在通过网络 URL 发送时有效,单位秒,表示下载网络文件的超时时间,默认 -1 不超时 |
|
||||
|
||||
发送时,`file` 参数除了支持使用收到的视频文件名直接发送外,还支持其它形式,参考上方发送图片。
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::rps() - 猜拳
|
||||
|
||||
定义:`CQ::rps()`
|
||||
|
||||
用法:`CQ::rps()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::dice() - 掷骰子
|
||||
|
||||
定义:`CQ::dice()`
|
||||
|
||||
用法:`CQ::dice()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::shake() - 窗口抖动
|
||||
|
||||
定义:`CQ::shake()`
|
||||
|
||||
用法:`CQ::shake()`
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::poke() - 戳一戳
|
||||
|
||||
发送戳一戳。
|
||||
|
||||
定义:`CQ::poke($type, $id, $name = "")`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | ------------------------------------------------------------ | ------ |
|
||||
| `type` | ✓ | ✓ | 见 [Mirai 的 PokeMessage 类](https://github.com/mamoe/mirai/blob/f5eefae7ecee84d18a66afce3f89b89fe1584b78/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt#L49) | 类型 |
|
||||
| `id` | ✓ | ✓ | 同上 | ID |
|
||||
| `name` | ✓ | | 同上 | 表情名 |
|
||||
|
||||
例子:`CQ::poke(6,-1)`
|
||||
|
||||
效果:放大招
|
||||
|
||||
> 此 CQ 码只能用于单独一条文本消息中,如果混有其他字符串,则会吞掉其他字符串内容。
|
||||
|
||||
### CQ::anonymous() - 匿名发消息
|
||||
|
||||
匿名发消息。需要在允许匿名发消息的群里发。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
当收到匿名消息时,需要通过 [消息事件的群消息](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md#群消息) 的 `anonymous` 字段判断。
|
||||
|
||||
:::
|
||||
|
||||
定义:`CQ::anonymous($ignore = 1)`
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("匿名测试")
|
||||
*/
|
||||
public function anonymousTest() {
|
||||
ctx()->reply(CQ::anonymous()."匿名测试");
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::share() - 链接分享
|
||||
|
||||
发送链接分享卡片,可自定义内容。
|
||||
|
||||
定义:`CQ::share($url, $title, $content = null, $image = null)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| --------- | ---- | ---- | -------- | -------------- |
|
||||
| `url` | ✓ | ✓ | - | URL |
|
||||
| `title` | ✓ | ✓ | - | 标题 |
|
||||
| `content` | ✓ | ✓ | - | 可选,内容描述 |
|
||||
| `image` | ✓ | ✓ | - | 可选,图片 URL |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("链接分享测试")
|
||||
*/
|
||||
public function shareTest() {
|
||||
ctx()->reply(CQ::share("https://baidu.com", "UC忽悠部", "震惊!我市一男子在光天化日之下..."));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::contact() - 推荐好友
|
||||
|
||||
发送推荐好友的卡片。
|
||||
|
||||
定义:`CQ::contact($type, $id)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | ------------- | ---------------------- |
|
||||
| `type` | ✓ | ✓ | `qq`,`group` | 推荐好友或群 |
|
||||
| `id` | ✓ | ✓ | - | 被推荐人的 QQ 号或群号 |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("我的名片")
|
||||
*/
|
||||
public function myCard() {
|
||||
ctx()->reply(CQ::contact("qq", ctx()->getUserId()));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::location() - 发送位置
|
||||
|
||||
发送位置,基于经纬度坐标发的。
|
||||
|
||||
定义:`CQ::location($lat, $lon, $title = "", $content = "")`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| --------- | ---- | ---- | -------- | -------------- |
|
||||
| `lat` | ✓ | ✓ | - | 纬度 |
|
||||
| `lon` | ✓ | ✓ | - | 经度 |
|
||||
| `title` | ✓ | ✓ | - | 可选,标题 |
|
||||
| `content` | ✓ | ✓ | - | 可选,内容描述 |
|
||||
|
||||
### CQ::music() - 音乐分享
|
||||
|
||||
分享音乐,通过卡片。
|
||||
|
||||
发送音乐分享卡片。此 CQ 码如果伴随着其他文字,则文字内容会被丢弃。
|
||||
|
||||
定义:`CQ::music($type, $id_or_url, $audio = null, $title = null, $content = null, $image = null)`
|
||||
|
||||
- `$type`: 发送类型
|
||||
- `$id_or_url`: 音乐的 id 或 音乐卡片点进去打开的链接
|
||||
- `$audio`: 音频文件的 HTTP 地址
|
||||
- `$title`: 音乐卡片的标题,建议 12 字以内
|
||||
- `$content`: 音乐卡片的简介内容(可选)
|
||||
- `$image`: 音乐卡片的图片的链接地址(可选)
|
||||
|
||||
如果 `$type` 参数为 `qq` 或 `163` 或 `xiami`,则必须且只和第二个参数 `$id_or_url` 配合使用。这三个为内置分享,需要先通过搜索功能获取对应平台歌曲的 id 后使用。
|
||||
|
||||
如果 `$type` 参数为 `custom`,则表明此音乐卡片为用户自定义,你可以根据自己的需要自定义卡片内容和音频。此时必须填写 `$id_or_url`, `$audio`, `$title` 三个参数。
|
||||
|
||||
```php
|
||||
ctx()->reply(CQ::music("163", "730806")); //一首我喜欢的歌
|
||||
// 以内置的发送类型发送音乐卡片,我这里挑了网易云音乐的一首歌。
|
||||
|
||||
ctx()->reply("custom", "https://baidu.com/", "https://zhamao.xin/file/hello.mp3", "我是Siri说出来的Hello", "不服来打我呀!", "https://zhamao.xin/file/hello.jpg");
|
||||
// 自定义整个卡片的每个内容
|
||||
```
|
||||
|
||||
### CQ::forward() - 合并转发
|
||||
|
||||
合并转发消息。
|
||||
|
||||
定义:`CQ::forward($id)`
|
||||
|
||||
参数
|
||||
|
||||
```
|
||||
[CQ:forward,id=123456]
|
||||
```
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ------ | ---- | ---- | -------- | ------------------------------------------------------------ |
|
||||
| `id` | ✓ | | 必填 | 合并转发 ID,需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取具体内容 |
|
||||
|
||||
### CQ::node() - 合并转发自定义节点
|
||||
|
||||
接收时,此消息段不会直接出现在消息事件的 `message` 中,需通过 [`get_forward_msg` API](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-获取合并转发消息) 获取。发送时,通过获取回来的 API 节点信息进行发送。
|
||||
|
||||
定义:`CQ::node($user_id, $nickname, $content)`
|
||||
|
||||
参数
|
||||
|
||||
| 参数名 | 收 | 发 | 可能的值 | 说明 |
|
||||
| ---------- | ---- | ---- | -------- | ------------------------------------------------------------ |
|
||||
| `user_id` | ✓ | ✓ | - | 发送者 QQ 号 |
|
||||
| `nickname` | ✓ | ✓ | - | 发送者昵称 |
|
||||
| `content` | ✓ | ✓ | - | 消息内容,支持发送消息时的 `message` 数据类型,见 [API 的参数](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/#参数) |
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("node测试")
|
||||
*/
|
||||
public function nodeTest() {
|
||||
ctx()->reply(CQ::node(123456, "Jack", "[CQ:face,id=123]哈喽~"));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::xml() - XML 消息
|
||||
|
||||
发送 QQ 兼容的 XML 多媒体消息。
|
||||
|
||||
定义:`CQ::xml($data)`
|
||||
|
||||
参数:`$data` 为 xml 字符串
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("xml测试")
|
||||
*/
|
||||
public function xmlTest() {
|
||||
ctx()->reply(CQ::xml("<?xml ..."));
|
||||
}
|
||||
```
|
||||
|
||||
### CQ::json() - JSON 消息
|
||||
|
||||
发送 QQ 兼容的 JSON 多媒体消息。
|
||||
|
||||
定义:`CQ::json($data, $resid = 0)`
|
||||
|
||||
参数同上,内含 JSON 字符串即可。
|
||||
|
||||
其中 `$resid` 是面向 go-cqhttp 扩展的参数,默认不填为 0,走小程序通道,填了走富文本通道发送。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
因为某些众所周知的原因,XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。
|
||||
|
||||
:::
|
||||
|
||||
### CQ::_custom() - 扩展自定义 CQ 码
|
||||
|
||||
用于兼容各类含有被支持的扩展 CQ 码,比如 go-cqhttp 的 `[CQ:gift]` 礼物类型。
|
||||
|
||||
定义:`CQ::_custom(string $type_name, array $params)`
|
||||
|
||||
| 参数名 | 说明 |
|
||||
| ----------- | --------------------------------------------------- |
|
||||
| `type_name` | CQ 码类型,如 `music`,`at` |
|
||||
| `params` | 发送的 CQ 码中的参数数组,例如 `["qq" => "123456"]` |
|
||||
|
||||
下面是一个例子:
|
||||
|
||||
```php
|
||||
CQ::_custom("at",["qq" => "123456","qwe" => "asd"]);
|
||||
// 返回:[CQ:at,qq=123456,qwe=asd]
|
||||
```
|
||||
@ -1,35 +0,0 @@
|
||||
# 命令帮助生成器 - CommandHelpGenerator
|
||||
|
||||
类定义:`\ZM\Utils\MessageUtil`
|
||||
目前包含在 `MessageUtil` 类中,日后可能会进行拆分。
|
||||
|
||||
> 2.7.3 版本起可用。
|
||||
|
||||
## 方法
|
||||
|
||||
### generateCommandHelp
|
||||
|
||||
自动扫描定义的所有命令,生成注解树,并以此生成命令列表及帮助。
|
||||
|
||||
第一次运行时,会遍历一遍注解树并进行生成,此后会从缓存中读取。
|
||||
|
||||
定义:`generateCommandHelp()`
|
||||
|
||||
返回值:`array` 每个元素对应一个命令的帮助信息,格式为:`命令名(其他触发条件):命令描述`
|
||||
|
||||
示例:`天气(温度、包含“天气”):查询指定城市的天气`
|
||||
|
||||
```php
|
||||
/**
|
||||
* 输出帮助信息
|
||||
*
|
||||
* @CQCommand("帮助")
|
||||
*/
|
||||
#[CQCommand('帮助')]
|
||||
public function help(): string
|
||||
{
|
||||
$helps = MessageUtil::generateCommandHelp();
|
||||
array_unshift($helps, '帮助:');
|
||||
return implode("\n", $helps);
|
||||
}
|
||||
```
|
||||
@ -1,181 +0,0 @@
|
||||
# 消息处理工具类 - 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
|
||||
```
|
||||
|
||||
### isAtMe()
|
||||
|
||||
检查消息中是否含有@bot的消息。
|
||||
|
||||
定义:`isAtMe($msg, $me_id)`
|
||||
|
||||
参数 `$me_id` 为Bot的QQ号。
|
||||
|
||||
返回:`bool`,true 就是有,false 就没有。
|
||||
|
||||
```php
|
||||
MessageUtil::isAtMe("[CQ:at,qq=123456]炸毛你好","123456"); // true
|
||||
MessageUtil::isAtMe("[CQ:at,qq=123456789]另一个朋友你好","123456"); // 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` 对象,含有匹配成功与否,匹配到的注解对象,匹配到的分割词等,见 []
|
||||
|
||||
### addShortCommand()
|
||||
|
||||
快速添加一条静态消息回复命令。
|
||||
|
||||
定义:`addShortCommand($command, string $reply)`
|
||||
|
||||
参数 `$command` 为问的内容,如 `炸毛不聪明`。
|
||||
|
||||
参数 `$reply` 为回复的内容,如 `其实还是很聪明的!`。
|
||||
|
||||
这个命令推荐在 `@OnStart` 注解下使用,可以用这个来做一个动态的词库,从文件加载后使用。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnStart()
|
||||
*/
|
||||
public function onStart() {
|
||||
MessageUtil::addShortCommand("炸毛不聪明", "其实还是很聪明的!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'炸毛不聪明'},
|
||||
{type:1,content:'其实还是很聪明的!'},
|
||||
]"></chat-box>
|
||||
|
||||
### strToArray()
|
||||
|
||||
将 `string` 类型的消息文本转换为 `array` 格式。
|
||||
|
||||
定义:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
|
||||
|
||||
参数 `$msg` 为带 OB/CQ 码的字符串消息,如 `你好啊,[CQ:at,qq=123]`。
|
||||
|
||||
参数 `$ignore_space` 在 `false` 时,转换的数组内会包含空 `text` 段。
|
||||
|
||||
参数 `$trim_text` 为 `true` 时,会自动去除 `text` 段消息头尾的换行符和空格。
|
||||
|
||||
这个命令转换的数组格式符合 OneBot 11/12 标准,但细节上可能会与不同 OneBot 实现有所差异。
|
||||
|
||||
```php
|
||||
$str = "你好啊,[CQ:at,qq=123]";
|
||||
$arr = \ZM\Utils\MessageUtil::strToArray($str);
|
||||
```
|
||||
|
||||
转换结果参考如下:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"type": "text",
|
||||
"data": {
|
||||
"text": "你好啊,"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "at",
|
||||
"data": {
|
||||
"qq": "123"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### arrayToStr()
|
||||
|
||||
将 `array` 格式的消息内容转换为字符串 + CQ 码的形式。
|
||||
|
||||
定义:`arrayToStr(array $array)`
|
||||
|
||||
```php
|
||||
// 我们使用上边的 $arr 作为传入值。
|
||||
$new_str = \ZM\Utils\MessageUtil::arrayToStr($arr);
|
||||
// 结果:"你好啊,[CQ:at,qq=123]"
|
||||
```
|
||||
@ -1,11 +0,0 @@
|
||||
# 机器人动作 - V12
|
||||
|
||||
::: tip 提示
|
||||
|
||||
目前由于 OneBot 12 标准还没有定稿,处于草案阶段,故框架暂不更新。
|
||||
|
||||
在未来升级到 OneBot 12 标准后,框架会提供转换及兼容措施以及 12 版本的 API 方法。
|
||||
|
||||
:::
|
||||
|
||||
见 [机器人动作(OneBot 11)](./robot-api.html)。
|
||||
@ -1,808 +0,0 @@
|
||||
# 机器人动作 - V11
|
||||
|
||||
OneBotV11 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
|
||||
|
||||
| 属性项 | 属性值 | 备注 |
|
||||
| -------- | ------------------ | -------------------------------- |
|
||||
| 名称 | OneBotV11 | |
|
||||
| 类型 | 实例化类 | `$r = new OneBotV11($conn)` |
|
||||
| 命名空间 | `ZM\API\OneBotV11` | 使用前先 `use ZM\API\OneBotV11;` |
|
||||
| 别名 | `ZM\API\ZMRobot` | 此类目前是 `extends OneBotV11` |
|
||||
|
||||
> 你也可以继续使用 2.5 版本之前的别名类 `ZMRobot`,但未来框架将会优先兼容 OneBot V12 版本的协议,可能会造成更新问题,建议切换为 OneBotV11 类。
|
||||
|
||||
## 属性
|
||||
|
||||
对象属性方法是对 API 的调整,例如是否以 `_async`、`_rate_limited` 后缀发送 API、设置协程返回还是异步返回结果等。
|
||||
|
||||
### OneBotV11::API_NORMAL
|
||||
|
||||
以默认(无后缀)方式请求 API。
|
||||
|
||||
### OneBotV11::API_ASYNC
|
||||
|
||||
以后缀 `_async` 方式异步请求 API。
|
||||
|
||||
### OneBotV11::API_RATE_LIMITED
|
||||
|
||||
以后缀 `_rate_limited` 方式请求 API。
|
||||
|
||||
## 方法
|
||||
|
||||
### setPrefix()
|
||||
|
||||
设置后缀。目前支持 `_async`、`_rate_limited`。
|
||||
|
||||
- **prefix**: `int` `默认:API_NORMAL`,可选 `OneBotV11::API_NORMAL`、`OneBotV11::API_ASYNC`、`OneBotV11::API_RATE_LIMITED`
|
||||
|
||||
设置后缀后,请求的 API 会发生变化。例如发送私聊消息:`sendPrivateMsg()`,请求的 API 为 `send_private_msg_async`,详见 [OneBot 文档](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/README.md)。
|
||||
|
||||
### setCallback()
|
||||
|
||||
设置 API 结果返回方式。默认为 true,就是直接通过框架处理后接收回包直接返回给结果。如果为 false,则 API 请求后只返回是否成功推送出 WS 数据包。
|
||||
|
||||
### getSelfId()
|
||||
|
||||
获取当前对象的机器人 QQ 或 OneBot 实例的 ID。
|
||||
|
||||
```php
|
||||
$bot = OneBotV11::get(123456);
|
||||
echo $bot->getSelfId(); //123456
|
||||
```
|
||||
|
||||
### OneBotV11::get()
|
||||
|
||||
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 OneBotV11对象。
|
||||
|
||||
参数:`$robot_id`,必填。
|
||||
|
||||
```php
|
||||
$r = OneBotV11::get(123456);
|
||||
$r->sendPrivateMsg(55555, "hello");
|
||||
```
|
||||
|
||||
### OneBotV11::getRandom()
|
||||
|
||||
静态方法,随机获取一个连接到框架的机器人(多个机器人实例连接到框架时适用)。
|
||||
|
||||
如果框架没有连接到任何机器人实例,则会抛出一个异常:`ZM\Exception\RobotNotFoundException`。
|
||||
|
||||
```php
|
||||
try {
|
||||
$bot = OneBotV11::getRandom();
|
||||
$bot->sendPrivateMsg(55555, "foo");
|
||||
} catch (\ZM\Exception\RobotNotFoundException $e) {
|
||||
echo "还没有机器人连接到框架!\n";
|
||||
}
|
||||
```
|
||||
|
||||
### OneBotV11::getAllRobot()
|
||||
|
||||
获取所有连接到框架的机器人的 OneBotV11 对象。
|
||||
|
||||
返回值:`OneBotV11[]`。
|
||||
|
||||
```php
|
||||
$all = OneBotV11::getAllRobot();
|
||||
foreach($all as $v) {
|
||||
$v->sendPrivateMsg(55555, "机器人轮流给一个人发消息啦!");
|
||||
}
|
||||
```
|
||||
|
||||
### __construct()
|
||||
|
||||
构造方法。
|
||||
|
||||
参数:`$connection`:炸毛框架内部的连接对象,必填参数。
|
||||
|
||||
```php
|
||||
//从上下文获取 Websocket 连接对象
|
||||
$conn = ctx()->getConnection();
|
||||
$bot = new OneBotV11($conn);
|
||||
```
|
||||
|
||||
## 返回结果处理
|
||||
|
||||
因为框架的机器人是兼容 OneBot 标准的(原 CQHTTP),所以每次接收发送 API 请求的结果都是大体一样的结构。我们以 `sendPrivateMsg()` 为例,因为发送出去的每一条消息都会在 OneBot 实例(如 CQHTTP 插件、go-cqhttp 等)中对应一个消息 ID,以供我们核查消息和后续撤回等操作需要。
|
||||
|
||||
```php
|
||||
$bot = OneBotV11::get("123456"); // 机器人QQ号
|
||||
$obj = $bot->sendGroupMsg("234567", "你好");
|
||||
echo json_encode($obj, 128|256);
|
||||
```
|
||||
|
||||
输出结果
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"retcode": 0,
|
||||
"data": {
|
||||
"message_id": 1243
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如上,`$obj` 就是我们的回包内容,我们通过调用 `sendPrivateMsg()` 这个 API 后,会拿到机器人发送此条消息的消息 ID,然后可以通过它来进行其他操作(例如撤回)。
|
||||
|
||||
```php
|
||||
$result = $bot->deleteMsg($obj["data"]["message_id"]);
|
||||
vardump($result["retcode"]); //如果成功撤回,输出 int(0)
|
||||
```
|
||||
|
||||
### 状态码和 Data
|
||||
|
||||
状态码一般情况成功都是 0 或者 200,在过去,炸毛框架兼容 CQHTTP 插件时,错误码的标准按照 CYKU 给出的标准编写,不同的 OneBot 标准的实现,可能有不同的数值,需要根据你对接的机器人客户端进行适配。
|
||||
|
||||
结果中返回的 `data` 字段根据下方不同 API 的调用而不同,具体查看每个 API 写明的 `响应数据` 表格。
|
||||
|
||||
### response 表
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ---------- | ----------------------- |
|
||||
| `status` | String | "ok" | 状态码说明 |
|
||||
| `retcode` | number | 0 | 返回状态码 |
|
||||
| `data` | array | 见 data 表 | 根据不同的 API 返回不同 |
|
||||
|
||||
## 机器人 API 方法
|
||||
|
||||
### sendPrivateMsg()
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `user_id` | number | - | 对方 QQ 号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
代码
|
||||
|
||||
```php
|
||||
$bot = OneBotV11::get(123456); // 123456是你的机器人QQ
|
||||
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
|
||||
```
|
||||
|
||||
效果
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:1,content:'你好啊!你好你好!'}
|
||||
]"></chat-box>
|
||||
|
||||
### sendGroupMsg()
|
||||
|
||||
发送群组消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
### sendMsg()
|
||||
|
||||
发送消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------------- | -------- | ------- | ------------------------------------------------------------ |
|
||||
| `message_type` | string | - | 消息类型,支持 `private`、`group`、`discuss`,分别对应私聊、群组、讨论组,如不传入,则根据传入的 `*_id` 参数判断 |
|
||||
| `target_id` | number | - | 目标号码,如 QQ 号,群号,讨论组号 |
|
||||
| `message` | message | - | 要发送的内容 |
|
||||
| `auto_escape` | boolean | `false` | 消息内容是否作为纯文本发送(即不解析 CQ 码),只在 `message` 字段是字符串时有效 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------- |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
|
||||
### deleteMsg()
|
||||
|
||||
撤回消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------------- | ------ | ------- |
|
||||
| `message_id` | number (int32) | - | 消息 ID |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### getMsg()
|
||||
|
||||
获取消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | -------------------------------- |
|
||||
| `message_id` | number | - | 消息 ID |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------------- | -------------- | ------------------------------------------------------------ |
|
||||
| `time` | number (int32) | 发送时间 |
|
||||
| `message_type` | string | 消息类型,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
|
||||
| `message_id` | number (int32) | 消息 ID |
|
||||
| `real_id` | number (int32) | 消息真实 ID |
|
||||
| `sender` | object | 发送人信息,同 [消息事件](https://github.com/howmanybots/onebot/blob/master/v11/specs/event/message.md) |
|
||||
| `message` | message | 消息内容 |
|
||||
|
||||
### getForwardMsg()
|
||||
|
||||
获取合并转发消息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ----------- |
|
||||
| `id` | string | 合并转发 ID |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| --------- | ------- | ------------------------------------------------------------ |
|
||||
| `message` | message | 消息内容,使用 [消息的数组格式](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/array.md) 表示,数组中的消息段全部为 [`node` 消息段](https://github.com/howmanybots/onebot/blob/master/v11/specs/message/segment.md#合并转发自定义节点) |
|
||||
|
||||
### sendLike()
|
||||
|
||||
发送好友赞。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | -------------------------------- |
|
||||
| `user_id` | number | - | 对方 QQ 号 |
|
||||
| `times` | number | 1 | 赞的次数,每个好友每天最多 10 次 |
|
||||
|
||||
响应数据
|
||||
|
||||
无
|
||||
|
||||
### setGroupKick()
|
||||
|
||||
群组踢人。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------------------- | -------- | ------- | ------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要踢的 QQ 号 |
|
||||
| `reject_add_request` | boolean | `false` | 拒绝此人的加群请求 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupBan()
|
||||
|
||||
群组单人禁言。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | --------- | -------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要禁言的 QQ 号 |
|
||||
| `duration` | number | `30 * 60` | 禁言时长,单位秒,0 表示取消禁言 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAnonymousBan()
|
||||
|
||||
群组匿名用户禁言。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------------- | ---------------- | --------- | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `anonymous_or_flag` | object 或 string | - | 要禁言的匿名用户对象(群消息上报的 `anonymous` 字段)或用户的 flag |
|
||||
| `duration` | number | `30 * 60` | 禁言时长,单位秒,无法取消匿名用户禁言 |
|
||||
|
||||
上面的 `anonymous_or_flag` 两者任选其一传入即可。
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupWholeBan()
|
||||
|
||||
群组全员禁言
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | -------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `enable` | boolean | `true` | 是否禁言 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAdmin()
|
||||
|
||||
群组设置管理员
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置管理员的 QQ 号 |
|
||||
| `enable` | boolean | `true` | true 为设置,false 为取消 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAnonymous()
|
||||
|
||||
群组匿名
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `enable` | boolean | `true` | 是否允许匿名聊天 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupCard()
|
||||
|
||||
设置群名片(群备注)
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置的 QQ 号 |
|
||||
| `card` | string | 空 | 群名片内容,不填或空字符串表示删除群名片 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupName()
|
||||
|
||||
设置群名。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------ |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 新群名 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupLeave()
|
||||
|
||||
退出群组
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------- | ------- | -------------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `is_dismiss` | boolean | `false` | 是否解散,如果登录号是群主,则仅在此项为 true 时能够解散 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupSpecialTitle()
|
||||
|
||||
设置群组专属头衔
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | 要设置的 QQ 号 |
|
||||
| `special_title` | string | 空 | 专属头衔,不填或空字符串表示删除专属头衔 |
|
||||
| `duration` | number | `-1` | 专属头衔有效期,单位秒,-1 表示永久,不过此项似乎没有效果,可能是只有某些特殊的时间长度有效,有待测试 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setFriendAddRequest()
|
||||
|
||||
处理加好友请求
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | ------ | ----------------------------------------- |
|
||||
| `flag` | string | - | 加好友请求的 flag(需从上报的数据中获得) |
|
||||
| `approve` | boolean | `true` | 是否同意请求 |
|
||||
| `remark` | string | 空 | 添加后的好友备注(仅在同意时有效) |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### setGroupAddRequest()
|
||||
|
||||
处理加群请求 / 邀请
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `flag` | string | - | 加群请求的 flag(需从上报的数据中获得) |
|
||||
| `sub_type` | string | - | `add` 或 `invite`,请求类型(需要和上报消息中的 `sub_type` 字段相符) |
|
||||
| `approve` | boolean | `true` | 是否同意请求/邀请 |
|
||||
| `reason` | string | 空 | 拒绝理由(仅在拒绝时有效) |
|
||||
|
||||
响应数据无
|
||||
|
||||
### getLoginInfo()
|
||||
|
||||
获取登录号信息
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | QQ 昵称 |
|
||||
|
||||
### getStrangerInfo()
|
||||
|
||||
获取陌生人信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `user_id` | number | - | QQ 号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------------------------------------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
|
||||
| `age` | number (int32) | 年龄 |
|
||||
|
||||
### getFriendList()
|
||||
|
||||
获取好友列表
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ---------- | -------------- | ------ |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `remark` | string | 备注名 |
|
||||
|
||||
### getGroupInfo()
|
||||
|
||||
获取群信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------ | -------------- | -------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 群名称 |
|
||||
| `member_count` | number (int32) | 成员数 |
|
||||
| `max_member_count` | number (int32) | 最大成员数(群容量) |
|
||||
|
||||
### getGroupList()
|
||||
|
||||
获取群列表
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ------ |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `group_name` | string | 群名称 |
|
||||
|
||||
### getGroupMemberInfo()
|
||||
|
||||
获取群成员信息
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------- | ---------------------------------------------------- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
| `user_id` | number | - | QQ 号 |
|
||||
| `no_cache` | boolean | `false` | 是否不使用缓存(使用缓存可能更新不及时,但响应更快) |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------- | -------------- | ------------------------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `card` | string | 群名片/备注 |
|
||||
| `sex` | string | 性别,`male` 或 `female` 或 `unknown` |
|
||||
| `age` | number (int32) | 年龄 |
|
||||
| `area` | string | 地区 |
|
||||
| `join_time` | number (int32) | 加群时间戳 |
|
||||
| `last_sent_time` | number (int32) | 最后发言时间戳 |
|
||||
| `level` | string | 成员等级 |
|
||||
| `role` | string | 角色,`owner` 或 `admin` 或 `member` |
|
||||
| `unfriendly` | boolean | 是否不良记录成员 |
|
||||
| `title` | string | 专属头衔 |
|
||||
| `title_expire_time` | number (int32) | 专属头衔过期时间戳 |
|
||||
| `card_changeable` | boolean | 是否允许修改群名片 |
|
||||
|
||||
### getGroupMemberList()
|
||||
|
||||
获取群成员列表
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------- | ------ | ---- |
|
||||
| `group_id` | number | - | 群号 |
|
||||
|
||||
响应数据
|
||||
|
||||
响应内容为 JSON 数组,每个元素的内容和上面的 `/get_group_member_info` 接口相同,但对于同一个群组的同一个成员,获取列表时和获取单独的成员信息时,某些字段可能有所不同,例如 `area`、`title` 等字段在获取列表时无法获得,具体应以单独的成员信息为准。
|
||||
|
||||
### getGroupHonorInfo()
|
||||
|
||||
获取群荣誉信息。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ---------- | -------------- | ------ | ------------------------------------------------------------ |
|
||||
| `group_id` | number (int64) | - | 群号 |
|
||||
| `type` | string | - | 要获取的群荣誉类型,可传入 `talkative` `performer` `legend` `strong_newbie` `emotion` 以分别获取单个类型的群荣誉数据,或传入 `all` 获取所有数据 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------------------- | -------------- | ---------------------------------------------------------- |
|
||||
| `group_id` | number (int64) | 群号 |
|
||||
| `current_talkative` | object | 当前龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
|
||||
| `talkative_list` | array | 历史龙王,仅 `type` 为 `talkative` 或 `all` 时有数据 |
|
||||
| `performer_list` | array | 群聊之火,仅 `type` 为 `performer` 或 `all` 时有数据 |
|
||||
| `legend_list` | array | 群聊炽焰,仅 `type` 为 `legend` 或 `all` 时有数据 |
|
||||
| `strong_newbie_list` | array | 冒尖小春笋,仅 `type` 为 `strong_newbie` 或 `all` 时有数据 |
|
||||
| `emotion_list` | array | 快乐之源,仅 `type` 为 `emotion` 或 `all` 时有数据 |
|
||||
|
||||
其中 `current_talkative` 字段的内容如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ----------- | -------------- | -------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `avatar` | string | 头像 URL |
|
||||
| `day_count` | number (int32) | 持续天数 |
|
||||
|
||||
其它各 `*_list` 的每个元素是一个 JSON 对象,内容如下:
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------- | -------------- | -------- |
|
||||
| `user_id` | number (int64) | QQ 号 |
|
||||
| `nickname` | string | 昵称 |
|
||||
| `avatar` | string | 头像 URL |
|
||||
| `description` | string | 荣誉描述 |
|
||||
|
||||
### getCookies()
|
||||
|
||||
获取 Cookies。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
目前开源的 mirai 为底层的机器人客户端均不支持获取 Cookies 和 CSRF Token,包括 go-cqhttp。
|
||||
|
||||
:::
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | ------ | ----------------------- |
|
||||
| `domain` | string | 空 | 需要获取 cookies 的域名 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| --------- | -------- | ------- |
|
||||
| `cookies` | string | Cookies |
|
||||
|
||||
### getCsrfToken()
|
||||
|
||||
获取 CSRF Token
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------- | -------------- | ---------- |
|
||||
| `token` | number (int32) | CSRF Token |
|
||||
|
||||
### getCredentials()
|
||||
|
||||
获取 QQ 相关接口凭证,即上面两个合并。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| -------- | -------- | ------ | ----------------------- |
|
||||
| `domain` | string | 空 | 需要获取 cookies 的域名 |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------ | -------------- | ---------- |
|
||||
| `cookies` | string | Cookies |
|
||||
| `csrf_token` | number (int32) | CSRF Token |
|
||||
|
||||
### getRecord()
|
||||
|
||||
获取语音。其实并不是真的获取语音,而是转换语音到指定的格式。
|
||||
|
||||
> **提示**:要使用此接口,通常需要安装 ffmpeg,请参考 OneBot 实现的相关说明。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------------ | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | string | - | 收到的语音文件名(CQ 码的 `file` 参数),如 `0B38145AA44505000B38145AA4450500.silk` |
|
||||
| `out_format` | string | - | 要转换到的格式,目前支持 `mp3`、`amr`、`wma`、`m4a`、`spx`、`ogg`、`wav`、`flac` |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------------------------------------------------------------ |
|
||||
| `file` | string | 转换后的语音文件路径,如 `/home/somebody/cqhttp/data/record/0B38145AA44505000B38145AA4450500.mp3` |
|
||||
|
||||
### getImage()
|
||||
|
||||
获取图片。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------ | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `file` | string | - | 收到的图片文件名(CQ 码的 `file` 参数),如 `6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------------------------------------------------------------ |
|
||||
| `file` | string | 下载后的图片文件路径,如 `/home/somebody/cqhttp/data/image/6B4DE3DFD1BD271E3297859D41C530F5.jpg` |
|
||||
|
||||
### canSendImage()
|
||||
|
||||
检查是否可以发送图片。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------ | -------- | ------ |
|
||||
| `yes` | boolean | 是或否 |
|
||||
|
||||
### canSendRecord()
|
||||
|
||||
检查是否可以发送语音,返回同上。
|
||||
|
||||
### getStatus()
|
||||
|
||||
获取插件运行状态。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| -------- | -------- | -------------------------------------------------------- |
|
||||
| `online` | boolean | 当前 QQ 在线,`null` 表示无法查询到在线状态 |
|
||||
| `good` | boolean | 状态符合预期,意味着各模块正常运行、功能正常,且 QQ 在线 |
|
||||
| ...... | - | OneBot 实例自行添加的其他内容 |
|
||||
|
||||
通常情况下建议只使用 `online` 和 `good` 这两个字段来判断运行状态,因为根据 OneBot 实现的不同,其它字段可能完全不同。
|
||||
|
||||
### getVersionInfo()
|
||||
|
||||
获取版本信息
|
||||
|
||||
响应数据
|
||||
|
||||
| 字段名 | 数据类型 | 说明 |
|
||||
| ------------------ | -------- | ----------------------------- |
|
||||
| `app_name` | string | 应用标识,如 `mirai-native` |
|
||||
| `app_version` | string | 应用版本,如 `1.2.3` |
|
||||
| `protocol_version` | string | OneBot 标准版本,如 `v11` |
|
||||
| …… | - | OneBot 实现自行添加的其它内容 |
|
||||
|
||||
### setRestartPlugin()
|
||||
|
||||
重启 OneBot 客户端
|
||||
|
||||
由于重启 OneBot 实现同时需要重启 API 服务,这意味着当前的 API 请求会被中断,因此需要异步地重启,接口返回的 `status` 是 `async`。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 | 说明 |
|
||||
| ------- | -------- | ------ | ------------------------------------------------------------ |
|
||||
| `delay` | number | `0` | 要延迟的毫秒数,如果默认情况下无法重启,可以尝试设置延迟为 2000 左右 |
|
||||
|
||||
响应数据:无
|
||||
|
||||
### cleanCache()
|
||||
|
||||
清理 OneBot 客户端的缓存。
|
||||
|
||||
参数:无
|
||||
|
||||
响应数据:无
|
||||
|
||||
### getExtendedAPI()
|
||||
|
||||
用来调用 OneBot 标准之外扩展出来的自定义 API。与下方 `callExtendedAPI` 不同的是,为了方便用户使用,炸毛框架内置了热门使用并且相对稳定的机器人客户端的专有 API。
|
||||
|
||||
目前内置了 `go-cqhttp` 频道相关的扩充 API。
|
||||
|
||||
使用示例:`getExtendedAPI('go-cqhttp')->getGuildList()`
|
||||
使用示例2:`getExtendedAPI()->sendGuildChannelMsg($guild_id, $channel_id, '频道的消息')`
|
||||
|
||||
唯一一个参数做保留,用于选择不同客户端,目前仅支持 `go-cqhttp`,所以缺省也默认为 `go-cqhttp`。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
由于不同版本的扩展 API 变化可能会很大,改动较多,炸毛框架不会将对应扩展方法写入文档,具体调用情况可根据 IDE 自动补全中的文档或对应类的注释查看。
|
||||
|
||||
:::
|
||||
|
||||
### callExtendedAPI() (扩充 API)
|
||||
|
||||
用来调用 OneBot 标准之外扩展出来的自定义 API。
|
||||
|
||||
使用不同 OneBot 客户端时,可能有一些 API 不在上方的 OneBot 标准里,这时可以使用此方法进行额外调用。
|
||||
|
||||
参数
|
||||
|
||||
| 字段名 | 数据类型 | 默认值 |
|
||||
| -------- | -------- | ------ |
|
||||
| `action` | string | 必填 |
|
||||
| `params` | array | `[]` |
|
||||
|
||||
例子
|
||||
|
||||
```php
|
||||
$result = $bot->callExtendedAPI("get_group_root_files", ["group_id" => 123456]);
|
||||
//这里以 go-cqhttp 扩展的一个获取群文件的 API 为例
|
||||
var_dump($result["data"]);
|
||||
// 输出群文件列表
|
||||
```
|
||||
@ -1,86 +0,0 @@
|
||||
# 图灵聊天 - TuringAPI
|
||||
|
||||
类定义:`\ZM\API\TuringAPI`
|
||||
|
||||
## 方法
|
||||
|
||||
### TuringAPI::getTuringMsg()
|
||||
|
||||
请求图灵接口,返回回复的消息。
|
||||
|
||||
定义:`getTuringMsg($msg, $user_id, $api)`
|
||||
|
||||
参数 `$msg` 为用户的消息内容,如果含有图片 CQ 码,则自动转换为图灵兼容的接口模式。
|
||||
|
||||
参数 `$user_id` 为用户 ID,一般默认给 QQ 号码就可以了,注意最好不要有特殊字符(如 `./\<>*` 等),否则会间断性调用失败。
|
||||
|
||||
参数 `$api` 为图灵机器人的 `apikey`,可以到 <http://www.turingapi.com/> 申请免费或付费的 API key。
|
||||
|
||||
在框架的示例模块中,已经写好了一个正常机器人响应图灵回复的命令,如下:
|
||||
|
||||
```php
|
||||
class Hello {
|
||||
/**
|
||||
* 图灵机器人的内置实现,在www.turingapi.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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如上述代码,我们将申请的 apikey 填入变量 `$api` 中,启动机器人即可使用,以下是实测消息(我用自己申请的 key 做测试回复的消息)。
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'你咋了'},
|
||||
{type:1,content:'我没事哦,谢谢您的关心。'},
|
||||
{type:0,content:'上海天气'},
|
||||
{type:1,content:'上海:周一 03月29日,小雨 东南风转东风,最低气温14度,最高气温24度。'},
|
||||
{type:2,content:'切换为群内'},
|
||||
{type:0,content:'机器人'},
|
||||
{type:1,content:'我在!有什么事吗?'},
|
||||
{type:0,content:'你叫啥'},
|
||||
{type:1,content:'我的名字叫炸毛,认识你很高兴呢!'},
|
||||
]"></chat-box>
|
||||
|
||||
在默认示例模块中的例子是直接可以拿来用的,这段代码同时做了对 at 的处理、以及兼容用户自定义写的其他命令的方式,下面是默认模块填好 apikey 后可以用的各种方式提问:
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:2,content:'切换为群内'},
|
||||
{type:0,content:'我是一条普通消息,这条机器人不会回复我'},
|
||||
{type:0,content:'@机器人 你叫啥'},
|
||||
{type:1,content:'我是聪明可爱的炸毛,认识你很高兴。'},
|
||||
{type:0,content:'机器人'},
|
||||
{type:1,content:'我在!有什么事吗?'},
|
||||
{type:0,content:'一言'},
|
||||
{type:1,content:'多少事,从来急,天地转,光阴迫,一万年太久,只争朝夕。'},
|
||||
]"></chat-box>
|
||||
@ -1,179 +0,0 @@
|
||||
# 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 带有任何颜色,使用无色主题
|
||||
```
|
||||
@ -1,428 +0,0 @@
|
||||
# 上下文
|
||||
|
||||
上下文作为整个框架中最重要的内容之一,请务必理解和完整地阅读此部分!
|
||||
|
||||
一个上下文描述了一个事件和所关联的对象的环境。例如:你在处理 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 :my-chats="[
|
||||
{type:2,content:'假设我们和连接55555的机器人的私聊'},
|
||||
{type:0,content:'测试fd'},
|
||||
{type:1,content:'当前机器人连接的fd是:1,机器人QQ是:55555'},
|
||||
{type:2,content:'假设切到了另一个机器人(66666)的私聊'},
|
||||
{type:0,content:'测试fd'},
|
||||
{type:1,content:'当前机器人连接的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 :my-chats="[
|
||||
{type:2,content:'假设我是QQ为123456的用户,私聊发消息'},
|
||||
{type:0,content:'哈咯!!'},
|
||||
{type:1,content:'消息类型是: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 :my-chats="[
|
||||
{type:2,content:'正在和机器人聊天'},
|
||||
{type:1,content:'发送私聊消息'},
|
||||
]"></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 :my-chats="[
|
||||
{type:2,content:'现在在群33333内,机器人已经成了复读机'},
|
||||
{type:0,content:'来世还做复读机!!!'},
|
||||
{type:1,content:'来世还做复读机!!!'},
|
||||
{type:0,content:'你不许复读!'},
|
||||
{type:1,content:'你不许复读!'},
|
||||
]"></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 :my-chats="[
|
||||
{type:0,content:'自我介绍'},
|
||||
{type:1,content:'你叫啥名字呀?'},
|
||||
{type:0,content:'jerry'},
|
||||
{type:1,content:'好的,可爱的机器人记住你叫 jerry 啦!以后多聊天哦!'},
|
||||
{type:0,content:'自我介绍'},
|
||||
{type:1,content:'你叫啥名字呀?'},
|
||||
{type:2,content:'10分钟没理机器人'},
|
||||
{type:1,content:'你都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 :my-chats="[
|
||||
{type:0,content:'test'},
|
||||
{type:1,content:'请输入你要传入的参数内容'},
|
||||
{type:0,content:'test2'},
|
||||
{type:1,content:'参数内容:test2'},
|
||||
]"></chat-box>
|
||||
|
||||
`getArgs()` 也有三层封装,在使用过程中避免麻烦的话,推荐使用下面这几种 `get*Arg()` 方式。
|
||||
|
||||
## getFullArg()
|
||||
|
||||
获取关键词后的整个字符串参数,包括空格,如果不存在则询问。
|
||||
|
||||
典型例子:`复读机 你好 你好`,获取参数时会将 `你好 你好` 当作一个参数来获取。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getFullArg("请输入你要传入的参数内容");
|
||||
return "参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'test abc def argtest'},
|
||||
{type:1,content:'参数内容:abc def argtest'},
|
||||
{type:0,content:'test'},
|
||||
{type:1,content:'请输入你要传入的参数内容'},
|
||||
{type:0,content:'abc def'},
|
||||
{type:1,content:'参数内容:abc def'},
|
||||
]"></chat-box>
|
||||
|
||||
## getNextArg()
|
||||
|
||||
获取下一个参数,分隔符可以是空格,tab。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getNextArg("请输入你要传入的参数内容");
|
||||
return "参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'test abc def argtest'},
|
||||
{type:1,content:'参数内容:abc'},
|
||||
{type:0,content:'test'},
|
||||
{type:1,content:'请输入你要传入的参数内容'},
|
||||
{type:0,content:'abc'},
|
||||
{type:1,content:'参数内容:abc'},
|
||||
]"></chat-box>
|
||||
|
||||
## getNumArg()
|
||||
|
||||
> 2.1.5 版本起可用。
|
||||
|
||||
获取下一个数字型参数,如果 `is_numeric()` 为 true 则获取成功,如果没有符合的则询问用户。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
$s = ctx()->getNextArg("请输入你要传入的数字内容");
|
||||
return "数字参数内容:".$s;
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'test abc 334 argtest'},
|
||||
{type:1,content:'数字参数内容:334'},
|
||||
{type:0,content:'test abc'},
|
||||
{type:1,content:'请输入你要传入的参数内容'},
|
||||
{type:0,content:'998'},
|
||||
{type:1,content:'参数内容:998'},
|
||||
]"></chat-box>
|
||||
|
||||
## copy()
|
||||
|
||||
获取整个上下文的所有内容的数组形式。
|
||||
|
||||
```php
|
||||
$arr = ctx()->copy();
|
||||
dump($arr);
|
||||
```
|
||||
|
||||
## getOption() - 获取匹配参数内容
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("test")
|
||||
*/
|
||||
public function argTest1() {
|
||||
return "参数内容:".implode(", ", ctx()->getOption());
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'test abc 334 argtest'},
|
||||
{type:1,content:'参数内容:abc, 334, argtest'},
|
||||
]"></chat-box>
|
||||
@ -1,62 +0,0 @@
|
||||
# 协程池
|
||||
|
||||
首先要声明的一点是,协程池这个概念是我自己编的。
|
||||
|
||||
因为协程的特点,它是单线程下运行的,所以在一个进程内同时实际上只会有一个协程的代码在执行逻辑,但是后面的 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 配置文件中指定的最大协程数量。
|
||||
@ -1,104 +0,0 @@
|
||||
# 事件跟踪器及调试
|
||||
|
||||
众所周知,炸毛框架中的事件由内置的事件分发器(EventDispatcher)负责分发,但调试事件分发在之前的版本比较困难,例如不能获取到事件如何被调用,以及事件如何被捕获。
|
||||
|
||||
EventTracer 的作用是记录事件的调用顺序,以便于调试。
|
||||
|
||||
命名空间使用指南:`use ZM\Event\EventTracer;`
|
||||
|
||||
## EventTracer::getCurrentEvent() - 获取当前注解事件对象
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnStart()
|
||||
*/
|
||||
public function onStart() {
|
||||
zm_dump(EventTracer::getCurrentEvent());
|
||||
}
|
||||
/*
|
||||
^ ZM\Annotation\Swoole\OnStart^ {#192
|
||||
+worker_id: 0
|
||||
+method: "onStart"
|
||||
+class: "Module\Example\Hello"
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
这里这个方法必须在注解事件内执行,如果在注解事件外执行,将会返回 `null`。
|
||||
|
||||
## EventTracer::getCurrentEventMiddlewares() - 获取当前注解事件的中间件们
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnStart()
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
public function onStart() {
|
||||
zm_dump(EventTracer::getCurrentEventMiddlewares());
|
||||
}
|
||||
/*
|
||||
^ array:1 [
|
||||
0 => ZM\Annotation\Http\Middleware^ {#194
|
||||
+middleware: "timer"
|
||||
+params: []
|
||||
+method: "onStart"
|
||||
+class: "Module\Example\Hello"
|
||||
}
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
返回值为当前注解事件的中间件们,如果没有注解中间件,返回 `[]`。
|
||||
|
||||
## EventTracer::getEventTraceList() - 获取注解事件的列表
|
||||
|
||||
此处返回的是 `getCurrentEvent()` 相同的对象,但是返回的是一个数组,数组中的元素是注解事件。
|
||||
|
||||
```php
|
||||
/**
|
||||
* 一个简单随机数的功能demo
|
||||
* 问法1:随机数 1 20
|
||||
* 问法2:从1到20的随机数
|
||||
* @CQCommand("随机数")
|
||||
* @Middleware("timer")
|
||||
* @CQCommand(pattern="*从*到*的随机数")
|
||||
* @return string
|
||||
*/
|
||||
public function randNum() {
|
||||
// 此处为随机数代码
|
||||
zm_dump(EventTracer::getEventTraceList());
|
||||
return "随机数:" . rand(1, 20);
|
||||
}
|
||||
|
||||
/*
|
||||
^ array:2 [
|
||||
0 => ZM\Annotation\CQ\CQCommand^ {#193
|
||||
+match: ""
|
||||
+pattern: "*从*到*的随机数"
|
||||
+regex: ""
|
||||
+start_with: ""
|
||||
+end_with: ""
|
||||
+keyword: ""
|
||||
+alias: []
|
||||
+message_type: ""
|
||||
+user_id: 0
|
||||
+group_id: 0
|
||||
+discuss_id: 0
|
||||
+level: 20
|
||||
+method: "randNum"
|
||||
+class: "Module\Example\Hello"
|
||||
}
|
||||
1 => ZM\Annotation\Swoole\OnMessageEvent^ {#165
|
||||
+connect_type: "default"
|
||||
+rule: "connectIsQQ()"
|
||||
+level: 99
|
||||
+method: "handleByEvent"
|
||||
+class: "ZM\Module\QQBot"
|
||||
}
|
||||
]
|
||||
*/
|
||||
```
|
||||
|
||||
## EventDispatcher::enableEventTrace() - 启用事件跟踪器
|
||||
|
||||
还没写完,不着急。
|
||||
@ -1,323 +0,0 @@
|
||||
# 全局方法
|
||||
|
||||
全局方法就是 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);
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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)`。
|
||||
@ -1,44 +0,0 @@
|
||||
# 远程终端
|
||||
|
||||
框架在 2.3 版本时删除了本地终端(就是框架启动后可以在终端输入一些参数),因为框架的多进程模式会导致终端输入错乱,所以暂时取消掉了。
|
||||
|
||||
而远程终端应运而生,为的是弥补这一功能。与之前不同的是,远程终端使用 nc 连接,无需任何其他组件和客户端,而且功能更丰富,支持自定义命令。
|
||||
|
||||
## 启用
|
||||
|
||||
有两种开启方式:
|
||||
|
||||
- 永久开启:全局配置文件中找到 `remote_terminal` 的 `status`,改为 true,启动框架即可。
|
||||
- 临时开启:启动框架时加上参数 `--remote-terminal`。例如:`vendor/bin/start server --remote-terminal`。
|
||||
|
||||
## 配置
|
||||
|
||||
在一般情况下,框架为了安全,直接按照默认配置,会监听 `127.0.0.1:20002` 端口,不可以远程访问,只能使用本机的 nc 连接,效果如下:
|
||||
|
||||
本地主机:
|
||||
|
||||

|
||||
|
||||
从别的主机:
|
||||
|
||||

|
||||
|
||||
如果将 `host` 改为 `0.0.0.0` 或对应监听地址,即可指向性访问。
|
||||
|
||||
但是,如果你又想远程连接,又想保证安全,那么可以设置一个 token 参数,来保证连接时需要输入 token 才能使用远程终端。
|
||||
假设我们的 token 是 `iAMTokEn`:
|
||||
|
||||

|
||||
|
||||
## 使用
|
||||
|
||||
默认情况下,使用 `nc` 命令即可。
|
||||
|
||||
```bash
|
||||
nc <your-host> <your-port> -vvv
|
||||
# nc 127.0.0.1 20002 -vvv
|
||||
```
|
||||
|
||||
输入 help 即可查看内置的常用指令:
|
||||
|
||||

|
||||
@ -1,43 +0,0 @@
|
||||
# 单例类 - 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;` 一句话即可。
|
||||
@ -1,25 +0,0 @@
|
||||
# TaskManager 工作进程管理
|
||||
|
||||
此类管理的是 TaskWorker 相关工作。有关使用 TaskWorker 的教程,见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)
|
||||
|
||||
类定义:`\ZM\Utils\Manager\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");
|
||||
```
|
||||
@ -1,66 +0,0 @@
|
||||
# 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)。
|
||||
@ -1,56 +0,0 @@
|
||||
# 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)。
|
||||
@ -1,274 +0,0 @@
|
||||
# ZMRequest(HTTP 客户端)
|
||||
|
||||
框架提供了轻量的 HTTP 请求发起工具类,直接静态调用即可。
|
||||
|
||||
命名空间:`use ZM\Requests\ZMRequest;`
|
||||
|
||||
::: warning 注意
|
||||
|
||||
在使用 Swoole 4.6.0 以下(不包含)的版本时,最好使用 Swoole 官方推荐的 Saber 或者 ZMRequest 这个轻量的 HTTP 请求客户端,不要使用 curl_exec,因为在老版本的 Swoole 上对 curl 的协程 Hook 支持不是很完善。
|
||||
|
||||
:::
|
||||
|
||||
## ZMRequest::get()
|
||||
|
||||
发起 GET 请求。
|
||||
|
||||
定义:`ZMRequest::get($url, $headers = [], $set = [], $return_body = true)`
|
||||
|
||||
全局函数别名:`zm_request_get($url, $headers = [], $set = [], $return_body = true)`
|
||||
|
||||
`$url`:要请求的 url,如 `http://captive.apple.com/`
|
||||
|
||||
`$headers`:要请求的 Headers,例如:`["User-Agent" => "Chrome"]`,数组形式
|
||||
|
||||
`$set`:请求时的一些设置,例如超时时间等等。详见下方“设置参数”
|
||||
|
||||
`$return_body`:是否只返回请求回来的内容部分,默认为 true,如果为 false 时则会返回一个 `\Swoole\Coroutine\Http\Client` 对象,可查阅 [Swoole 文档](http://wiki.swoole.com/#/coroutine_client/http_client) 进行接下来的一系列操作。
|
||||
|
||||
如果 `$return_body` 为 true,但是请求失败(HTTP 状态码不是 200 或无法连接到目标服务器或者无法解析域名等问题)时,方法会返回 false,否则会返回内容。
|
||||
|
||||
返回值:`false|string|\Swoole\Coroutine\Http\Client`
|
||||
|
||||
```php
|
||||
$r = ZMRequest::get("http://captive.apple.com/", ["User-Agent" => "Chrome"]);
|
||||
echo $r.PHP_EOL; // <HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>
|
||||
```
|
||||
|
||||
```php
|
||||
$r = zm_request_get("http://captive.apple.com/", [], [], false);
|
||||
echo $r->body.PHP_EOL; // 这行输出和上方的一致
|
||||
dump($r);
|
||||
/*
|
||||
^ Swoole\Coroutine\Http\Client {#170
|
||||
+errCode: 0
|
||||
+errMsg: ""
|
||||
+connected: false
|
||||
+host: "captive.apple.com"
|
||||
+port: 80
|
||||
+ssl: false
|
||||
+setting: array:1 [
|
||||
"timeout" => 15.0
|
||||
]
|
||||
+requestMethod: "GET"
|
||||
+requestHeaders: []
|
||||
+requestBody: null
|
||||
+uploadFiles: null
|
||||
+downloadFile: null
|
||||
+downloadOffset: 0
|
||||
+statusCode: 200
|
||||
+headers: array:4 [
|
||||
"content-type" => "text/html"
|
||||
"content-length" => "68"
|
||||
"date" => "Thu, 07 Jan 2021 06:22:32 GMT"
|
||||
"connection" => "keep-alive"
|
||||
]
|
||||
+set_cookie_headers: null
|
||||
+cookies: null
|
||||
+body: "<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>"
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
## ZMRequest::post()
|
||||
|
||||
发送一个 POST 请求。
|
||||
|
||||
定义:`ZMRequest::post($url, array $header, $data, $set = [], $return_body = true)`
|
||||
|
||||
全局函数别名:`zm_request_post($url, array $header, $data, $set = [], $return_body = true)`
|
||||
|
||||
`$url`:同上,填入 url,必填
|
||||
|
||||
`$header`:请求的 Headers,必填,数组形式,例如 `["Content-Type" => "application/json"]`
|
||||
|
||||
`$data`:请求的数据体,默认应该传入数组,如果传入 `array` 类型,则会默认当作 `Content-Type: application/x-www-form-urlencoded` 方式自动转码和转换,例如 `["key1" => "b1", "key2" => "b2"]` 会变成 `key1=b1&key2=b2`
|
||||
|
||||
`$set`:同上,见下面的设置参数部分。
|
||||
|
||||
`$return_body`:同上。
|
||||
|
||||
```php
|
||||
$s = ZMRequest::post("http://captive.apple.com/", ["Content-Type" => "application/json"], json_encode(["key1" => "value1"]));
|
||||
```
|
||||
|
||||
## ZMRequest::request()
|
||||
|
||||
发起自定义一切参数的 HTTP 请求。
|
||||
|
||||
参数:
|
||||
|
||||
- `$url`:请求的链接,自动解析端口、HTTPS、DNS 等操作
|
||||
- `$attribute`:请求的属性,示例见下方
|
||||
- `$return_body`:可选参数,`bool` 类型,和上面的 `$return_body` 参数意义相同
|
||||
|
||||
其中 `$attribute` 参数对应可设置的有:
|
||||
|
||||
- `method`:可选 `GET`,`POST` 等 HTTP 请求的方式
|
||||
- `set`:设置 Swoole 客户端的参数
|
||||
- `headers`:要请求的 HTTP Headers
|
||||
- `data`:请求的 body 数据,为数组时自动转换头部为 `x-www-form-urlencoded`
|
||||
- `file`:要发送的文件,数组,可选多个文件
|
||||
|
||||
例1:使用 GET 请求发送带有 Body 的 HTTP 请求
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com", [
|
||||
"method" => "GET",
|
||||
"data" => [
|
||||
"key1" => "value1"
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
例2:设置请求超时时间并指定自定义头部
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com", [
|
||||
"method" => "POST",
|
||||
"headers" => [
|
||||
"X-Custom-Header" => "Hello world",
|
||||
"User-Agent" => "HEICORE"
|
||||
],
|
||||
"set" => ["timeout" => 10.0]
|
||||
]);
|
||||
```
|
||||
|
||||
例3:发送文件和 data
|
||||
|
||||
```php
|
||||
$r = ZMRequest::request("http://example.com/sendfile", [
|
||||
"file" => [
|
||||
[
|
||||
"path" => "/path/to/image1.jpg", // path字段必填
|
||||
"name" => "file1", // name字段必填,这个是 POST 过去的 key
|
||||
//"mime_type" => "image/jpeg", // 可选字段,底层会自动推断
|
||||
//"filename" => "a.jpg", // 可选字段,文件名称
|
||||
//"offset" => 0, // 可选字段,可以从指定文件的中间部分开始传输数据,此特性用于断点续传
|
||||
//"length" => 1024 // 可选字段,默认为整个文件的尺寸
|
||||
],
|
||||
[
|
||||
"path" => "/path/to/image2.jpg",
|
||||
"name" => "file2"
|
||||
]
|
||||
],
|
||||
"data" => [
|
||||
"key1" => "value1"
|
||||
]
|
||||
]);
|
||||
```
|
||||
|
||||
## ZMRequest::downloadFile()
|
||||
|
||||
下载文件到本地。
|
||||
|
||||
定义:`ZMRequest::downloadFile($url, $dst = null)`
|
||||
|
||||
`$url`:不多讲,下载链接。
|
||||
|
||||
`$dst`:本地位置,例如 `/tmp/hello.html`
|
||||
|
||||
下载成功返回 true 或指定的文件位置,失败返回 false。
|
||||
|
||||
```php
|
||||
ZMRequest::downloadFile("http://captive.apple.com/", "/tmp/apple.html");
|
||||
```
|
||||
|
||||
## ZMRequest::websocket()
|
||||
|
||||
创建一个 WebSocket 连接。因为 Swoole 提供的是同步协程的方案,但对于 WebSocket 这样的全双工通信,反而不是一个好的代码逻辑,炸毛框架将此同步协程的方案封装成了异步事件调用的方式。
|
||||
|
||||
定义:`ZMRequest::websocket($url, $set = ['websocket_mask' => true], $header = [])`
|
||||
|
||||
返回:一个 `\ZM\Requests\ZMWebSocket` 对象
|
||||
|
||||
效果等同于:`$s = new \ZM\Requests\ZMWebSocket($url, $set = ['websocket_mask' => true], $header = [])`
|
||||
|
||||
这个是 ZMRequest 扩展而来的异步 WebSocket 客户端,可供方便地连接、收发 WebSocket 消息所定制。
|
||||
|
||||
命名空间:`\ZM\Requests\ZMWebSocket`
|
||||
|
||||
```php
|
||||
$ws = ZMRequest::websocket("ws://127.0.0.1:12345/"); //使用工具函数
|
||||
// $ws = new ZMWebSocket("ws://127.0.0.1:12345/"); //直接构造
|
||||
if($ws->is_available) {
|
||||
$ws->onMessage(function(\Swoole\WebSocket\Frame $frame, $client) {
|
||||
var_dump($frame->data);
|
||||
});
|
||||
$ws->onClose(function($client){
|
||||
Console::info("Websocket closed.");
|
||||
});
|
||||
$result = $ws->upgrade();
|
||||
var_dump($result);
|
||||
}
|
||||
```
|
||||
|
||||
### 属性
|
||||
|
||||
#### is_available
|
||||
|
||||
`bool` 类型,用于判断构造对象是否成功或链接是否可用。在构建新的对象并执行 `upgrade()` 前,如果 ws 链接没有问题,则会变为 true;在 `onClose()` 回调执行后,此值变回 false。
|
||||
|
||||
### 方法
|
||||
|
||||
#### __construct()
|
||||
|
||||
客户端对象的构造方法。
|
||||
|
||||
参数:
|
||||
|
||||
- `$url`:要请求到的 WebSocket 目标地址,必须以 `ws(s)://` 开头
|
||||
- `$set`:可选,Swoole 客户端的参数,例如超时、是否使用 `websocket_mask` 等,如果为空数组则默认为 `["websocket_mask" => true]`,具体可设置的内容见 [Swoole 文档](https://wiki.swoole.com/#/coroutine_client/http_client?id=set)
|
||||
- `$header`:可选,请求的头部信息,数组
|
||||
|
||||
```php
|
||||
$a = new ZMWebSocket("ws://127.0.0.1:8080/", ["websocket_mask" => true], [
|
||||
"User-Agent" => "Firefox"
|
||||
]);
|
||||
```
|
||||
|
||||
#### onMessage()
|
||||
|
||||
设置收到消息的回调函数。
|
||||
|
||||
回调的参数:
|
||||
|
||||
- `$frame`:`Swoole\WebSocket\Frame` 类型,消息帧,一般只用 `$frame->data` 获取数据,具体见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)
|
||||
- `$client`:`Swoole\Coroutine\Http\Client` 类型,为客户端本身的对象,用于 push 数据等
|
||||
|
||||
```php
|
||||
$a->onMessage(function($frame, $client){
|
||||
echo "收到消息:".$frame->data.PHP_EOL;
|
||||
$client->push("hello world");
|
||||
});
|
||||
```
|
||||
|
||||
#### onClose()
|
||||
|
||||
设置连接断开后执行的回调函数。
|
||||
|
||||
回调的参数:
|
||||
|
||||
- `$client`:同上,但断开连接后不能使用 `push()` 发送数据了,只建议作为重连等机制的使用
|
||||
|
||||
```php
|
||||
$a->onClose(function($client){
|
||||
echo "WS 链接断开了!".PHP_EOL;
|
||||
});
|
||||
```
|
||||
|
||||
#### upgrade()
|
||||
|
||||
发起连接。
|
||||
|
||||
返回值:`true|false`,当为 `true` 时代表握手成功,此时可以在回调里愉快地收发消息了。如果为 `false` 表明握手失败。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
这里由于是协程转异步,所以不能确定 `upgrade()` 和 `onMessage()` 哪个先会被触发(一般情况下如果服务器不是立刻响应回包信息,总是会先返回 `upgrade()` 的结果。
|
||||
|
||||
:::
|
||||
|
||||
## 设置参数
|
||||
|
||||
见:[Swoole - HTTP 客户端](http://wiki.swoole.com/#/coroutine_client/http_client?id=set)
|
||||
@ -1,264 +0,0 @@
|
||||
# 模块打包
|
||||
|
||||
从 2.5 版本起,炸毛框架的模块源码支持了打包和分发,开发者可以通过将自己的功能编写打包,并通过互联网进行分发,供其他人使用。此外,还提供了模块包热加载(不解包直接运行)和模块包解包功能。
|
||||
|
||||
## 构建模块包配置文件
|
||||
|
||||
炸毛框架的模块区分是根据 `src` 目录下的文件夹定义的,模块包的配置文件命名必须为 `zm.json`,此外,假设我们编写了一个最简单的模块,以脚手架生成的 Example 模块为例,文件夹结构如下:
|
||||
|
||||
```
|
||||
src/
|
||||
└── Module/
|
||||
├── Example/
|
||||
│ ├── Hello.php
|
||||
│ └── zm.json
|
||||
└── Middleware/
|
||||
└── TimerMiddleware.php
|
||||
```
|
||||
|
||||
我们在 Example 目录下创建一个 `zm.json` 的文件,编写配置,即代表 `src/Module/Example/` 文件夹及里面的用户模块源码为一个模块包,也就可以被框架识别并打包处理。
|
||||
|
||||
编写的配置文件结构如下:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "my-first-module"
|
||||
}
|
||||
```
|
||||
|
||||
对!你没看错,只需要定义一个 `name` 字段,即可声明这是一个模块包!
|
||||
|
||||
### 配置文件参数
|
||||
|
||||
#### - description
|
||||
|
||||
- 类型:`string`。
|
||||
|
||||
- 含义:模块的描述。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-first-module",
|
||||
"description": "这个是一个示例模块打包教程"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### - version
|
||||
|
||||
- 类型:string。
|
||||
- 含义:模块的版本。
|
||||
|
||||
版本处理方式和 Composer 基本一致,建议使用三段式,也就是 `大版本.小版本.补丁版本`。关于三段式版本的描述和规范,见 [到底三段式版本号是什么?](https://www.chrisyue.com/what-the-hell-are-semver-and-the-difference-between-composer-version-control-sign-tilde-and-caret.html)。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-first-module",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### - depends
|
||||
|
||||
- 类型:map of string(例如 `{"foo":"bar","baz":"zoo"}`)。
|
||||
- 含义:模块的依赖关系和版本依赖声明。
|
||||
|
||||
此处用作模块的依赖检测,假设模块 `foo` 依赖模块 `bar` 的 1.x 版本但是不兼容 `bar` 的 2.x 版本,可以像 Composer 的 `require` 一样编写版本依赖:`^1.0`。也可以使用 `~`、`>=`、`*` 这些与 Composer 包管理相同逻辑的版本依赖关系,详见 [Composer - 包版本](https://docs.phpcomposer.com/01-basic-usage.html#Package-Versions)。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"depends": {
|
||||
"bar": "^1.0",
|
||||
"bsr": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### - light-cache-store
|
||||
|
||||
- 类型:array of string(例如 `["foo","bar"]`)。
|
||||
- 含义:打包模块时要储存的持久化 LightCache 键名列表。
|
||||
|
||||
这里需要配合 LightCache 使用,如果你有一些需要全局缓存的数据,例如动态配置项,比如群服务状态列表,可以先使用 LightCache 存储并使用 `addPersistence()` 持久化,此后在使用模块打包时编写此配置项。
|
||||
|
||||
我们假设在项目模块中使用到了 `group-status` 这一个 LightCache,那么只需要写 `light-cache-store` 配置项,在模块打包时就会将持久化的数据也打包到 phar 模块包内。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"light-cache-store": [
|
||||
"group-status"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### - global-config-override
|
||||
|
||||
- 类型:string | false。
|
||||
- 含义:解包时是否需要手动编辑全局配置(`global.php`)。
|
||||
|
||||
这里如果写 string 类型的,那么就是相当于在解包时会提示此处的内容,内容推荐填写要求解包模块用户需要编辑的项目,比如 「请将 static_file_server 的 status 改为 true,以便使用静态文本功能」。
|
||||
|
||||
如果是 false,那么和不指定此参数效果是一样的,无需用户修改 global.php。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"global-config-override": "请将 static_file_server 的 status 改为 true"
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
#### - allow-hotload
|
||||
|
||||
- 类型:bool。
|
||||
- 含义:是否允许用户无需解压直接加载模块包文件(phar)。
|
||||
|
||||
当此项为 true 时,可以将模块包直接放入 `zm_data/modules` 文件夹下,然后将 `global.php` 中的 `module_loader` 项中的 `enable_hotload` 改为 true,启动框架即可加载。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"allow-hotload": true
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
::: warning 注意
|
||||
|
||||
如果使用允许热加载,那么模块包中的配置最好不要有 `global-config-override` 和 `light-cache-store`,以此来达到最正确的效果,一般热加载更适合 Library(库)类型的模块。
|
||||
|
||||
:::
|
||||
|
||||
#### - zm-data-store
|
||||
|
||||
- 类型:array of string(例如 `["foo","bar"]`)。
|
||||
- 含义:打包时要添加到模块包内的 `zm_data` 目录下的子目录或文件。
|
||||
|
||||
其中项目必须是相对路径,不能是绝对路径,且必须是在配置项 `zm_data` 指定的目录(默认会在框架项目的根目录下的 `zm_data/` 目录。
|
||||
|
||||
我们假设要打包一个 `{zm_data 目录}/config/` 目录及其目录下的文件,和一个 `main.png` 文件,下方是实例。
|
||||
|
||||
::: tip 编写实例
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"zm-data-store": [
|
||||
"config/",
|
||||
"main.png"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
在打包时框架会自动添加这些文件到 phar 插件包内,到解包时,会自动将这些文件释放到对应框架的 `zm_data` 目录下。
|
||||
|
||||
## 打包模块命令
|
||||
|
||||
编写配置文件 `zm.json` 后,就可以被框架正常识别为模块形式,你也可以使用对无需打包的模块进行配置以进行分类管理。
|
||||
|
||||
### module:list
|
||||
|
||||
使用 list 命令可以列出炸毛框架检测到配置文件或打包好的模块。
|
||||
|
||||
```
|
||||
$ ./zhamao module:list
|
||||
[foo]
|
||||
类型: source
|
||||
版本: 1.0.0
|
||||
描述: 示例模块打包文件
|
||||
目录: src/Module/Example
|
||||
没有发现已打包且装载的模块!
|
||||
```
|
||||
|
||||
其中 `[ ]` 内为识别出来的模块名称,由上方用户编写的 `zm.json` 定义,类型为 `source` 是源码形式,也就是指定了 `zm.json` 形式的模块,目录为模块所在子目录。
|
||||
|
||||
我们假设打包上方定义的 `foo` 模块,使用下方命令 `module:pack` 即可。
|
||||
|
||||
### module:pack
|
||||
|
||||
使用 pack 命令可以将配置好的模块打包为 `xxx.phar` 文件并转移或发布给他人。
|
||||
|
||||
我们假设打包模块脚手架的默认模块 `src/Module/Example` 下面的模块源码和附带一个 `zm_data` 目录下的文件(我们就随便打包一下 Swoole 的输出日志吧)。`zm.json` 文件内容如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "示例模块打包文件",
|
||||
"version": "1.0.0",
|
||||
"allow-hotload": true,
|
||||
"zm-data-store": [
|
||||
"crash/swoole_error.log"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
然后输入命令:
|
||||
|
||||
```
|
||||
$ ./zhamao module:pack foo
|
||||
[15:07:11] [I] 模块输出文件:/root/zhamao-framework/zm_data/output/foo_1.0.0.phar
|
||||
[15:07:11] [S] 打包完成!
|
||||
```
|
||||
|
||||
如果提示文件夹不存在,请先手动创建文件夹:`mkdir /path/to/your/zm_data/output`
|
||||
|
||||
在打包后,你将获得一个 `foo_1.0.0.phar` 的文件。
|
||||
|
||||
> 如果你没有在 `zm.json` 中指定 `version`,那么输出的 phar 文件是不会带版本号的。
|
||||
|
||||
打包后的 phar 内将包含:
|
||||
|
||||
- Hello.php
|
||||
- zm.json
|
||||
- crash/swoole_error.log
|
||||
- 必要的框架热加载以及解包需要的配置信息
|
||||
|
||||
## 打包命令
|
||||
|
||||
```bash
|
||||
# ./zhamao 和原先的 vendor/bin/start 是完全一致的
|
||||
./zhamao module:pack <module-name>
|
||||
```
|
||||
|
||||
例如,打包上面的名叫 foo 的模块:`./zhamao module:pack foo`。
|
||||
|
||||
打包命令执行后,将会在 `zm_data` 下的 `output` 目录输出一个 phar 文件。如果你指定了 `version` 参数,那么文件名将会是 `${name}_${version}.phar`,如果没有指定版本,那么只会有 `${name}.phar`,同时如果文件已经存在,将覆盖写入。
|
||||
|
||||
## 查看模块信息命令
|
||||
|
||||
```bash
|
||||
./zhamao module:list
|
||||
```
|
||||
|
||||
通过此命令可以查看模块相关的信息,如未打包但已配置的模块信息等。
|
||||
@ -1,84 +0,0 @@
|
||||
# 模块解包
|
||||
|
||||
从 2.5 版本起,炸毛框架的模块源码支持了打包和分发,分发后必不可少的一步就是将其解包。
|
||||
|
||||
解包过程大致为:
|
||||
|
||||
1. 检查模块的配置文件是否正常。
|
||||
2. 检查模块的依赖问题,如果有依赖但未安装,则抛出异常。
|
||||
3. 检查 LightCache 轻量缓存是否需要写入。
|
||||
4. 检查 `zm_data` 是否有需要存入的数据。
|
||||
5. 合并 `composer.json` 文件。
|
||||
6. 拷贝 `zm_data` 相关的文件。
|
||||
7. 写入 LightCache 相关数据。
|
||||
8. 提示用户手动合并 `global.php` 全局配置文件。
|
||||
9. 拷贝模块 PHP 源文件。
|
||||
|
||||
## 解包命令
|
||||
|
||||
```bash
|
||||
./zhamao module:unpack <module-name>
|
||||
```
|
||||
|
||||
首先将待解包的 phar 文件放入 `zm_data` 目录下的 `modules` 文件夹(如果不存在需要手动创建),如果你手动修改过 `global.php` 下面的 `module_loader.load_path` 项,需要放入对应的目录。
|
||||
|
||||
放入后,结构如下:
|
||||
|
||||
```
|
||||
zm_data/
|
||||
zm_data/modules/
|
||||
zm_data/modules/foo.phar
|
||||
```
|
||||
|
||||
接下来,需要知道模块的名称。当然一般情况下,phar 的名称可以获取到模块的实际名称,如 `foo`,但最好用 `./zhamao module:list` 列出模块的信息来获取真实的模块名称。
|
||||
|
||||
```
|
||||
./zhamao module:list
|
||||
# 下面是输出
|
||||
[foo]
|
||||
类型: 模块包(phar)
|
||||
位置: zm_data/modules/我是假的名字.phar
|
||||
```
|
||||
|
||||
解包过程十分简单,只需要执行一次命令即可。
|
||||
|
||||
```
|
||||
./zhamao module:unpack foo
|
||||
# 下面是输出
|
||||
[10:05:40] [I] Releasing source file: src/Module/Example/Hello.php
|
||||
[10:05:40] [I] Releasing source file: src/Module/Example/zm.json
|
||||
[10:05:40] [S] 解压完成!
|
||||
```
|
||||
|
||||
### 命令参数
|
||||
|
||||
在解包时会遇到各种复杂的情况,如源码文件已存在、数据已存在、依赖问题等,通过增加参数可以控制解包时的行为。
|
||||
|
||||
#### --overwrite-light-cache
|
||||
|
||||
含义:覆盖现有的 LightCache 键值(如果存在的话)。
|
||||
|
||||
#### --overwrite-zm-data
|
||||
|
||||
含义:覆盖现有的 `zm_data` 下的文件(如果存在的话)。
|
||||
|
||||
#### --overwrite-source
|
||||
|
||||
含义:覆盖现有的 PHP 模块源文件(如果存在的话)。
|
||||
|
||||
#### --ignore-depends
|
||||
|
||||
含义:解包时忽略检查依赖。
|
||||
|
||||
### 常见问题
|
||||
|
||||
如果你解包的模块包要求修改 `global.php`,则会出现类似这样的提示:
|
||||
|
||||
```
|
||||
# ./zhamao module:unpack foo
|
||||
[14:47:39] [W] 模块作者要求用户手动修改 global.php 配置文件中的项目:
|
||||
[14:47:39] [W] *请把全局配置文件的light_cache项目中max_strlen选项调整为至少65536
|
||||
请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N]
|
||||
```
|
||||
|
||||
一般这种情况,根据第二条提示(第二条提示为打包时填入的 `global-config-override`)。如果输入 y,则会自动执行命令 `vim config/global.php`,如果输入的是 e,则会等待你手动修改完成文件,最后按回车完成修改。默认情况直接回车的话,会跳过此步骤,如果模块要求了修改但跳过修改,安装后可能会有功能缺失等问题。
|
||||
@ -1,67 +0,0 @@
|
||||
# ZMAtomic 原子计数器
|
||||
|
||||
原子计数器是用于多进程间跨进程使用的原子计数使用的,比如统计入站请求数量等。此功能基于 Swoole 的 Atomic,详情见 [Swoole - 文档]([进程间无锁计数器(Atomic) (swoole.com)](http://wiki.swoole.com/#/memory/atomic))。
|
||||
|
||||
## 配置和初始化
|
||||
|
||||
见配置文件:`config/global.php` 中的 `init_atomics` 字段:
|
||||
|
||||
```php
|
||||
/** zhamao-framework在框架启动时初始化的atomic们 */
|
||||
$config['init_atomics'] = [
|
||||
'foo' => 0,
|
||||
'bar' => 4,
|
||||
];
|
||||
```
|
||||
|
||||
这时我们就成功初始化两个原子计数器,名字分别为 `foo` 和 `bar`。
|
||||
|
||||
::: warning 注意
|
||||
|
||||
初始化的值必须是不小于 0 的 int32 值!
|
||||
|
||||
:::
|
||||
|
||||
## 使用
|
||||
|
||||
定义和命名空间:`ZM\Store\ZMAtomic`
|
||||
|
||||
连接计数示例:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Store\ZMAtomic;
|
||||
class Hello {
|
||||
/**
|
||||
* @OnRequestEvent()
|
||||
*/
|
||||
public function onRequest() {
|
||||
$cnt = ZMAtomic::get("foo")->add(1);
|
||||
ctx()->getResponse()->end("当前已访问:".$cnt."次");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ZMAtomic::get()->get()
|
||||
|
||||
获取计数的数字:`dump(ZMAtomic::get("bar")->get());` 返回 4。
|
||||
|
||||
### ZMAtomic::get()->add($num)
|
||||
|
||||
加上一定的数并返回结果:`dump(ZMAtomic::get("bar")->add(5));` 返回 9。
|
||||
|
||||
### ZMAtomic::get()->sub($num)
|
||||
|
||||
要减少的数值(必须为正整数):`dump(ZMAtomic::get("bar")->sub(5));` 返回 5。
|
||||
|
||||
### ZMAtomic::get()->set($num)
|
||||
|
||||
设置计数的数字:`ZMAtomic::get("bar")->set(77);`
|
||||
|
||||
::: tip 提示
|
||||
|
||||
还有一些不常用的方法,可以看 Swoole 官方的文档,这里就不一一列举了。
|
||||
|
||||
:::
|
||||
@ -1,108 +0,0 @@
|
||||
# 存储管理(文件)
|
||||
|
||||
DataProvider 是框架内提供的一个简易的文件管理类。
|
||||
|
||||
定义:`\ZM\Utils\DataProvider`
|
||||
|
||||
## DataProvider::getWorkingDir()
|
||||
|
||||
同 `working_dir()`。
|
||||
|
||||
## DataProvider::getSourceRootDir()
|
||||
|
||||
获取用户的源码根目录,除 Phar 模式外与 `getWorkingDir()` 相同。
|
||||
|
||||
## DataProvider::getFrameworkLink()
|
||||
|
||||
同 `ZMConfig::get("global", "http_reverse_link")`,获取反向代理的链接。
|
||||
|
||||
## DataProvider::getDataFolder()
|
||||
|
||||
获取配置项 `zm_data` 指定的目录。
|
||||
|
||||
如果指定参数 `$second`,则返回二级目录地址,如果二级目录不存在则自动创建。
|
||||
|
||||
```php
|
||||
DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framework/zm_data/TestModule/
|
||||
```
|
||||
|
||||
## DataProvider::getFrameworkRootDir()
|
||||
|
||||
返回框架本体的根目录。
|
||||
|
||||
## DataProvider::saveToJson()
|
||||
|
||||
将变量内容保存为 json 格式的文件,存储在 `zm_data/config/` 目录下或子目录下。
|
||||
|
||||
定义:`saveToJson($filename, $file_array)`
|
||||
|
||||
`$filename` 是文件名,不需要加后缀,比如你想保存成 `foo/bar.json`,这里写 `foo/bar` 就好。如果不想要二级目录,就直接写 `bar`,不需要加 `.json` 后缀。
|
||||
|
||||
这里只支持二级目录,不支持更多级的子目录。
|
||||
|
||||
`$file_array` 为内容,一般是数组,比如你缓存了一个 API 接口返回的数据,然后直接解析成数组后丢给它就好了。
|
||||
|
||||
## DataProvider::loadFromJson()
|
||||
|
||||
从 json 文件加载内容至变量。
|
||||
|
||||
定义:`loadFromJson($filename)`
|
||||
|
||||
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
|
||||
|
||||
## DataProvider::scanDirFiles()
|
||||
|
||||
递归或非递归扫描目录,返回相对目录的文件列表或绝对目录的文件列表。(非常好用)
|
||||
|
||||
定义:`scanDirFiles($dir, $recursive = true, $relative = false)`
|
||||
|
||||
`$dir` 为要扫描的目录,`$recursive` 为是否递归,`$relative` 为是否返回相对目录的文件列表。
|
||||
|
||||
从给定的目录下开始遍历整个目录,如果将 `$recursive` 设置为 `true`,则会递归扫描子目录,否则将返回包含目录的文件列表。
|
||||
|
||||
如果将 `$relative` 设置为 `true`,则会返回文件列表的相对路径,否则返回绝对路径。
|
||||
|
||||
例如:假设目录 `/home/test/` 下有两个文件:`test1.txt` 和 `testdir/test2.txt`:如果将 `$recursive` 设置为 `true`,`$relative` 设置为 `false`,则返回的文件列表为:
|
||||
|
||||
```json
|
||||
[
|
||||
"/home/test/test1.txt",
|
||||
"home/test/testdir/test2.txt"
|
||||
]
|
||||
```
|
||||
|
||||
相同条件下,如果将 `$relative` 设置为 `true`:
|
||||
|
||||
```json
|
||||
[
|
||||
"test1.txt",
|
||||
"testdir/test2.txt"
|
||||
]
|
||||
```
|
||||
|
||||
如果再把 `$recursive` 设置为 `false`:
|
||||
|
||||
```json
|
||||
[
|
||||
"test1.txt",
|
||||
"testdir"
|
||||
]
|
||||
```
|
||||
|
||||
## 其他文件读取
|
||||
|
||||
框架比较贴近原生的 PHP,所以推荐直接使用原生的方法来读写文件(`file_get_contents` 和 `file_put_contents`)。但有一点要注意,框架内最好使用**工作目录或者绝对路径**。
|
||||
|
||||
```php
|
||||
// 读取框架工作目录的文件 composer.json 文件
|
||||
$r = file_get_contents(working_dir() . "/composer.json");
|
||||
|
||||
// 写入 Linux 临时目录下的文件
|
||||
file_put_contents("/tmp/test.txt", "hello world");
|
||||
```
|
||||
|
||||
::: warning 注意
|
||||
|
||||
在默认的情况里,框架的根目录均为可写可读的,在读写文件时务必要注意目录的位置和权限。使用 `working_dir()` 获取目录后面需要加 `/` 再追加自己的文件名或子目录名。
|
||||
|
||||
:::
|
||||
@ -1,353 +0,0 @@
|
||||
# LightCache 轻量缓存
|
||||
|
||||
在炸毛框架 1.x 时代,框架里有非常方便使用的 ZMBuf 缓存,但是由于 2.x 版本框架加入了多进程模式,所以不能再以传统的存到全局变量的方式来构建和管理缓存了,LightCache 就是替代方案。LightCache 依旧是 key-value 键值对形式的存储,支持多种类型的变量。
|
||||
|
||||
定义:`ZM\Store\LightCache`。
|
||||
|
||||
## 与 ZMBuf 的不同
|
||||
|
||||
从存储内容角度,LightCache 存入的是 Swoole 初始化的共享内存,基于 Swoole/Table 编写。优势在于多进程下的性能极佳,而且没有数据同步问题;劣势在于它需要在启动框架前就声明总大小,不能根据存储数据的大小来划定,需提前指定最大能存储的容量。而 ZMBuf 基于直接把变量存到静态成员中 `public static $data` 类似这样,且 1.x 框架基于单进程单线程,无任何数据同步的问题。
|
||||
|
||||
总之来说,LightCache 是让用户在涉及多进程编程时,一个折中的解决方案,提出和解决了很多多进程开发时存储数据遇到的问题:数据同步、进程间通信效率、数据是否需要上锁等。
|
||||
|
||||
- 数据同步:多进程下因为是固定的内存大小区域,所以每个进程读取和写入都是只有一份数据的,不存在数据不同步的问题。
|
||||
- 进程间通信:因为多个进程共享一片区域的内存,所以不需要进程间通信,无协程切换。
|
||||
- 镀锡是否需要上锁:看情况。一般情况下 Swoole/Table 模块自带一个行锁,只有两个进程在两个 CPU 上同时读取一行数据时才会发生抢锁,作为框架的使用者,如果只写或只读,是无需手动上任何锁的。只有在先 `get()` 再 `set()` 这样的情况才需要上自旋锁。后面的段会详细讲述。
|
||||
|
||||
使用体验上,基本和 ZMBuf 无差,如果没有用过 1.x 的版本,可无视此段话。
|
||||
|
||||
## 使用
|
||||
|
||||
### 配置和初始化
|
||||
|
||||
配置文件还是在 `config/global.php` 文件里,字段是 `light_cache`。
|
||||
|
||||
```php
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
'size' => 512, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, //单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => $config['zm_data'].'_cache.json',
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
```
|
||||
|
||||
其中 `$size` 是最多保存的键值对数目,填写非 2 的倍数时底层会自动修正为 2 的倍数值。
|
||||
|
||||
`$max_strlen` 为单条值最长保存的长度。因为 Swoole/Table 只能存数字、字符串,所以在存取数组等变量时会先将其序列化为字符串形式保存,get 时自动反序列化回来。在存数组等非字符串变量时,请先自行计算你要存取的内容序列化后的最大长度。如果长度超出最大长度,则无法保存,`set()` 将返回 false。
|
||||
|
||||
`hash_conflict_proportion`:Table 模块底层使用 hash 表,会存在 hash 冲突,调大 Hash 冲突率会提升 `size` 指定条目数的准确性,但也将增加物理内存的使用。这里单位是百分比,`0.6` 为 `60%`。
|
||||
|
||||
`persistence_path` 是持久化保存变量的文件保存位置,默认在 `zm_data/_cache.json` 文件。
|
||||
|
||||
`auto_save_interval` 是持久化保存变量的自动保存时间,单位秒。
|
||||
|
||||
### LightCache::set()
|
||||
|
||||
设置内容。
|
||||
|
||||
定义:`LightCache::set($key, $value, $expire = -1)`
|
||||
|
||||
返回值:`bool`。当 value 超出了最大长度或内存不足时,返回 false,其余 true。
|
||||
|
||||
参数:
|
||||
|
||||
`$key` 的长度不能超过 64 字节,且不能存入二进制内容。
|
||||
|
||||
`$value` 可存入 `bool`、`string`、`int`、`array` 等可被 `json_encode()` 的变量,闭包函数和对象不可存入。
|
||||
|
||||
`$expire` 是 `int`,超时时间(秒)。如果设定了大于 0 的值,则表明是在 `$expire` 秒后自动删除(框架中途停止不受影响)。如果为 -1 则什么都不做。框架停止后自动被清除。
|
||||
|
||||
**注意:如果前面使用了 set() ,后面再次使用 set() 会重置 expire 过期时间为 -1(-1 是框架运行时不过期,关闭框架删除的状态),如果只需要更新值,请使用 update()。**
|
||||
|
||||
```php
|
||||
// use ZM\Store\LightCache;
|
||||
/**
|
||||
* @CQCommand("store")
|
||||
*/
|
||||
public function store() {
|
||||
LightCache::set("key1", ["value1" => "strOrInt", "value2" => 123]);
|
||||
return "OK!";
|
||||
}
|
||||
/**
|
||||
* @CQCommand("storeAfterRemove")
|
||||
*/
|
||||
public function storeAfterRemove() {
|
||||
LightCache::set("store1", "remove1", 30);
|
||||
ctx()->reply(LightCache::get("store1") !== null ? "内容存在!" : "内容不存在!");
|
||||
zm_sleep(30);
|
||||
ctx()->reply(LightCache::get("store1") !== null ? "内容存在!" : "内容不存在!");
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'store'},
|
||||
{type:1,content:'OK!'},
|
||||
{type:0,content:'storeAfterRemove'},
|
||||
{type:1,content:'内容存在!'},
|
||||
{type:2,content:'等待 30 秒'},
|
||||
{type:1,content:'内容不存在!'},
|
||||
]"></chat-box>
|
||||
|
||||
### LightCache::update()
|
||||
|
||||
更新值而不更新状态。如果键值对不存在,则返回 false。
|
||||
|
||||
定义:`LightCache::update(string $key, $value)`
|
||||
|
||||
参数同 `set()`,可参考。
|
||||
|
||||
### LightCache::get()
|
||||
|
||||
获取内容。
|
||||
|
||||
返回值:`mixed|null`。当无内容或过期时返回 null,剩余情况返回原数据。
|
||||
|
||||
### LightCache::getExpire()
|
||||
|
||||
获取存储项剩余过期时间(秒)。
|
||||
|
||||
定义:`LightCache::getExpire(string $key)`
|
||||
|
||||
```php
|
||||
$s = LightCache::set("test", "hello", 20);
|
||||
zm_sleep(10);
|
||||
dump(LightCache::getExpire("test")); // 返回 10
|
||||
```
|
||||
|
||||
### LightCache::getExpireTS()
|
||||
|
||||
获取存储项要过期的时间戳。(2.4.3 起可用)
|
||||
|
||||
定义:`LightCache::getExpireTS(string $key)`
|
||||
|
||||
```php
|
||||
$s = LightCache::set("test", "hello", 20); //假设这条代码执行时时间戳是 1616838482
|
||||
zm_sleep(10);
|
||||
dump(LightCache::getExpireTS("test")); // 返回 1616838502
|
||||
zm_sleep(10);
|
||||
dump(LightCache::getExpireTS("test")); // 返回 null
|
||||
```
|
||||
|
||||
### LightCache::getMemoryUsage()
|
||||
|
||||
获取轻量缓存使用的总空间大小(字节)
|
||||
|
||||
```php
|
||||
LightCache::getMemoryUsage();
|
||||
```
|
||||
|
||||
轻量缓存的内存手工计算方式:(Table 结构体长度` + `KEY 长度 64 字节 + `$size`) * (1 + `$conflict_proportion`) * 列尺寸。
|
||||
|
||||
Table 结构体长度根据你所设定的 `max_strlen` 会变化。
|
||||
|
||||
> 框架默认配置下的轻量缓存启动后大约占用内存 25MB 左右。
|
||||
|
||||
### LightCache::isset()
|
||||
|
||||
判断某项是否存在。
|
||||
|
||||
```php
|
||||
LightCache::set("foo", "bar");
|
||||
dump(LightCache::isset("foo")); // true
|
||||
```
|
||||
|
||||
### LightCache::unset()
|
||||
|
||||
删除某项。
|
||||
|
||||
```php
|
||||
LightCache::set("foo", "bar");
|
||||
LightCache::unset("foo");
|
||||
dump(LightCache::isset("foo")); // false
|
||||
```
|
||||
|
||||
### LightCache::getAll()
|
||||
|
||||
获取所有项。
|
||||
|
||||
```php
|
||||
LightCache::set("k1", ["I", "am", "array"]);
|
||||
LightCache::set("k2", "v2");
|
||||
LightCache::set("k3", 20001);
|
||||
dump(LightCache::getAll());
|
||||
/*
|
||||
{
|
||||
"k1": ["I", "am", "array"],
|
||||
"k2": "v2",
|
||||
"k3": 20001
|
||||
}
|
||||
*/
|
||||
```
|
||||
|
||||
### LightCache::addPersistence()
|
||||
|
||||
添加持久化存储的键。
|
||||
|
||||
用法:`LightCache::addPersistence($key)`。
|
||||
|
||||
注:只需调用一次即可,无需多次重复调用,也不需要设置 expire 为 -2 了。(2.4.2 起可用此方法)。
|
||||
|
||||
详见下方 **持久化**。
|
||||
|
||||
### LightCache::removePersistence()
|
||||
|
||||
删除持久化的键。
|
||||
|
||||
用法:`LightCache::removePersistence($key)`。
|
||||
|
||||
注:只需调用一次即可,无需多次重复调用,也不需要设置 expire 为非 -2 了。(2.4.2 起可用此方法)。
|
||||
|
||||
### 持久化
|
||||
|
||||
使用 `LightCache::addPersistence($key)` 添加对应需要持久化的键名即可。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @OnStart()
|
||||
*/
|
||||
public function onStart() {
|
||||
LightCache::addPersistence("msg_time");
|
||||
}
|
||||
/**
|
||||
* @CQCommand("getStore")
|
||||
*/
|
||||
public function getStore() {
|
||||
return "存储时间:".date("Y-m-d H:i:s", LightCache::get("msg_time"));
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:2,content:'我在 2021-01-05 15:21:00 发送这条消息'},
|
||||
{type:0,content:'getStore'},
|
||||
{type:1,content:'2021-01-05 15:20:00'},
|
||||
{type:2,content:'这时我用 Ctrl+C 停止框架,过一会儿再启动'},
|
||||
{type:0,content:'getStore'},
|
||||
{type:1,content:'存储时间:2021-01-05 15:20:00'},
|
||||
]"></chat-box>
|
||||
|
||||
### 数据加锁
|
||||
|
||||
在特定情况下,使用 LightCache 必须配合锁使用,否则会出现数据错乱。我们来看下面的例子:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
*/
|
||||
public function test() {
|
||||
$s = LightCache::get("web_count");
|
||||
if($s === null) $s = 1;
|
||||
else $s += 1;
|
||||
LightCache::set("web_count", $s);
|
||||
return "<h1>It works!</h1>";
|
||||
}
|
||||
```
|
||||
|
||||
我们使用压测工具,例如 `ab`,对此路由接口开很多很多线程进行测试,假设我们设置请求总数为 200000 次,框架的工作进程数为 8(我用的是 2020 年末的 i5 MacBook Pro 13 inch)。
|
||||
|
||||
> 懒得再测了,下面就口述过程吧。
|
||||
|
||||
在运行完测试后,通过 `LightCache::get("web_count")`,获取到的数你会发现不是 200000。怎么回事呢?请自行翻阅多进程开发相关的书籍哦!(或者简单理解为,有一些情况下,进程 1 执行到了 `if-else` 语句,另一个进程也执行到了这里,两次在代码层面加的数是相同的,则虽然请求了两次,但是后执行 set 的那个进程又覆盖了前一个进程执行的值,导致最终结果加了 1 而不是 2)
|
||||
|
||||
::: tip 提示
|
||||
|
||||
同样的场景,使用 ZMAtomic 就不需要使用锁了。Atomic 是一句话:`add(1)` 立即加值的。而 LightCache 需要加锁的情况一般都是 `get->改值->set` 这样的代码。
|
||||
|
||||
:::
|
||||
|
||||
解决这一问题,就需要用到锁。这种情况下,我们首先考虑的是自旋锁,框架也因此内置了一个方便使用的自旋锁组件。详见下一章:自旋锁。
|
||||
|
||||
## 如何临时缓存大变量
|
||||
|
||||
由于 LightCache 需要提前声明最大大小,所以在某些情况下,比如第三方 API 接口结果临时缓存,可能不太适合使用,这时对于 2.x 版本的多进程炸毛框架是一个新的问题。
|
||||
|
||||
解决方案有三种:
|
||||
|
||||
- 将 `global.php` 中的 `swoole.worker_num` 调整为 `1` 即可,所有除所有主 handler 事件的用户类外其他类均可使用如 `Hello::$store` 类似的静态变量全局存取
|
||||
- 使用 WorkerCache(需要 2.2.0 以上版本)
|
||||
- 使用 Redis(需要安装 `redis` 扩展)
|
||||
|
||||
以上,WorkerCache 是为了弥补 LightCache 的不足而诞生的,以下就是 WorkerCache 的具体内容。
|
||||
|
||||
### WorkerCache 跨进程大缓存
|
||||
|
||||
WorkerCache 和 LightCache 几乎完全不同,WorkerCache 存储的方式说白了就是 PHP 的静态变量,不过框架支持使用封装好的进程间通信进行跨进程读取。但由于需要设置一个存储变量的进程,所以配置文件必须先指定要将数据存到哪个 Worker/TaskWorker 进程中。关于框架内多进程的说明,请见 [进阶 - 多进程 Hack](/advanced/multi-process)。
|
||||
|
||||
定义:`ZM\Store\WorkerCache`。
|
||||
|
||||
#### 配置
|
||||
|
||||
见 [基本配置](/guide/basic-config)。
|
||||
|
||||
#### WorkerCache::get()
|
||||
|
||||
定义:`get($key)`。
|
||||
|
||||
`$key` 为指定要获取的键值对的值,如果不存在则返回 null。
|
||||
|
||||
#### WorkerCache::set()
|
||||
|
||||
定义:`set($key, $value, $async = false)`。
|
||||
|
||||
设置变量,你懂的。
|
||||
|
||||
注意,`$value` 可以是被无损 `json_encode` 和 `json_decode` 的变量,闭包(Closure)、资源(resource)等类型不支持存储。
|
||||
|
||||
`$async` 默认为 false,当为 true 时候,不会返回是否成功设置与否,否则会协程等待是否目标进程存储成功。
|
||||
|
||||
#### WorkerCache::unset()
|
||||
|
||||
定义:`unset($key, $async = false)`
|
||||
|
||||
删除键对应的值。`$async` 的意义同上。
|
||||
|
||||
#### WorkerCache::add()
|
||||
|
||||
定义:`add($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值加一,如果值不存在,则默认为 0 且加上目标的 `$value`。
|
||||
|
||||
#### WorkerCache::sub()
|
||||
|
||||
定义:`sub($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值减一,如果值不存在,则默认为 0 且减去目标的 `$value`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand("set_store")
|
||||
*/
|
||||
public function setStorage() {
|
||||
$arg1 = ctx()->getNextArg("请输入要设置的内容名称");
|
||||
$arg2 = ctx()->getFullArg("请输入要设置的内容");
|
||||
WorkerCache::set($arg1, $arg2);
|
||||
return "成功!";
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand("get_store")
|
||||
*/
|
||||
public function getStorage() {
|
||||
$arg1 = ctx()->getFullArg("请输入要获取的内容名称");
|
||||
$data = WorkerCache::get($arg1);
|
||||
return $data ?? "内容不存在!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'set_store hello world'},
|
||||
{type:1,content:'成功!'},
|
||||
{type:0,content:'get_store hello'},
|
||||
{type:1,content:'world'},
|
||||
{type:0,content:'get_store foo'},
|
||||
{type:1,content:'内容不存在!'},
|
||||
]"></chat-box>
|
||||
@ -1,101 +0,0 @@
|
||||
# MySQL 数据库(旧版组件)
|
||||
|
||||
::: warning 注意
|
||||
|
||||
此 MySQL 组件为旧版 MySQL 查询器组件,为了统一和提升对未来独立组件的兼容性,现转变为使用 `doctrine/dbal` 和 `doctrine/orm` 库来实现查询器,请转到 [MySQL 查询器]()。
|
||||
|
||||
:::
|
||||
|
||||
## 配置
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
|
||||
|
||||
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类。
|
||||
|
||||
## 查询构造器
|
||||
|
||||
在 炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串。
|
||||
|
||||
### 新增数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
|
||||
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
|
||||
```
|
||||
|
||||
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name`,`password` 两列。
|
||||
|
||||
> 自增 ID 插入 0 即可。
|
||||
|
||||
### 删除数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->delete()->where("name", "admin_name")->save();
|
||||
// DELETE FROM admin WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=`,`!=`,`LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`。
|
||||
|
||||
### 更新数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
|
||||
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`。
|
||||
|
||||
### 查询数据
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
|
||||
// SELECT name FROM admin WHERE name = 'fake_admin'
|
||||
echo $r["name"];
|
||||
echo DB::table("admin")->where("name", "fake_admin")->value("name");
|
||||
// SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
|
||||
|
||||
其中 `fetchFirst()` 获取第一行数据。
|
||||
|
||||
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
|
||||
|
||||
多列数据获取需要使用 `fetchAll()`
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select()->fetchAll();
|
||||
// SELECT * FROM admin WHERE 1
|
||||
foreach($r as $k => $v) {
|
||||
echo $v["name"].PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### 查询条数
|
||||
|
||||
```php
|
||||
DB::table("admin")->where("name", "fake_admin")->count();
|
||||
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
## 直接执行 SQL
|
||||
|
||||
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
|
||||
|
||||
### DB::rawQuery()
|
||||
|
||||
- 用途:直接执行模板查询的裸 SQL 语句。
|
||||
- 参数:`$line`,`$params`
|
||||
- 返回:查到的行的数组
|
||||
|
||||
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
|
||||
|
||||
```php
|
||||
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
|
||||
//SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
echo $r[0]["password"];
|
||||
```
|
||||
|
||||
> 参数查询已经从根本上杜绝了 SQL 注入的问题。
|
||||
@ -1,3 +0,0 @@
|
||||
# MySQLStatement
|
||||
|
||||
你好啊,这里是 Statement。TODO
|
||||
@ -1,208 +0,0 @@
|
||||
# MySQL 数据库
|
||||
|
||||
## 简介
|
||||
|
||||
炸毛框架的数据库组件对接了 MySQL 连接池,在使用过程中无需配置即可实现 MySQL 查询,同时拥有高并发。
|
||||
|
||||
目前 2.5 版本后炸毛框架底层采用了 `doctrine/dbal` 组件,可以方便地构建 SQL 语句。
|
||||
|
||||
本章大体查询内容均以下表 `users` 为基础:
|
||||
|
||||
| id | username | gender | update_time |
|
||||
| -- | -------- | ------ | ----------- |
|
||||
| 1 | jack | man | 2021-10-12 |
|
||||
| 2 | rose | woman | 2021-10-11 |
|
||||
|
||||
## 配置
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `mysql_config` 段,见 [全局配置](../../../../guide/basic-config#mysql_config)。
|
||||
|
||||
如果 `mysql_config.host` 字段为空,则不创建数据库连接池,填写后将创建,且默认保持长连接。
|
||||
|
||||
## 执行 SQL 语句
|
||||
|
||||
在一开始,无论你做什么数据库操作,均需要获取一个 `\ZM\MySQL\MySQLWrapper` 作为你的操作对象。
|
||||
|
||||
```php
|
||||
/** @var \ZM\MySQL\MySQLWrapper $wrapper */
|
||||
$wrapper = \ZM\MySQL\MySQLManager::getWrapper();
|
||||
```
|
||||
|
||||
::: tip 提示
|
||||
|
||||
这部分内容部分直接取自 [DBAL - Data Retrieval And Manipulation](https://www.doctrine-project.org/projects/doctrine-dbal/en/2.13/reference/data-retrieval-and-manipulation.html) 原文并直接翻译,如有实际不同请提交 Issue 反馈。
|
||||
|
||||
:::
|
||||
|
||||
### 执行预处理 SQL 语句
|
||||
|
||||
预处理查询很巧妙地解决了 SQL 注入问题,并且可以方便地绑定参数进行查询。
|
||||
|
||||
预处理一般是指使用 `?` 占位符或 `:xxx` 命名标签进行参数留空,先处理 SQL 语句再填入数据。
|
||||
|
||||
一般 `?` 具有前后位置性,例如如下的查询:
|
||||
|
||||
```php
|
||||
$sql = "SELECT * FROM users WHERE id = ? AND username = ?";
|
||||
$stmt = $wrapper->getConnection()->prepare($sql);
|
||||
$stmt->bindValue(1, "1");
|
||||
$stmt->bindValue(2, "jack");
|
||||
$resultSet = $stmt->executeQuery();
|
||||
```
|
||||
|
||||
其中 `$resultSet` 与 `Statement` 方法相似,此处的对象可能是 [数据库语句对象](./mysql-statement) 或 数据库结果对象(结果对象与语句对象的 `fetchXXX()` 部分一致)。
|
||||
|
||||
这里也可以使用命名标签,使用标签可以给相同参数处使用同一个标签:
|
||||
|
||||
```php
|
||||
$sql = "SELECT * FROM users WHERE gender = :name OR username = :name";
|
||||
$stmt = $wrapper->getConnection()->prepare($sql);
|
||||
$stmt->bindValue("name", "jack");
|
||||
$resultSet = $stmt->executeQuery();
|
||||
```
|
||||
|
||||
### 执行常规语句
|
||||
|
||||
执行常规语句为 `statement` 方式执行,此方法执行后只返回影响的行数,而不返回结果,适用于 `UPDATE` 等语句。
|
||||
|
||||
```php
|
||||
<?php
|
||||
$count = $wrapper->executeStatement('UPDATE users SET username = ? WHERE id = ?', array('jwage', 1));
|
||||
echo $count; // 1
|
||||
```
|
||||
|
||||
### 执行查询语句
|
||||
|
||||
为给定的 SQL 创建一个准备好的语句并将参数传递给 executeQuery 方法,然后返回结果集。此方法为上述的「预处理查询语句」的简化版,可直接在第二个参数使用 array 插入绑定参数执行。
|
||||
|
||||
```php
|
||||
$resultSet = $wrapper->executeQuery('SELECT * FROM user WHERE username = ?', array('jack'));
|
||||
$user = $resultSet->fetchAssociative();
|
||||
|
||||
/* $user 值
|
||||
array(
|
||||
0 => array(
|
||||
'id' => 1,
|
||||
'username' => 'jack',
|
||||
'gender' => 'man',
|
||||
'update_time' => '2021-10-12'
|
||||
)
|
||||
)
|
||||
*/
|
||||
```
|
||||
|
||||
#### fetchAllAssociative()
|
||||
|
||||
执行查询并将所有结果返回一个数组中。
|
||||
|
||||
因此,上面的查询语句还可以直接被简化为一次方法调用:
|
||||
|
||||
```php
|
||||
$resultSet = $wrapper->fetchAllAssociative('SELECT * FROM user WHERE username = ?', array('jack'));
|
||||
// 结果同 executeQuery()->fetchAllAssociative() 中 $user 的值。
|
||||
```
|
||||
|
||||
#### fetchAllKeyValue()
|
||||
|
||||
执行查询并将前两列分别作为键和值提取到关联数组中。
|
||||
|
||||
```php
|
||||
$resultSet = $wrapper->fetchAllKeyValue('SELECT username, gender FROM user WHERE username = ?', array('jack'));
|
||||
|
||||
/* $resultSet 值
|
||||
array(
|
||||
'jack' => 'man'
|
||||
)
|
||||
*/
|
||||
```
|
||||
|
||||
#### fetchAllAssociativeIndexed()
|
||||
|
||||
执行查询并将数据作为关联数组获取,其中键代表第一列,值是其余列及其值的关联数组。
|
||||
|
||||
```php
|
||||
$users = $wrapper->fetchAllAssociativeIndexed('SELECT id, username, gender FROM users');
|
||||
|
||||
/*
|
||||
array(
|
||||
1 => array(
|
||||
'username' => 'jack',
|
||||
'gender' => 'man',
|
||||
'update_time' => '2021-10-12'
|
||||
)
|
||||
)
|
||||
*/
|
||||
```
|
||||
|
||||
#### fetchNumeric()
|
||||
|
||||
查询并返回第一行数据,形式以数字索引方式返回每一列。
|
||||
|
||||
```php
|
||||
$user = $wrapper->fetchNumeric('SELECT * FROM users WHERE username = ?', array('jack'));
|
||||
|
||||
/*
|
||||
array(
|
||||
0 => 'jwage',
|
||||
1 => 'man',
|
||||
2 => '2021-10-12'
|
||||
)
|
||||
*/
|
||||
```
|
||||
|
||||
#### fetchOne()
|
||||
|
||||
仅返回查询结果的第一行第一列的值。
|
||||
|
||||
```php
|
||||
$username = $wrapper->fetchOne('SELECT username FROM users WHERE id = ?', array(1));
|
||||
echo $username; // jack
|
||||
```
|
||||
|
||||
#### fetchAssociative()
|
||||
|
||||
返回结果内第一行的关联数组形式的数据。
|
||||
|
||||
```php
|
||||
$users = $wrapper->fetchAssociative('SELECT * FROM users');
|
||||
|
||||
/*
|
||||
array(
|
||||
'id' => 1,
|
||||
'username' => 'jack',
|
||||
'gender' => 'man',
|
||||
'update_time' => '2021-10-12'
|
||||
)
|
||||
*/
|
||||
```
|
||||
|
||||
#### delete()
|
||||
|
||||
删除查询操作,第一个参数为表名,第二个参数为 `['列名' => '列值']`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
$wrapper->delete('users', array('username' => 'jack'));
|
||||
// 等同于执行DELETE FROM user WHERE username = ? ,参数列表为('jack')
|
||||
```
|
||||
|
||||
#### insert()
|
||||
|
||||
插入数据库一行,第一个参数为表名,第二个参数为对应数据。
|
||||
|
||||
```php
|
||||
$wrapper->insert('users', array('id' => 0, 'username' => 'jwage', 'gender' => 'woman', 'update_time' => '2021-10-17'));
|
||||
// INSERT INTO user (id, username, gender, update_time) VALUES (?,?,?,?) (0,jwage,woman,2021-10-17)
|
||||
```
|
||||
|
||||
#### update()
|
||||
|
||||
更新数据库,使用给定数据更新匹配键值标识符的所有行。
|
||||
|
||||
```php
|
||||
<?php
|
||||
$wrapper->update('user', array('username' => 'jwage'), array('id' => 1));
|
||||
// UPDATE user (username) VALUES (?) WHERE id = ? (jwage, 1)
|
||||
```
|
||||
@ -1,62 +0,0 @@
|
||||
# Redis
|
||||
|
||||
炸毛框架内置了 Redis 连接池,供开发者使用。使用前需要先安装 `redis` 扩展:
|
||||
|
||||
```bash
|
||||
pecl install redis
|
||||
```
|
||||
|
||||
> 如果是 Docker 环境,则默认已安装。
|
||||
|
||||
## 配置
|
||||
|
||||
配置文件在 `config/global.php` 的全局配置文件下,详情见 [配置](/guide/basic-config/#redis_config)。
|
||||
|
||||
示例配置(假设 Redis Server 开到了本地):
|
||||
|
||||
```php
|
||||
/** Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 6379,
|
||||
'timeout' => 1,
|
||||
'db_index' => 0,
|
||||
'auth' => ''
|
||||
];
|
||||
```
|
||||
|
||||
## 使用
|
||||
|
||||
当写好配置文件后,不可以使用 reload 进行重载,因为连接池需要在主进程中声明配置,才可以应用到多个工作进程中。所以必须输入 `stop` 或 Ctrl+C 停止后再启动框架。
|
||||
|
||||
定义:`ZM\Store\Redis\ZMRedis`
|
||||
|
||||
因为使用的是连接池,所以每次使用完一个连接需要归还连接给连接池。框架封装了两种方式自动归还,你可以选择下面其中的任意一种。
|
||||
|
||||
以下的方式获取的 `$redis` 都是 `redis` 扩展的对象 `\Redis`,关于 redis 扩展的方法文档,详情见:[Redis 文档](https://www.php.cn/course/49.html)。
|
||||
|
||||
### 对象模式
|
||||
|
||||
```php
|
||||
$obj = new ZMRedis();
|
||||
$redis = $obj->get();
|
||||
ctx()->reply($redis->ping("123"));
|
||||
```
|
||||
|
||||
### 回调模式
|
||||
|
||||
```php
|
||||
// 前面的代码
|
||||
ZMRedis::call(function($redis) {
|
||||
$redis->set("key1", "hello world");
|
||||
$result = $redis->get("key1");
|
||||
ctx()->reply($result);
|
||||
});
|
||||
// 后面的代码
|
||||
```
|
||||
|
||||
### 二者的区别
|
||||
|
||||
选一个喜欢的就好。硬要是说区别的话,对象模式是在 PHP 自动回收这个 `ZMRedis` 对象时会归还连接,也可以通过手动 `unset($obj)` 进行回收,否则就会执行到函数结尾自动回收。切记不可将 `$obj` 对象持久化存到静态或全局变量等。
|
||||
|
||||
回调模式看似是回调,但是是同步执行的,不会发生顺序错乱。也就是说到了 `ZMRedis::call()` 方法里面的时候,后面的代码不会提前执行,是顺序执行的。回调的作用仅仅是用作自动回收连接对象。
|
||||
@ -1,81 +0,0 @@
|
||||
# SpinLock 自旋锁
|
||||
|
||||
前面讲到 LightCache 轻量缓存在特定的情况下为了保证数据不被多进程的因素导致丢失或覆盖,在高并发情况下修改数据需要加锁,所以炸毛框架内置了 SpinLock 自旋锁。
|
||||
|
||||
::: tip 提示
|
||||
|
||||
框架单进程运行的模式下不需要任何自旋锁。
|
||||
|
||||
:::
|
||||
|
||||
## 配置
|
||||
|
||||
自旋锁使用无需配置,和 LightCache 同源。
|
||||
|
||||
## 使用
|
||||
|
||||
定义:`ZM\Store\Lock\SpinLock`
|
||||
|
||||
### SpinLock::lock($key)
|
||||
|
||||
给信号量 `$key` 上锁。如果该信号量已经被上锁,则原地等待直到其他资源释放锁。
|
||||
|
||||
```php
|
||||
SpinLock::lock("foo");
|
||||
```
|
||||
|
||||
### SpinLock::unlock($key)
|
||||
|
||||
给信号量 `$key` 释放锁。
|
||||
|
||||
```php
|
||||
SpinLock::unlock("foo");
|
||||
```
|
||||
|
||||
### SpinLock::tryLock($key)
|
||||
|
||||
给信号量 `$key` 上锁。如果该信号量已经被上锁,则立刻返回 false。
|
||||
|
||||
```php
|
||||
SpinLock::trylock("foo");
|
||||
```
|
||||
|
||||
## 综合实例
|
||||
|
||||
我们这里以之前在 LightCache 中的实例进行继续讲解,如何给之前那样的情况加锁:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
*/
|
||||
public function test() {
|
||||
SpinLock::lock("web_count"); // 加上这行
|
||||
$s = LightCache::get("web_count");
|
||||
if($s === null) $s = 1;
|
||||
else $s += 1;
|
||||
LightCache::set("web_count", $s);
|
||||
SpinLock::unlock("web_count"); // 再加上这行
|
||||
return "<h1>It works!</h1>";
|
||||
}
|
||||
```
|
||||
|
||||
加两行就 OK。这时再使用压测工具请求 200000 次,值就会是 200000 了!
|
||||
|
||||
原理剖析:在 LightCache 获取前,先对此内容上锁,这时如果其他进程有同时也在执行这个代码的时候,就会在 `SpinLock::lock()` 这行代码处原地等待,防止继续执行。等前面的那个进程执行到 `SpinLock::unlock()` 释放锁时,其他进程才可继续执行,从而避免了多个进程并行执行这段代码导致的数据错乱。
|
||||
|
||||
::: danger 警告
|
||||
|
||||
使用锁时务必谨慎,如果不按照下面的规则使用自旋锁可能导致 CPU 占用率上升。
|
||||
|
||||
:::
|
||||
|
||||
自旋锁使用约定:
|
||||
|
||||
- 使用 `SpinLock::lock()` 指定信号量名称时必须指定为字符串,且最好与你的 LightCache 缓存名称相同。
|
||||
- 使用 `lock()` 时最好紧跟在 `LightCache::get()` 代码前。
|
||||
- 使用自旋锁后,`LightCache::get()` 到 `LightCache::set()` 之间的代码段一定不能有 **读写文件、数据库操作和网络请求** 等代码,最好为纯 PHP 逻辑代码,且越短越好,如示例代码。
|
||||
- 在 `LightCache::set()` 后最好紧跟 `SpinLock::unlock()`。
|
||||
|
||||
## 性能
|
||||
|
||||
使用自旋锁几乎没有性能损失,自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下,使用自旋锁和不适用自旋锁的测试成绩时间分别为:7.4s 和 6.9s。
|
||||
@ -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` 就可以快速地调用此路由下的局部文件服务器功能了。
|
||||
@ -1,7 +0,0 @@
|
||||
# FAQ
|
||||
|
||||
这里会写一些常见的疑难解答,点击左侧问题名称打开对应解决方法。
|
||||
|
||||
如果框架运行过程中发现带有错误码(如 `E00034` 的形式),可以到 [错误码](/guide/errcode) 查看。
|
||||
|
||||
框架的常见问题见 [常见问题汇总](/faq/usual-question)。
|
||||
@ -1,19 +0,0 @@
|
||||
# 启动时报错 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 看不到框架相关进程,证明关闭成功,否则需要使用第一条强行杀死
|
||||
```
|
||||
@ -1,22 +0,0 @@
|
||||
# 出现 deadlock 字样
|
||||
|
||||
一般情况下,如果误操作框架可能会报如下图的错误:
|
||||
|
||||
```
|
||||
===================================================================
|
||||
[FATAL ERROR]: all coroutines (count: 1) are asleep - deadlock!
|
||||
===================================================================
|
||||
|
||||
[Coroutine-1]
|
||||
--------------------------------------------------------------------
|
||||
#0 Swoole\Coroutine\System::sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/global_functions.php:232]
|
||||
#1 zm_sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/Module/Example/Hello.php:38]
|
||||
#2 Module\Example\Hello->onStart() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:205]
|
||||
#3 ZM\Event\EventDispatcher->dispatchEvent() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:89]
|
||||
#4 ZM\Event\EventDispatcher->dispatchEvents() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/SwooleEvent/OnWorkerStart.php:130]
|
||||
#5 ZM\Event\SwooleEvent\OnWorkerStart->onCall() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Framework.php:336]
|
||||
```
|
||||
|
||||
这种错误的出现原因一般是因为协程未结束而 Worker 进程提前退出导致的,这个错误也可手动造成(在任意 Worker 进程内的位置使用 `zm_yield()` 且不使用 `zm_resume()` 恢复,期间使用 reload 或 stop 重启或停止框架就会报错)。
|
||||
|
||||
还有一种情况是数据库、文件读取或下载上传还没有传送结束,时间已经超时,在关闭或重启框架时不得不强行切断协程的运行。这种情况建议根据下方的打印输出栈进行插错,建议将协程运行时间长的过程缩短或调长 `swoole` 配置项下面的 `max_wait_time` 时间(秒),2.4.3 版本起此参数默认为 5 秒。
|
||||
@ -1,5 +0,0 @@
|
||||
# 使用 LightCache 关闭时无法正常保存持久化
|
||||
|
||||
LightCache 因为是跨内存使用的,所以每次重启和关闭框架时,都只会让其中一个进程去保存。因为在 2.4.2 版本开始,持久化的逻辑发生了更改,不再支持 `expire = -2` 进行设置持久化(因为那样会很容易让开发者写错),仅支持使用 `LightCache::addPersistence($key)` 这样的方式进行设置持久化,所以在 2.4.2 版本以后,请使用此方法进行持久化设置,保证数据不丢失。
|
||||
|
||||
此外,2.4.2 版本起,不再支持用户手动调用 `savePersistence()` 方法,普通用户不可手动调用此方法,否则会导致数据出错。
|
||||
@ -1,39 +0,0 @@
|
||||
# 从炸毛框架 V1 升级
|
||||
|
||||
> 这里只写明可能在升级过程中会影响原先代码执行的部分,不包含新增的特性等。
|
||||
|
||||
### 需要改变命名空间的类
|
||||
|
||||
- `Framework\Console` -> `ZM\Console\Console`
|
||||
- `Swlib\Util\SingletonTrait` -> `ZM\Utils\SingletonTrait`
|
||||
- `ZM\Annotation\Http\Before` -> `ZM\Annotation\Http\HandleBefore`
|
||||
- `ZM\Annotation\Http\After` -> `ZM\Annotation\Http\HandleAfter`
|
||||
- `@SwooleEventAt` -> `@OnSwooleEvent`
|
||||
- 删除 `@SwooleEventAfter`
|
||||
- 删除 `ModBase`
|
||||
- `@HandleEvent` -> `@SwooleHandler`
|
||||
- `ZM\Utils\ZMRobot` -> `\ZM\API\ZMRobot`
|
||||
|
||||
### 方法名称变更
|
||||
|
||||
- `ZM\Console::stackTrace()` -> `ZM\Console::trace()`
|
||||
|
||||
### 注解的变化
|
||||
|
||||
`@OnSwooleEvent`(原 `@SwooleEventAt`)中,`rule` 参数不再是自定义语法的东西了(比如之前的 `connectType:qq` 之类的鸡肋语法),直接是可执行的 PHP 代码,比如 `3 == 4`,`connectIsQQ()` 之类的。
|
||||
|
||||
去除 `@CQAPISend`,因为目前没什么意义。
|
||||
|
||||
`@CQCommand` 中,`regexMatch` 变成 `pattern`,`fullMatch` 变成 `regex`,消除歧义(第一个是 * 号匹配符进行匹配的,第二个是标准的正则表达式匹配)。同时新增 `start_with`,`end_with`,`keyword` 平行选项。
|
||||
|
||||
`@OnTick` 注解新增第二个参数 `worker_id`,其中默认是 0,代表只在 `#0` 号工作进程上运行计时器。
|
||||
|
||||
### 中间件编写的改变
|
||||
|
||||
原先的 Middleware 是需要含有 `getName()` 方法才合法,现在不需要了,但是对 `@MiddlewareClass` 注解需要增加参数,也就是说原先 `getName()` 返回的名称现在需要写到 `@MiddlewareClass("xxx")` 这样的形式。
|
||||
|
||||
### ZMBuf 的变化
|
||||
|
||||
由于 2.0 框架使用了多进程模型,所以不能使用原先适用于单进程下全局变量的方式(ZMBuf)进行存取变量,所以 ZMBuf 下的所有方法都需要更改,其中 `get, set` 等对缓存操作的模型请根据 2.0 的文档变更使用 `Redis` 或内置的多进程共享内存可用的 `LightCache` 轻量缓存。
|
||||
|
||||
而获取全局配置文件,如 `global.php` 文件,也发生了变化,新框架引入了 `ZMConfig` 对象,可以快速地区分各类环境变量从而读取不同的配置文件。比如我们获取原先的 global 配置文件中的一项:`ZMBuf::globals("port")`,在 2.0 中需要使用 `ZMConfig::get("global", "port")` 方式。以此类推,`ZMBuf::config("xxx")` 也直接变为 `ZMConfig::get("xxx")` 了。
|
||||
@ -1,65 +0,0 @@
|
||||
# 框架常见问题(持续更新)
|
||||
|
||||
## 如何正确地强制退出炸毛框架?
|
||||
|
||||
首先要知道一个概念,炸毛框架和传统的 PHP 以及其他如 Python 等语言的轻量框架都不同,框架启动后会依次启动 Master、Manager、Worker 等多个进程,而用户启动时入口的 PHP 进程就是 Master 进程,在一些对框架的正常中止、热重启上,我们给 Master 进程发送相应的 Linux 信号(如 SIGTERM)即可对整个框架的多个进程生效,无需给每个进程发送。
|
||||
|
||||
但是如果因为用户的误操作,导致炸毛框架其中的一个或多个进程阻塞,或者比如将框架挂在 screen 等守护但是守护服务进程被杀掉,总之就是无法使用 Ctrl+C 的方式正常关闭框架,这时就需要正确地杀掉所有框架进程(这固然可能会造成内存的缓存数据丢失)。
|
||||
|
||||
### v2.7.0 及以上版本教程
|
||||
|
||||
- 安全关框架指令:`./zhamao server:stop`
|
||||
- 万能杀死所有框架进程指令:`./zhamao server:stop --force`
|
||||
- 监视框架是否在运行:`./zhamao server:status`
|
||||
- Worker 进程卡死:连续按 5 次 Ctrl+C 即可强行杀掉所有进程(SIGKILL)
|
||||
|
||||
### v2.6.6 及以下版本教程
|
||||
|
||||
::: warning 注意
|
||||
|
||||
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
|
||||
|
||||
:::
|
||||
|
||||
**一、**首先,使用 `ps`、`htop`、`netstat -nlp` 等命令确定框架的入口进程(也就是 Master 进程的 pid)。
|
||||
|
||||
确认方式示例如下:
|
||||
|
||||
- 如果你使用的是 >=2.4 版本的框架,在框架启动时就会在最先开始的 motd 上方显示 `master_pid`,如果你还能找到此处的显示,那么恭喜你,可以直接进行下面的第二步。
|
||||
- 如果你不能正常通过框架的方式找到 pid,可以通过命令 `ps aux | grep php | grep server` 的方式找到框架所有的进程。其中列出的相关框架的进程,可以寻找 pid 最小的进程,即为 Master 进程。关于如何区分进程对应关系,见本页 [使用 Linux 工具辨别框架进程]()。
|
||||
- 如果你对 `ps` 不熟悉,可以使用 `htop` 工具,使用 `F5 Tree` 方式显示,并且使用 `F4` 的 Filter,过滤 `php` 或 `bin/start` 等字样,找到进程树。
|
||||
|
||||
**二、**然后,确定框架是否正常运行且正常流程关闭。
|
||||
|
||||
如果框架能正常运行,比如可以通过访问浏览器的 `http://地址:端口/httpTimer` 等 HTTP 路由,可以使用 `SIGINT` 或 `SIGTERM` 信号正常关闭框架。我们假设 Master 进程的 pid 为 31234:`kill -TERM 31234` 或 `kill -INT 31234`,如果稍后使用 `ps aux | grep php | grep server` 命令发现没有进程存在(排除掉 grep 自身的进程),说明可以正常关闭,此关闭方法为正常停止流程,即保存了 `LightCache` 等内存缓存持久化的数据。
|
||||
|
||||
如果以上方式没有任何效果,继续看第三步。
|
||||
|
||||
**三、**不能正常流程关闭,需要手动杀掉所有进程。
|
||||
|
||||
首先使用 `ps aux | grep php | grep server | grep -v grep | awk '{print $2}'` 列出框架所有进程的 pid,确认无误后,在此条命令后接 `| xargs kill -9` 即可:
|
||||
|
||||
```bash
|
||||
# 列出进程,只显示包含php,只显示包含server,排除grep本身进程,显示第二列的pid,使用xargs循环kill这里面的进程
|
||||
ps aux | grep php | grep server | grep -v grep | awk '{print $2}' | xargs kill -9
|
||||
```
|
||||
|
||||
## 如何使用 Linux 工具查看框架进程状态?
|
||||
|
||||
框架有多个进程,有时候我们需要通过监视进程状态来确定框架是否正常运行或查看框架的资源占用率。首先一个大概念,老生常谈,炸毛框架由 Master、Manager、Worker(、TaskWorker)进程组成的。
|
||||
|
||||
如果使用 htop 工具,就比较简单,比如我启动了一个应用,使用炸毛框架编写的垃圾分类小程序 API 服务器,在 htop 命令后找到如图这部分(下面的树状图是按 F5 后切换为树状显示,避免进程刷太快可以输入 `Shift+z`):
|
||||
|
||||

|
||||
|
||||
其中,`-zsh` 下有唯一一个 php 进程,在图中对应的第一列 pid 为 `16258`,代表 Master 进程。
|
||||
|
||||
Master 进程下的唯一一个子进程(白色的是进程,绿色是线程),在图中对应的 pid 为 `16263`,代表 Manager 进程,用作管理 Worker 进程。
|
||||
|
||||
Manager 进程下的子进程,连号部分为对应的 Worker 进程,比如图中的 `16266`,`16267`,`16268`,`16269` 分别代表 `Worker #0`,`Worker #1`,`Worker #2`,`Worker #3` 四个 Worker 进程。
|
||||
|
||||
如果你还设置了 TaskWorker 进程,TaskWorker 进程的 pid 会和 Worker 进程一样是连续的,一般会接在 Worker 进程后面。
|
||||
|
||||
`htop` 使用方向键选择进程,选择到对应进程后可以使用 `F9` 来选择 kill 指令,比如让框架热重启,可以将光标移到 Master 进程上,使用 `SIGUSR1`:
|
||||
|
||||

|
||||
@ -1,23 +0,0 @@
|
||||
# CQBefore 过滤不了 waitMessage
|
||||
|
||||
因为 `waitMessage()` 功能是要等待接收下一个消息事件的,而消息事件又会被 CQBefore 走一遍。但是这里就会有一个问题,那 `waitMessage()` 的消息会不会走 CQBefore 呢?(显然不会啊!这个问题的题目就是这个!)
|
||||
|
||||
框架在 2.4.2 版本之前是无法过滤 waitMessage() 的(之前在 2.1 版本左右的几个版本是可以的,但这里不讨论历史版本),从 2.4.2 版本起支持过滤 `waitMessage`,但是需要设置一下 `@CQBefore` 的级别。
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQBefore("message",level=201)
|
||||
*/
|
||||
public function filter1() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQBefore("message")
|
||||
*/
|
||||
public function filter2() {
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
如果 `level >= 200`,那么此注解事件则会过滤 `waitMessage()`,如果 `level < 200`,则不会。(`@CQBefore` 的默认 level 为 20,所以默认情况下是不会过滤 waitMessage 的)
|
||||
@ -1,50 +1,30 @@
|
||||
# 介绍
|
||||
|
||||
::: tip 提示
|
||||
> 编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request,以此来贡献此文档!
|
||||
|
||||
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request,以此来贡献此文档!
|
||||
炸毛框架主要面向聊天机器人(OneBot 标准)和 API 服务的开发。
|
||||
|
||||
:::
|
||||
框架使用 PHP 编写,无需安装任何额外扩展即可运行在任意主流平台,支持包括 Swoole 和 Workerman 在内的多种驱动,同时支持所有在 OneBot 标准内的通信方式,并支持使用注解(Annotation)和属性(Attribute)注册绑定各种事件,同时引入了依赖注入容器让开发更为便捷。
|
||||
|
||||
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
|
||||
框架内置了对于 WebSocket 和 HTTP 的服务端和客户端支持,并针对聊天机器人消息处理进行优化扩展,提供常用会话机制和内部调用机制,让代码更为灵活。
|
||||
|
||||
框架主要用途为 HTTP/WebSocket 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
|
||||
这里放个代码示例
|
||||
|
||||
此外,QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
|
||||
## 环境要求
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("你好")
|
||||
*/
|
||||
public function hello() {
|
||||
ctx()->reply("你好,我是炸毛!");
|
||||
}
|
||||
/**
|
||||
* @RequestMapping("/index")
|
||||
*/
|
||||
public function index() {
|
||||
return "<h1>hello!</h1>";
|
||||
}
|
||||
```
|
||||
虽然我们已经大力简化了运行框架的要求,但仍然存在少量的必要项:
|
||||
|
||||
|
||||
|
||||
## 开始前
|
||||
|
||||
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
|
||||
|
||||
1. Linux 命令行(会跑 Linux 程序)
|
||||
2. php >=7.2 开发环境(项目会持续支持最新的 PHP 版本)
|
||||
4. OneBot 机器人聊天接口标准
|
||||
|
||||
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
|
||||
- PHP 8.0 或以上版本
|
||||
- JSON 扩展
|
||||
- Tokenizer 扩展
|
||||
- Composer 工具
|
||||
|
||||
## 框架特色
|
||||
|
||||
- 支持MySQL数据库(连接池),自带查询缓存提高多查询时的效率
|
||||
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
|
||||
- WebSocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
|
||||
- 支持命令、自然语言处理等多种插件形式
|
||||
- 支持多个机器人账号负载均衡
|
||||
- 协程 + TaskWorker 进程重度任务处理机制,保证高效,单个请求响应时间为 0.1 ms 左右
|
||||
- 模块分离和自由组合,可根据自身需求自己建立模块内的目录结构和代码结构
|
||||
- 灵活的注释注解注册事件方式,弥补 PHP 语言缺少注解的遗憾
|
||||
- 灵活的注释和注解注册事件方式,支持 PHP 原生注解,提示更为友好
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
# 基本配置
|
||||
|
||||
到目前为止,炸毛框架的配置文件还没有任何变更,是默认的行为。在本章内容中,将列举出炸毛框架的配置文件的规则和使用。
|
||||
|
||||
::: danger 警告
|
||||
|
||||
因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将敏感信息写入配置文件并提交至开源仓库!
|
||||
|
||||
:::
|
||||
|
||||
## 全局配置文件 global.php
|
||||
|
||||
框架的全局配置文件在 `config/global.php` 文件中。下面是配置文件的各个选项,请根据自己的需要自行配置。
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| :--------------------------- | ------------------------------------------------------------ | ---------------------------- |
|
||||
| `host` | 框架监听的地址 | 0.0.0.0 |
|
||||
| `port` | 框架监听的端口 | 20001 |
|
||||
| `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 |
|
||||
| `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` |
|
||||
| `debug_mode` | 框架是否启动 debug 模式 | false |
|
||||
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
|
||||
| `config_dir` | 存放 saveToJson() 方法保存的数据的目录 | `zm_data` 下的 `config/` |
|
||||
| `swoole` | 对应 Swoole server 中 set 的参数,参考Swoole文档 | 见子表 `swoole` |
|
||||
| `runtime` | 一些框架运行时调整的设置 | 见子表 `runtime` |
|
||||
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
|
||||
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
|
||||
| `mysql_config` | MySQL 数据库连接信息 | 见子表 `mysql_config` |
|
||||
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
|
||||
| `access_token` | OneBot 客户端连接约定的token,留空则无,相关设置见 [组件 - Access Token 验证](component/access-token) | 空 |
|
||||
| `http_header` | HTTP 请求自定义返回的header | 见配置文件 |
|
||||
| `http_default_code_page` | HTTP服务器在指定状态码下回复的默认页面 | 见配置文件 |
|
||||
| `init_atomics` | 框架启动时初始化的原子计数器列表 | 见配置文件 |
|
||||
| `info_level` | 终端日志显示等级(0-4) | 2 |
|
||||
| `context_class` | 上下文所定义的类,见对应上下文说明文档 | `\ZM\Context\Context::class` |
|
||||
| `static_file_server` | 静态文件服务器配置项 | 见子表 `static_file_server` |
|
||||
| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表,在 Swoole 服务器启动前就被加载 | 空 |
|
||||
| `onebot` | OneBot 协议相关配置 | 见子表 `onebot` |
|
||||
| `remote_terminal` | 远程终端相关配置 | 见子表 `remote_terminal` |
|
||||
| `module_loader` | 模块/插件 加载相关配置 | 见子表 `module_loader` |
|
||||
|
||||
### 子表 **swoole**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ----------------------- | ------------------------------------------------------------ | ----------------------------------- |
|
||||
| `log_file` | Swoole 的日志文件 | `crash_dir` 下的 `swoole_error.log` |
|
||||
| `worker_num` | Worker 工作进程数 | 运行框架的主机 CPU 核心数 |
|
||||
| `dispatch_mode` | 数据包分发策略,见 [文档](https://wiki.swoole.com/#/server/setting?id=dispatch_mode) | 2 |
|
||||
| `max_coroutine` | 最大协程并发数 | 300000 |
|
||||
| `max_wait_time` | 退出进程时等待协程恢复的最长时间(秒) | 5(2.4.3 版本后默认值) |
|
||||
| `task_worker_num` | TaskWorker 工作进程数 | 默认不开启(此参数被注释) |
|
||||
| `task_enable_coroutine` | TaskWorker 工作进程启用协程 | 默认不开启(此参数被注释)或 `bool` |
|
||||
|
||||
### 子表 runtime
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ----------------------------- | ------------------------------------------------------------ | --------------------------------------- |
|
||||
| `swoole_coroutine_hook_flags` | Swoole 启动时一键协程化 Hook 的 Flag 值,详见 [一键协程化](http://wiki.swoole.com/#/runtime?id=%e5%87%bd%e6%95%b0%e5%8e%9f%e5%9e%8b) | `SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)` |
|
||||
| `swoole_server_mode` | Swoole Server 启动的进程模式,有 `SWOOLE_PROCESS` 和 `SWOOLE_BASE` 两种,见 [启动方式](http://wiki.swoole.com/#/learn?id=swoole_process) | `SWOOLE_PROCESS` |
|
||||
| `middleware_error_policy` | 中间件错误处理策略,见 [中间件 - 错误处理策略](../../event/middleware/#_6) | 1 |
|
||||
| `reload_delay_time` | 框架 reload 重载命令接收后延迟的时间(毫秒,0 为不等待) | 800 |
|
||||
| `global_middleware_binding` | 给注解事件绑定全局中间件,见 [中间件 - 全局中间件](../../event/middleware/#_6) | `[]` |
|
||||
| `save_console_log_file` | 当这里输入字符串路径时,所有 `Console::xxx()` 输出的日志都将保存到目标文件 | false |
|
||||
|
||||
### 子表 **light_cache**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------------------------- | ----------------------------------------------- | ---------------------------- |
|
||||
| `size` | 最多可以缓存的 k-v 条目数(必须是 2 的 n 次方) | 512 |
|
||||
| `max_strlen` | 作为 value 字符串的最大长度 | 32768 |
|
||||
| `hash_conflict_proportion` | Hash冲突率(越大越好,但是需要的内存更多) | 0.6 |
|
||||
| `persistence_path` | 持久化的键值对的存储路径 | `zm_data` 下的 `_cache.json` |
|
||||
| `auto_save_interval` | 持久化的键值对自动保存时间间隔(秒) | 900 |
|
||||
|
||||
### 子表 worker_cache
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | --------------------------- | ------ |
|
||||
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
|
||||
|
||||
### 子表 **mysql_config**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ------------------------ | ------------------------------ | ------------------------------------------------------------ |
|
||||
| `host` | 数据库地址(留空则不使用数据库) | 空 |
|
||||
| `port` | 数据库端口 | 3306 |
|
||||
| `username` | 连接数据库的用户名 | |
|
||||
| `dbname` | 要连接的数据库名 | |
|
||||
| `password` | 数据库连接密码 | |
|
||||
| `options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]` |
|
||||
| `pool_size` | MySQL 连接池大小 | 64 |
|
||||
| `charset` | MySQL 字符集 | `utf8mb4` |
|
||||
|
||||
### 子表 **redis_config**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ---------- | ------------------------------------------ | ------ |
|
||||
| `host` | Redis 服务器地址,留空则启动时不创建连接池 | 空 |
|
||||
| `port` | Redis 服务器端口 | 6379 |
|
||||
| `timeout` | Redis 超时时间 | 1 |
|
||||
| `db_index` | Redis 要连接的数据库 index | 0 |
|
||||
| `auth` | 认证字符串 | 空 |
|
||||
|
||||
### 子表 static_file_server
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ---------------- | ---------------------- | ------------------------------ |
|
||||
| `status` | 是否开启静态文件服务器 | false |
|
||||
| `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` |
|
||||
| `document_index` | 默认索引的文件名列表 | `["index.html"]` |
|
||||
|
||||
### 子表 onebot
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ------------------------ | ------------------------------------------------------------ | ------ |
|
||||
| `status` | 是否开启 OneBot 标准机器人解析功能 | true |
|
||||
| `single_bot_mode` | 是否开启单机器人模式 | false |
|
||||
| `message_level` | 机器人的 WebSocket 事件在 Swoole 原生事件 `@OnMessageEvent` 中的等级(越高说明越被优先处理) | 99 |
|
||||
| `message_convert_string` | 是否将数组格式的消息转换为字符串以保证与旧版本的兼容性 | true |
|
||||
| `message_command_policy` | CQCommand命令匹配后执行流程,`interrupt` 为不执行后续 CQMessage,`continue` 为继续 | `interrupt` |
|
||||
|
||||
### 子表 remote_terminal
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | ------------------------------------------------------------ | ----------- |
|
||||
| `status` | 是否开启远程终端功能,见 [组件 - 远程终端](/component/remote-terminal) | false |
|
||||
| `host` | 远程终端监听地址,为安全起见,默认值只允许本地回环地址(127.0.0.1) | `127.0.0.1` |
|
||||
| `port` | 远程终端监听的 TCP 端口 | 20002 |
|
||||
| `token` | 远程终端连接的令牌(如果为空("")则不验证) | "" |
|
||||
|
||||
### 子表 module_loader
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | ------------------------------------------------------------ | ----------- |
|
||||
| `enable_hotload` | 是否开启热加载模块包的功能 | false |
|
||||
| `load_path` | 模块包加载的目录地址 | `zm_data` 下的 `modules` |
|
||||
|
||||
## 多环境下的配置文件
|
||||
|
||||
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(development / staging / production)来编写配置文件。
|
||||
|
||||
### 使用环境参数
|
||||
|
||||
在启动框架时,额外增加参数 `--env` 可以指定当前的环境,从而使用不同的配置文件。现在框架支持以下几种环境: `development`,`staging`,`production`。
|
||||
|
||||
```bash
|
||||
vendor/bin/start server --env=development
|
||||
```
|
||||
|
||||
### 不同环境配置文件
|
||||
|
||||
由于框架默认只带有 `global.php` 文件,所以假设你现在需要区分开发环境和生产环境的配置,将 `global.php` 文件复制并重命名为 `global.development.php` 或 `global.production.php` 即可。
|
||||
|
||||
### 优先级
|
||||
|
||||
如果指定了 `--env` 环境参数:`global.{对应环境}.php` > `global.php`,如果两个配置文件都找不到则报错。
|
||||
|
||||
如果未指定 `--env` 环境参数:`global.php` > `global.development.php` > `global.staging.php` > `global.production.php`。
|
||||
|
||||
## 其他自定义配置文件
|
||||
|
||||
炸毛框架的全局配置文件为 `global.php`,为了让不同的开发者更好的二次开发或者集成更多功能,炸毛框架的配置文件模块也支持自己编写的其他 `*.php` 或 `*.json` 格式的配置文件。例如炸毛框架默认附带了 `file_header.json` 这个配置文件(用来返回各类文件扩展名对应的 `Content-Type` 头参数的表)。
|
||||
|
||||
使用也非常简单,我们先以 `.json` 格式为例,我们创建一个 `example_a.json` 文件在 `config/` 目录(和 `global.php` 一个文件夹下),并编写自己的任意配置内容:
|
||||
|
||||
```json
|
||||
{
|
||||
"key1": "value1"
|
||||
}
|
||||
```
|
||||
|
||||
在框架中,启动后就会默认加载,使用只需要用以下方式即可:
|
||||
|
||||
```php
|
||||
use ZM\Config\ZMConfig; # 先 use 再使用!
|
||||
$r = ZMConfig::get("example_a", "key1"); # $r == "value1"
|
||||
```
|
||||
|
||||
如果需要用到变量或其他动态的内容,可以使用 `.php` 格式的配置文件。这里还是以 `example_a.php` 来举例:
|
||||
|
||||
```php
|
||||
<?php
|
||||
$config['key1'] = "value1";
|
||||
$config['starttime'] = time();
|
||||
return $config;
|
||||
```
|
||||
|
||||
使用方式同上:
|
||||
|
||||
```php
|
||||
$r = ZMConfig::get("example_a", "key1"); # $r == "value1"
|
||||
$time = ZMConfig::get("example_a", "starttime"); # $time == 服务器启动时间
|
||||
```
|
||||
|
||||
同时,自定义配置文件也支持环境变量,例如:`example_a.development.json` 或 `example_a.production.php` 均可。
|
||||
47
docs/guide/configuration.md
Normal file
47
docs/guide/configuration.md
Normal file
@ -0,0 +1,47 @@
|
||||
# 配置
|
||||
|
||||
炸毛框架的所有配置文件都存储在 `config` 目录中。每个选项都带有文档,所以你可以查阅这些文件并熟悉可用的配置项。
|
||||
|
||||
一般来说,我们建议你优先查看 `config/global.php` 文件及其文档,它包含了运行框架所需要的绝大部分配置,例如通信方式和时区等。
|
||||
|
||||
## 配置文件格式
|
||||
|
||||
除了 `php` 文件外,我们还支持以下格式:
|
||||
|
||||
- YAML
|
||||
- JSON
|
||||
- TOML
|
||||
|
||||
## 环境配置
|
||||
|
||||
根据运行的环境采用不同的配置值是很有必要的,例如你可能希望在本地和生产环境中使用不同的数据库配置。
|
||||
|
||||
为了方便这一功能,我们提供了基于环境的配置加载措施。你可以在启动框架时使用 `--env` 选项指定当前环境,例如 `./zhamao server --env=production` 。
|
||||
|
||||
如果未明确指定当前环境,则默认环境为 `development`。
|
||||
|
||||
如果要使某一配置文件只在特定环境下记载,你可以为给文件添加后缀,例如 `example.production.php` 只会在当前环境为 `production` 时加载。
|
||||
|
||||
你可以同时设置基础配置文件和环境特定配置文件,例如 `global.php` 和 `global.production.php` ,后者将会覆盖前者中的配置项。
|
||||
|
||||
### 配置安全
|
||||
|
||||
我们**非常不建议**你将数据库信息、访问密钥的敏感字段提交给版本控制,因为这可能会导致相关信息的泄露,同时也不利于其他开发人员修改自己的本地配置。
|
||||
|
||||
相反,我们建议你借助 DotEnv 等库,将相关配置信息写入 `.env` 文件中,并按需读取。
|
||||
|
||||
## 访问配置项
|
||||
|
||||
你可以在任何地方使用全局 `config` 函数获取配置项值。支持使用点语法获取,配置项名称由文件名开头,并允许指定默认值。
|
||||
|
||||
```php
|
||||
# 获取时区,未设置则返回 Asia/Shanghai
|
||||
$value = config('global.runtime.timezone', 'Asia/Shanghai');
|
||||
```
|
||||
|
||||
你也可以使用 `config` 函数设置配置项,传递数组即可:
|
||||
|
||||
```php
|
||||
# 将时区修改为 UTC
|
||||
config(['global.runtime.timezone' => 'UTC']);
|
||||
```
|
||||
@ -1,86 +0,0 @@
|
||||
# 错误码对照表
|
||||
|
||||
| 异常码 | 含义 | 解决方案 |
|
||||
|--------|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| E00001 | 炸毛框架未检测到 PHP 安装了 Swoole 扩展 | 根据文档安装扩展去! |
|
||||
| E00002 | Swoole 扩展安装的版本过低 | 升级 Swoole 版本,最好为最新版。 |
|
||||
| E00003 | PHP 版本过低 | 升级 PHP 版本,至少为 7.2。 |
|
||||
| E00004 | Swoole 版本低于 4.6.7 且未安装 pcntl 扩展 | 安装 pcntl 扩展或升级 Swoole 至少为4.6.7。 |
|
||||
| E00005 | 在框架命令行解析过程中出现了致命错误 | 请根据提示的错误位置进行调试和修复,如果未解决请将问题反馈给作者。 |
|
||||
| E00006 | 炸毛框架在源码模式启动时未能修改 composer.json 文件 | 检查源码模式下 composer.json 文件是否正常可写可读。 |
|
||||
| E00007 | 框架在启动时未找到 global.php 全局配置文件 | 如果是使用 `composer create-project` 或用 git 来克隆 starter 仓库的,需要先使用 `vendor/bin/start init` 指令,再启动服务器。 |
|
||||
| E00008 | 框架在启动时给用于存储连接数据的共享内存表初始化失败 | 请检查系统内存是否过小,如果一切正常,此问题一般是框架内部导致的问题,请将错误日志反馈给开发者。 |
|
||||
| E00009 | 使用 `--remote-terminal` 时远程终端处理命令出现异常或致命错误 | 检查自身的远程终端是否正确配置和使用,自定义的 `@TerminalCommand` 注解是否抛出了致命错误。 |
|
||||
| E00010 | 框架在第一步的启动阶段抛出异常或致命错误,导致框架不能继续运行 | 此错误涵盖的错误内容较多,请根据实际抛出的异常内容进行处理或反馈给开发者。<br />如果你使用了 `@SwooleHandler` 或 `@OnSetup` 注解,那么可以自行检查一下注解绑定的函数有没有出错。 |
|
||||
| E00011 | 框架在调用 Swoole 服务器启动 `$server->start()` 过程中出现了异常 | 此问题未经测试,暂无解决方案,也没有遇到过,如果有发生,请将错误反馈开发者。 |
|
||||
| E00012 | 框架在启动时调用脚本解析 `@SwooleHandler` 和 `@OnSetup` 时出现了异常 | 留个坑,下次写,TODO。 |
|
||||
| E00013 | 使用命令行参数动态设置启动的 Worker/TaskWorker 进程数时输入了非法的数字 | 填写合法的数字或不使用此功能。 |
|
||||
| E00014 | 炸毛框架的启动命令报错,提示没有找到 PHP 环境 | 使用 `./install-runtime.sh` 命令安装便携的静态 PHP 环境或根据教程和 Linux 发行版安装环境。 |
|
||||
| E00015 | 启动命令启动框架找不到框架本体的入口文件 | 请检查 Composer 拉取的框架代码是否完整。 |
|
||||
| E00016 | 连接中断后 `@OnCloseEvent` 事件抛出异常 | 检查 `@OnCloseEvent` 或 `@OnSwooleEvent("close")` 注解事件。 |
|
||||
| E00017 | 框架作为 WebSocket 服务器收到客户端数据后 `@OnMessageEvent`、`@OnSwooleEvent("message")` 或 OneBot 相关事件抛出了未被捕获的异常或错误 | 检查 `@OnSwooleEvent("message")`、`@OnMessageEvent` 或 OneBot 相关注解事件。 |
|
||||
| E00018 | 框架设置 `access_token` 参数为自定义闭包函数,有新 WebSocket 连接接入但是闭包函数返回失败 | 说白了就是自定义的 `access_token` 验证失败。如果是自己的 OneBot 客户端连接,那么请检查你的函数或 OneBot 客户端那边和框架约定的 token 是否一致,如果将框架开到了公网并有人尝试连接但失败了说明是正常现象。 |
|
||||
| E00019 | 框架设置了 `access_token` 为固定字符串,但是 WebSocket 新连接验证 Token 失败 | 如果是自身行为,比如 OneBot 客户端接入,请检查 Token 是否一致。如果不需要设置 Token,请检查全局配置文件的 `access_token` 项是否为空字符串。 |
|
||||
| E00020 | 框架在收到 WebSocket 连接后触发 `@OnOpenEvent` 注解事件过程中抛出了异常 | 检查用户代码中 `@OnOpenEvent`、`@OnSwooleEvent("open")` 注解事件下的代码是否有问题。 |
|
||||
| E00021 | 框架在处理 pipeMessage 事件时出现了异常 | 如果写了 `@OnPipeMessageEvent` 注解事件,请检查对应注解事件。如果未设置,可能是框架内部错误,请将报错信息反馈开发者。 |
|
||||
| E00022 | 调用 `ProcessManager::sendActionToWorker()` 方法时,调用此方法的进程不是 Worker 或 Manager 进程 | 如果你在 Master 进程调用此方法会直接报此错误,框架不支持从 Master 进程调用此方法。 |
|
||||
| E00023 | 框架在收到 HTTP 请求后处理过程中出现了未捕获的异常 | 检查 HTTP 请求相关的注解解析代码,如果调用栈显示非用户代码所致,请将错误信息反馈开发者。 |
|
||||
| E00024 | 框架使用 `--watch` 时无法使用热更新并报错 | PHP 未安装 inotify 扩展,请使用 pecl 安装 inotify 扩展并启用后再试。 |
|
||||
| E00025 | 框架使用终端输入时产生了未捕获的异常或致命错误 | 检查 `@TerminalCommand` 注解事件或检查使用动态命令的内容(例如 bc 或 call 运行的代码或函数有没有错误)。 |
|
||||
| E00026 | 框架使用 `@OnTask` 注解在 TaskWorker 进程中执行函数抛出了异常 | 检查 TaskWorker 运行的任务代码是否会抛出未捕获的异常。 |
|
||||
| E00027 | 框架在运行过程中 Worker 进程发生未捕获的异常导致崩溃退出 | 见 [Issue #38](https://github.com/zhamao-robot/zhamao-framework/issues/38)。 |
|
||||
| E00028 | PHP 未安装 pdo_mysql(mysqlnd+PDO)扩展 | 安装 php-mysql(以 ubuntu 为例,apt install php-pdo php-mysql)。 |
|
||||
| E00029 | PHP 未安装 redis 扩展 | 安装 redis 扩展。 |
|
||||
| E00030 | 框架在 Worker 进程启动时出现错误 | 检查 `@OnStart` 相关事件的问题,或根据报错信息定位问题所在。此问题可能较常见,一般在启动时导致的。 |
|
||||
| E00031 | 框架在启动前解析代码出现错误 | 检查模块代码中是否有 PHP 语法错误。 |
|
||||
| E00032 | 上下文的 class 没有 implements ContextInterface 接口 | 如果从 global.php 设置了自定义上下文类,那么请检查上下文类有没有根据文档标准来编写接口。 |
|
||||
| E00033 | Worker 进程运行过程使用 `zm_*` 方法过程中抛出了未被捕获的异常 | 一般是由 `zm_go()` 或 `zm_timer_tik()` 造成的,协程或计时器内抛出了异常未被捕获。建议根据 trace 检查是什么地方抛出的异常。 |
|
||||
| E00034 | 由带中间件的 `@OnTick` 计时器产生了未被捕获的异常 | 建议检查计时器内的代码抛出异常位置,如果错误处理也是一部分功能,建议使用 `try catch` 自行捕获。 |
|
||||
| E00035 | CQ 码相关错误 | 根据提示检查调用 CQ 码的代码即可。 |
|
||||
| E00036 | OneBot WebSocket API 推送失败,可能是 WebSocket 客户端出现了问题 | 建议检查 OneBot 客户端和框架的连接是否正常。 |
|
||||
| E00037 | OneBot 机器人端连接未找到,或单例模式连接了多个机器人 | 根据提示信息进行修复,比如机器人 xxx 未连接到框架,就看一下 OneBot 客户端是否启用和配置正常。 |
|
||||
| E00038 | 图灵机器人 API 调用出错 | 根据提示和图灵错误码进行检查。 |
|
||||
| E00039 | 使用 build 命令时检测到目标目录不存在 | 重新指定一个存在的目录即可。 |
|
||||
| E00040 | 使用 build 命令时检测到 PHP 未设置 `phar.readonly=Off` | 修改 php.ini 将此项设置为 Off。 |
|
||||
| E00041 | 使用 init 命令时未检测到 composer.json 文件 | 检查引用框架的 composer.json 文件位置。 |
|
||||
| E00042 | 框架使用 init 命令时启动模式不是 Composer 模式 | 如果你是使用 git 且下载的仓库是 `zhamao-robot/zhamao-framework.git`,那么代表其以源码模式启动,详见[框架启动模式 - 炸毛框架 v2 (zhamao.xin)](https://framework.zhamao.xin/advanced/custom-start/)。 |
|
||||
| E00043 | MySQL 数据库出错,抛出异常 | 根据提示信息检查 MySQL 语句是否正确,数据库是否连接正常等,其他不能解决的问题建议反馈开发者。 |
|
||||
| E00044 | 打包模块过程中抛出了异常 | 根据提示文本进行修复错误的指令和代码即可。 |
|
||||
| E00045 | 打包模块过程中无法储存 `light-cache-store` 项指定的缓存数据 | 根据提示进行修复即可。 |
|
||||
| E00046 | Redis 连接池在使用过程中未提前初始化,可能是未设置全局配置文件启用 Redis 连接池 | 检查 global.php 是否设置 Redis 服务器。 |
|
||||
| E00047 | Redis 连接池初始化失败 | 根据提示报错信息进行修复,先检查 global.php 是否设置 Redis 服务器。 |
|
||||
| E00048 | LightCache 未初始化 | LightCache 会根据 global.php 初始化申请内存,如果申请出错请根据启动时的报错信息调整配置。 |
|
||||
| E00049 | LightCache 不能接收字符串、数组、int 之外的变量数据 | 检查传入的数据类型。 |
|
||||
| E00050 | 系统内存不足,LightCache 申请内存失败 | 让 PHP 可使用的内存或系统内存变大,也可以调小全局配置中设置的 LightCache 配置项。 |
|
||||
| E00051 | LightCache 的 Hash 冲突过多,导致无法动态空间分配内存 | 设置 `hash_conflict_proportion` 大一些(范围 0-1,默认是 0.6)。 |
|
||||
| E00052 | 在 /src/ 目录下不可以直接标记为模块(zm.json),因为命名空间不能为根空间 | 将模块标记文件 zm.json 放到子目录下,不能直接放在 src 目录下。 |
|
||||
| E00053 | 框架检测到了重名模块 | 更改模块名称。 |
|
||||
| E00054 | 打包好的模块文件(phar)内检测不到 zm.json 原始模块标记文件存在 | 检查 phar 模块是否完整。 |
|
||||
| E00055 | 打包好的模块文件(phar)不能正常读取模块标记文件(zm.json) | 检查 phar 模块是否完整。 |
|
||||
| E00056 | 未开启 TaskWorker 进程 | 请先修改 global 配置文件启用。 |
|
||||
| E00057 | 调用 `DataProvider::saveToJson()` 失败,因为传入了多级目录 | `saveToJson()` 方法的 `$filename` 参数只能最多到第二级子目录,不能有三级,例如 `foo/bar`。 |
|
||||
| E00058 | 调用 `DataProvider::scanDirFiles()` 失败,因为传入的 `$relative` 错误 | `$relative` 参数只能传入 `string/false` 两种类型。 |
|
||||
| E00059 | 使用 `MessageUtil::downloadCQImage()` 失败,因为指定下载的目录不存在 | 新建目录,检查目录地址是否是绝对路径,如果手动指定了目录,最好为绝对路径。 |
|
||||
| E00060 | 使用 `MessageUtil::downloadCQImage()` 失败,因为图片下载失败 | 检查下载图片的链接地址是否能正常的访问。 |
|
||||
| E00061 | 使用 `set_coroutine_params()` 失败,因为不能在非协程环境使用此函数 | 检查调用此函数的位置,注意不能在非协程环境(比如 Master 进程)下调用。 |
|
||||
| E00062 | 注解事件非法或不可回溯 | 不能在非注解调用的类中的方法调用 `EventTracer` 方法。 |
|
||||
| E00063 | 模块检测到依赖版本问题 | 检查是否部署或正确配置依赖的模块/插件版本。 |
|
||||
| E00064 | 模块系统检测到依赖的模块不存在或未安装部署 | 检查依赖的模块是否正确存在于源码目录。 |
|
||||
| E00065 | 模块系统检测到打包的模块文件中未含有 `light_cache_store.json` 文件 | 可能是打包此模块后打包的文件损坏,请询问原开发者打包一个新的没有损坏的 phar 文件。 |
|
||||
| E00066 | 模块打包时 `zmdata-store` 指定的文件或目录不存在 | 检查是否存在,检查写的相对路径是否有误(相对路径的初始路径为框架当前的 `zm_data` 配置的目录。 |
|
||||
| E00067 | 模块解包合并 `composer.json` 时没有找到项目原文件 | 检查项目的工作目录下是否有 `composer.json` 文件存在。 |
|
||||
| E00068 | 模块解包时无法正常拷贝文件 | 检查文件夹是否正常可以创建和写入。 |
|
||||
| E00069 | 框架不能启动两个 ConsoleApplication 实例 | 不要重复使用 `new ConsoleApplication()`。 |
|
||||
| E00070 | 框架找不到 `zm_data` 目录 | 检查配置中指定的 `zm_data` 目录是否存在。 |
|
||||
| E00071 | 框架找不到对应类型的 API 调用类 | 检查 `getExtendedAPI($name)` 传入的 `$name` 是否正确 |
|
||||
| E00072 | 上下文无法找到 | 检查上下文环境,如是否处于协程环境中 |
|
||||
| E00073 | 在类中找不到方法 | 检查调用对象是否存在对应的方法(method)或检查是否插入了对应的macro宏方法 |
|
||||
| E00074 | 参数非法 | 检查调用的参数是否正常(此处可能有多处问题,请看具体调用栈炸掉的地方) |
|
||||
| E00075 | Cron表达式非法 | 检查 @Cron 中的表达式是否书写格式正确 |
|
||||
| E00076 | Cron检查间隔非法 | 检查 @Cron 中的检查间隔(毫秒)是否在1000-60000之间 |
|
||||
| E00077 | 输入了非法的消息匹配参数类型 | 检查传入 `@CommandArgument` 或 `checkArguments()` 方法有没有传入非法的 `type` 参数 |
|
||||
| E00078 | 配置文件读取失败,未找到 base 配置文件 | 检查配置文件是否存在(一般是由于没找到基类配置文件产生的此错误) |
|
||||
| E00079 | 配置文件读取失败 | 根据报错信息修复配置文件(或将 debug 日志发给框架开发者或反馈 Issue) |
|
||||
| E00080 | 调用 `FileSystem::scanDirFiles()` 失败,因为传入的不是目录或无法读取 | 使用 scanDirFiles() 传入一个可读取的目录 |
|
||||
| E00081 | 传入了未知类型的 Driver | 使用受支持的 Driver 名称,例如 `swoole` 或 `workerman`(如需支持其他驱动,请反馈) |
|
||||
| E99999 | 未知错误 | |
|
||||
76
docs/guide/get_started.md
Normal file
76
docs/guide/get_started.md
Normal file
@ -0,0 +1,76 @@
|
||||
# 快速上手
|
||||
|
||||
在这里,我们将会以一个基础的复读机为例,帮助你快速熟悉框架的开发流程。
|
||||
|
||||
在开始之前,你需要先准备一个可用的 OneBot 机器人实现端(客户端)。
|
||||
|
||||
其中一些可用的选项为:
|
||||
|
||||
- Walle-Q
|
||||
- 更多…
|
||||
|
||||
## 机器人实现端
|
||||
|
||||
> 机器人实现端与炸毛框架相互独立。关于实现端本身的问题请向对应的开发者反馈。如果你确定相关问题由炸毛框架引起(例如缺少适配或代码问题等)请向我们报告。
|
||||
|
||||
框架支持多种通信方式,这里将以反向 WebSocket 为例,即框架充当 WS 服务端,实现端作为 WS 客户端连接到框架。
|
||||
|
||||
这里以 Walle-Q 实现端为例,在实际使用中,你可以自由选用不同的实现端。
|
||||
|
||||
你可以前往 Walle-Q 的[发布页面](https://github.com/onebot-walle/walle-q/releases)下载最新的发行版本,并运行以进行初始化。
|
||||
|
||||
在登录成功后,请关闭 Walle-Q 以修改配置文件。
|
||||
|
||||
首次运行后,将会在当前目录下生成 `walle-q.toml` 文件。在大多数场景下,这是你唯一需要接触的文件。
|
||||
|
||||
在于框架对接的情况中,我们只需要关注 `onebot.websocket_rev` 配置。
|
||||
|
||||
```toml
|
||||
[onebot]
|
||||
http = []
|
||||
http_webhook = []
|
||||
websocket = []
|
||||
|
||||
[[onebot.websocket_rev]]
|
||||
url = "ws://127.0.0.1:20001" # 这里是框架的监听地址
|
||||
reconnect_interval = 4
|
||||
```
|
||||
|
||||
修改完成并保存后,重新启动 Walle-Q 并登录即可。如果出现连接失败也请勿惊慌,因为框架此时尚未启动,失败是正常现象。
|
||||
|
||||
## 编写第一个功能
|
||||
|
||||
在框架中,几乎所有事件的绑定都是通过注解进行的,详情可以参阅 注解的使用。
|
||||
|
||||
让我们在 `src/Module/Repeater.php` 中开发我们的第一个功能。
|
||||
|
||||
```php
|
||||
namespace Module;
|
||||
|
||||
class Repeater
|
||||
{
|
||||
#[BotCommand('echo')]
|
||||
public function repeat(OneBotEvent $event, BotContext $context): void
|
||||
{
|
||||
$context->reply($event->getMessage());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
借助容器的依赖注入功能,我们可以直接指定相应的类,相关实例会在调用时自动传入。
|
||||
|
||||
## 启动框架
|
||||
|
||||
在保存了上述的代码后,你就可以通过 `./zhamao server` 启动框架了。
|
||||
|
||||
启动后,Walle-Q 的日志应当会显示连接成功的信息。
|
||||
|
||||
此时,你可以通过任意账号向机器人发送 `echo 给我复读` 消息,机器人会回复 `给我复读`。
|
||||
|
||||
至此,你的第一个功能,复读机,也就开发完成了。
|
||||
|
||||
## 使用机器人 API 和更多事件
|
||||
|
||||
如果你希望机器人进行其他复杂的动作(操作),请参见 机器人 API。
|
||||
|
||||
关于更多可以注册绑定的事件,请参见 注解事件。
|
||||
@ -1,88 +1,46 @@
|
||||
# 安装
|
||||
|
||||
> 这篇为炸毛框架以及环境的部署教程。
|
||||
我们希望尽可能轻松地使用炸毛,不论是本地开发或是部署都提供多种选择,尽量覆盖所有需求。
|
||||
|
||||
框架部署分为两部分,一部分是安装 PHP 环境,另一部分是通过 Composer 或 GitHub 拉取框架的脚手架。
|
||||
- 一键脚本
|
||||
- Docker
|
||||
- Composer
|
||||
|
||||
## 一键下载静态 PHP 环境和框架脚手架
|
||||
## 一键脚本
|
||||
|
||||
从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架(如果本机内安装的 PHP 已符合要求,则不安装),只需运行下面的脚本即可。
|
||||
炸毛框架提供了一键脚本来设置运行环境并拉取脚手架,帮助你快速进行开发。
|
||||
|
||||
```bash
|
||||
# 将会把 PHP、框架都安装在此目录下
|
||||
如果检测到本机未安装 PHP 或不符合运行要求,脚本将会自动拉取提前编译好的静态 PHP 运行时。
|
||||
|
||||
```shell
|
||||
# 将静态 PHP 和框架安装在当前目录
|
||||
bash <(curl -fsSL https://zhamao.xin/go.sh)
|
||||
|
||||
# 安装完成后的启动框架命令(2.5.0 版本后可省略掉 runtime/php 前缀)
|
||||
cd zhamao-app
|
||||
# 安装完成后启动
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
> 有关静态 PHP 的多种用法(如 Composer),见 [进阶 - PHP 环境高级](/advanced/php-env)
|
||||
> 关于静态运行时的更多用法,请参见 这里是链接
|
||||
|
||||
## 使用 Docker 部署 PHP 和框架
|
||||
你也可以使用 Docker 进行拉取 PHP 环境。
|
||||
## Docker
|
||||
|
||||
```bash
|
||||
# 拉取 Docker 镜像
|
||||
docker pull zmbot/swoole
|
||||
待完善
|
||||
|
||||
# 再通过 GitHub 或其他方式拉取框架脚手架
|
||||
git clone --depth=1 https://github.com/zhamao-robot/zhamao-framework-starter.git
|
||||
cd zhamao-framework-starter/
|
||||
## Composer
|
||||
|
||||
# Docker 内使用 Composer 更新依赖
|
||||
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update
|
||||
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start init
|
||||
如果你已经有了必要的 PHP 环境和 Composer 工具,你可以直接在任意目录下初始化框架。
|
||||
|
||||
# 使用 Docker 启动框架
|
||||
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server
|
||||
> 由于目前 3.0 仍处于预发布阶段,请使用 `composer require zhamao/framework:^3` 来安装
|
||||
|
||||
```shell
|
||||
# 在当前目录初始化框架
|
||||
composer require zhamao/framework
|
||||
./vendor/bin/zhamao init
|
||||
|
||||
## 安装完成后启动
|
||||
./zhamao server
|
||||
```
|
||||
|
||||
启动后你会看到和下方类似的初始化内容,表明启动成功了
|
||||
## 更多的环境部署和开发方式
|
||||
|
||||
```verilog
|
||||
$ vendor/bin/start server
|
||||
=================================================================
|
||||
working_dir: /app/zhamao-framework-starter
|
||||
listen: 0.0.0.0:20001 | worker: 4 (auto)
|
||||
environment: default | log_level: 2
|
||||
version: 2.7.0 | master_pid: 28449
|
||||
=================================================================
|
||||
______
|
||||
|__ / |__ __ _ _ __ ___ __ _ ___
|
||||
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
|
||||
/ /_| | | | (_| | | | | | | (_| | (_) |
|
||||
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
|
||||
|
||||
[03-20 22:30:56] [S] [#1] Worker #1 started
|
||||
[03-20 22:30:56] [S] [#2] Worker #2 started
|
||||
[03-20 22:30:56] [S] [#3] Worker #3 started
|
||||
[03-20 22:30:56] [S] [#0] Worker #0 started
|
||||
```
|
||||
|
||||
单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问:http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。
|
||||
|
||||
## 命令总结
|
||||
|
||||
1. 对于框架的启动,必须 cd 到项目的跟目录,比如 `cd zhamao-app/` 进入到项目根目录。
|
||||
2. 无论何种方式启动,启动框架的命令格式都为这个格式:`{php二进制路径} vendor/bin/start server {--如果需要参数的话这样跟}`
|
||||
3. 第二条的 `php 二进制路径` 指的是,比如使用第一种静态 PHP 环境,这里写 `runtime/php` 就好了,如果是安装到系统的 PHP 的话,这里为空,如果是 Docker 部署的环境,则这里填 `docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`
|
||||
|
||||
## 使用 IDE 等工具开发代码
|
||||
|
||||
我们使用文本编辑器进行炸毛框架开发,在使用集成开发环境 **IDEA** 或 **PhpStorm** 时,推荐通过插件市场搜索并安装 **PHP Annotations** 插件以提供注解命名空间自动补全、注解属性代码提醒、注解类跳转等,非常有助于提升开发效率的功能。
|
||||
|
||||
## 进阶环境部署和开发
|
||||
炸毛框架还支持更多种启动方式,如源码模式、守护进程模式,具体后续有关环境和部署的进阶教程,请查看 [进阶开发](/advanced/) 部分!
|
||||
|
||||
## Windows 注意事项
|
||||
|
||||
由于 Swoole 扩展目前无法原生支持 Windows 环境的 PHP,所以以上方式都是默认在 Linux、macOS 系统下的命令。
|
||||
|
||||
如果需要在 Windows 上开发和运行,可以使用 WSL(1 和 2 均可)、Linux 虚拟机、Docker 或 cygwin。
|
||||
|
||||
如果使用 WSL、虚拟机或 Docker,方式可以直接参考上方相关命令。如果使用 cygwin,可先从 [Swoole 官方仓库](https://github.com/swoole/swoole-src) 下载 swoole 的 cygwin 构建版本,然后下载 Composer 后安装依赖,直接运行框架即可。
|
||||
|
||||
## macOS 注意事项
|
||||
|
||||
macOS 理论上运行环境和 Linux 无异,但 macOS 由于不能静态编译,所以不能使用静态编译的 PHP 直接运行,需自行从 `Homebrew` 下载最新版 PHP(命令 `brew install php`),然后使用命令 `pecl install swoole` 来安装 Swoole 后运行框架。
|
||||
除了上述方式之外,框架还支持源码模式、守护进程等运行方式,详情请参阅 [进阶开发]。
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
# OneBot 实例
|
||||
|
||||
## 什么是 OneBot
|
||||
|
||||
OneBot 是一个聊天机器人应用接口标准,详情戳 [这里](https://github.com/howmanybots/onebot)。
|
||||
|
||||
## OneBot 实现选择
|
||||
|
||||
如果你使用炸毛框架作为聊天机器人的开发框架,请先选择一种兼容 OneBot 标准的机器人接口。理论上,基于 OneBot 标准开发的**任何** SDK、框架和机器人应用,都可以无缝地在下面的不同实现中切换。当然,在一小部分细节上各实现可能有一些不同。
|
||||
|
||||
| 项目地址 | 平台 | 核心作者 | 备注 |
|
||||
| ------------------------------------------------------------ | --------------------------------------------- | -------------- | ------------------------------------------------------------ |
|
||||
| [richardchien/coolq-http-api](https://github.com/richardchien/coolq-http-api) | CKYU | richardchien | 可在 Mirai 平台使用 [mirai-native](https://github.com/iTXTech/mirai-native) 加载 |
|
||||
| [Mrs4s/go-cqhttp](https://github.com/Mrs4s/go-cqhttp) | [MiraiGo](https://github.com/Mrs4s/MiraiGo) | Mrs4s | 炸毛框架推荐使用此项目机器人应用 |
|
||||
| [yyuueexxiinngg/cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) | [Mirai](https://github.com/mamoe/mirai) | yyuueexxiinngg | |
|
||||
| [takayama-lily/onebot](https://github.com/takayama-lily/onebot) | [OICQ](https://github.com/takayama-lily/oicq) | takayama | |
|
||||
| [ProtobufBot](https://github.com/ProtobufBot) | [Mirai](https://github.com/mamoe/mirai) | lz1998 | 事件和 API 数据内容和 OneBot 一致,通信方式不兼容 |
|
||||
|
||||
::: warning 注意
|
||||
|
||||
因为目前炸毛框架 2.0 只支持 WebSocket 方式的 OneBot 实现,所以目前上述项目的连接方式均只可选支持反向 WebSocket 通信的。后期会兼容 HTTP 和正向 WebSocket 通信方式。
|
||||
|
||||
:::
|
||||
|
||||
如果你还没有自己的 QQ,或者是其他原因导致的暂时无法使用上述 OneBot 实例,可以使用炸毛项目中的 OneBot 协议聊天模拟器。但目前还处在开发中,暂不可用。
|
||||
@ -1,9 +0,0 @@
|
||||
# 快速上手 - HTTP 服务器篇
|
||||
|
||||
HTTP 服务器篇主要讲解如何通过炸毛框架来实现微服务、API 通用接口等等这些东西的。
|
||||
|
||||
- [HTTP 服务器 - 路由和静态文件篇](/event/route-annotations/)
|
||||
- [HTTP 服务器 - 存储 - LightCache 轻量缓存](/component/light-cache/)
|
||||
- [HTTP 服务器 - 存储 - Redis](/component/redis/)
|
||||
- [HTTP 服务器 - 存储 - MySQL](/component/mysql/)
|
||||
- [HTTP 客户端](/component/zmrequest/)
|
||||
@ -1,175 +0,0 @@
|
||||
# 快速上手 - 机器人篇
|
||||
|
||||
## 简介
|
||||
|
||||
看到这里,你已经完成了前面的环境部署,到了最关键的第一步了!
|
||||
|
||||
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 **机器人问答模块** 的准备。
|
||||
|
||||
炸毛框架和机器人客户端是什么关系呢?炸毛框架就好比我们传统的一系列例如 Spring 框架、ThinkPHP 框架等,是服务端,而机器人客户端是一个 HTTP / WebSocket 客户端,时刻准备着连接到炸毛框架。
|
||||
|
||||
## 机器人客户端
|
||||
|
||||
开发之前请注意,**机器人客户端和框架相互独立!**故有关**机器人客户端**出现的问题请到对应机器人客户端开发者或 GitHub 项目中咨询和讨论,炸毛框架为对接机器人客户端的一个快速开发的框架。
|
||||
|
||||
机器人客户端是炸毛框架以外的程序或软件,目前炸毛框架支持的机器人客户端通信标准为 OneBot 标准(原 CQHTTP),只要你的机器人客户端是 OneBot 标准的,就可以和炸毛框架进行无缝对接。
|
||||
|
||||
OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)。
|
||||
|
||||
这里以炸毛框架开发过程中使用的 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 来举例进行第一个机器人的配置工作。
|
||||
|
||||
简要步骤描述为:
|
||||
|
||||
1. 下载 go-cqhttp 对应平台的 [release 文件](https://github.com/Mrs4s/go-cqhttp/releases)
|
||||
2. 双击 exe 文件或者使用 `./go-cqhttp` 启动
|
||||
3. 生成默认配置文件并修改默认配置
|
||||
|
||||
::: warning 注意
|
||||
|
||||
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`。
|
||||
|
||||
:::
|
||||
|
||||
config.yml(最新格式):
|
||||
|
||||
```yaml {2,3,78,83}
|
||||
account: # 账号相关
|
||||
uin: 1233456 # QQ账号
|
||||
password: '' # 密码为空时使用扫码登录
|
||||
encrypt: false # 是否开启密码加密
|
||||
status: 0 # 在线状态 请参考 https://github.com/Mrs4s/go-cqhttp/blob/dev/docs/config.md#在线状态
|
||||
relogin: # 重连设置
|
||||
delay: 3 # 首次重连延迟, 单位秒
|
||||
interval: 3 # 重连间隔
|
||||
max-times: 0 # 最大重连次数, 0为无限制
|
||||
|
||||
# 是否使用服务器下发的新地址进行重连
|
||||
# 注意, 此设置可能导致在海外服务器上连接情况更差
|
||||
use-sso-address: true
|
||||
|
||||
heartbeat:
|
||||
# 心跳频率, 单位秒
|
||||
# -1 为关闭心跳
|
||||
interval: 5
|
||||
|
||||
message:
|
||||
# 上报数据类型
|
||||
# 可选: string,array
|
||||
post-format: string
|
||||
# 是否忽略无效的CQ码, 如果为假将原样发送
|
||||
ignore-invalid-cqcode: false
|
||||
# 是否强制分片发送消息
|
||||
# 分片发送将会带来更快的速度
|
||||
# 但是兼容性会有些问题
|
||||
force-fragment: false
|
||||
# 是否将url分片发送
|
||||
fix-url: false
|
||||
# 下载图片等请求网络代理
|
||||
proxy-rewrite: ''
|
||||
# 是否上报自身消息
|
||||
report-self-message: false
|
||||
# 移除服务端的Reply附带的At
|
||||
remove-reply-at: false
|
||||
# 为Reply附加更多信息
|
||||
extra-reply-data: false
|
||||
|
||||
output:
|
||||
# 日志等级 trace,debug,info,warn,error
|
||||
log-level: warn
|
||||
# 是否启用 DEBUG
|
||||
debug: false # 开启调试模式
|
||||
|
||||
# 默认中间件锚点
|
||||
default-middlewares: &default
|
||||
# 访问密钥, 强烈推荐在公网的服务器设置
|
||||
access-token: ''
|
||||
# 事件过滤器文件目录
|
||||
filter: ''
|
||||
# API限速设置
|
||||
# 该设置为全局生效
|
||||
# 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
|
||||
# 目前该限速设置为令牌桶算法, 请参考:
|
||||
# https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
|
||||
rate-limit:
|
||||
enabled: false # 是否启用限速
|
||||
frequency: 1 # 令牌回复频率, 单位秒
|
||||
bucket: 1 # 令牌桶大小
|
||||
|
||||
database: # 数据库相关设置
|
||||
leveldb:
|
||||
# 是否启用内置leveldb数据库
|
||||
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
|
||||
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
|
||||
enable: true
|
||||
|
||||
# 连接服务列表
|
||||
servers:
|
||||
# 添加方式,同一连接方式可添加多个,具体配置说明请查看文档
|
||||
#- http: # http 通信
|
||||
#- ws: # 正向 Websocket
|
||||
#- ws-reverse: # 反向 Websocket
|
||||
#- pprof: #性能分析服务器
|
||||
# Zhamao Framework 所需要的服务器配置
|
||||
- ws-reverse:
|
||||
# 是否禁用当前反向WS服务
|
||||
disabled: false
|
||||
# 反向WS Universal 地址
|
||||
# 注意 设置了此项地址后下面两项将会被忽略
|
||||
universal: ws://127.0.0.1:20001/
|
||||
# 反向WS API 地址
|
||||
api: ws://your_websocket_api.server
|
||||
# 反向WS Event 地址
|
||||
event: ws://your_websocket_event.server
|
||||
# 重连间隔 单位毫秒
|
||||
reconnect-interval: 3000
|
||||
middlewares:
|
||||
<<: *default # 引用默认中间件
|
||||
```
|
||||
|
||||
其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT
|
||||
|
||||
## 第一次对话
|
||||
|
||||
一旦新的配置文件正确生效之后,所在的控制台(如果正在运行的话)应该会输出类似下面的内容:
|
||||
|
||||
```verilog
|
||||
[15:26:34] [I] [#2] 机器人 你的QQ号 已连接!
|
||||
```
|
||||
|
||||
表明机器人已成功连接到炸毛框架了!
|
||||
|
||||
这时,如果你是根据安装教程走下来并且未编写任何模块,炸毛自带一个示例模块,里面含有命令:`你好`,`随机数`。如果你对机器人回复:`你好`,它会回复你 `你好啊,我是由炸毛框架构建的机器人!`。这一历史性的对话标志着你已经成功地运行了炸毛框架,开始了编写更强大的 QQ 机器人的创意之旅!
|
||||
|
||||
## 编写一个命令
|
||||
|
||||
让我们转到框架的模块源代码部分,目录是 `src/Module/Example`,文件是 `Hello.php`。我们插入一段这样的代码:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @CQCommand("echo")
|
||||
*/
|
||||
public function repeat() {
|
||||
$repeat = ctx()->getFullArg("请输入你要回复的内容");
|
||||
ctx()->reply($repeat);
|
||||
//return $repeat; // 这样的效果等同于 ctx()->reply()
|
||||
}
|
||||
```
|
||||
|
||||
这样,一个简易的复读机就做好了!回到 QQ 机器人聊天,向机器人发送 `echo 你好啊`,它会回复你 `你好啊`。
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'echo 你好啊'},
|
||||
{type:1,content:'你好啊'},
|
||||
{type:0,content:'echo'},
|
||||
{type:1,content:'请输入你要回复的内容'},
|
||||
{type:0,content:'哦豁'},
|
||||
{type:1,content:'哦豁'},
|
||||
]"></chat-box>
|
||||
|
||||
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。
|
||||
|
||||
## 使用机器人 API 和事件
|
||||
|
||||
如果你想不只是回复消息,还要做其他复杂的动作(Action),使用 OneBot Action(又名 OneBot API)进行发送即可,见 [机器人 API](/component/bot/robot-api)。
|
||||
|
||||
如果想处理其他类型的事件,比如 QQ 群通知事件等,见 [机器人注解事件](/event/robot-annotations/)。
|
||||
@ -1,69 +0,0 @@
|
||||
# 注册事件响应(机器人篇)
|
||||
|
||||
现在模块已经创建完毕,我们可以开始编写实际代码了。本段以机器人会话为例子来讲述事件注册和响应,有关 HTTP 服务器等注册事件响应请看后面事件和注解章节。
|
||||
|
||||
## 机器人聊天事件处理
|
||||
|
||||
首先知道,QQ 等聊天机器人的消息我们的处理逻辑为如下简单的模式:
|
||||
|
||||
- QQ 用户消息 -> 机器人客户端 -> 连接客户端的框架(炸毛框架)
|
||||
- 框架处理逻辑后返回给用户的消息 -> 机器人客户端 -> QQ用户
|
||||
|
||||
第一步,我们以框架这边的角度考虑,我们称之为“事件”,我们编写代码所做的就是要响应这一事件。
|
||||
|
||||
首先我们以一句简单的功能——查天气,我们要从零实现一个查天气的功能进行示范如何快捷有效地开发一个功能。
|
||||
|
||||
### 确定问法
|
||||
|
||||
我们首先要确定的用户问法是一般由我们自己定义,但最好贴合用户的自然语言来进行定义。比如我们这里提到的天气功能,用户一般就会询问“北京天气”,“北京天气怎么样”,“天气 北京”。
|
||||
|
||||
### 注册消息事件
|
||||
|
||||
我们以最简单的命令方式“天气 北京”进行处理。问法为参数化的,通过空格来分开,这也是炸毛框架默认支持最基本的聊天事件之一。我们通过上一部分的方式新建一个单文件模块 `Weather.php` 在 `src/Module` 目录下,并编写:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
class Weather {
|
||||
/**
|
||||
* @CQCommand("天气")
|
||||
* @return string
|
||||
*/
|
||||
public function searchWeather() {
|
||||
$city = ctx()->getNextArg("请告诉我你要查询的城市"); // 发送 “天气 北京”时,变量为“北京”
|
||||
// 这里假设是天气API接口的对接,返回了天气的数据
|
||||
$weather = "2020年12月22日,晴,-2~9℃ blablabla";
|
||||
return "$city 天气情况:".$weather;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
::: tip 提示
|
||||
|
||||
为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API,本教程的进阶一栏后期会详细编写如何对接一个天气 API 接口。
|
||||
|
||||
:::
|
||||
|
||||
在上方代码编写完毕后,运行框架(或运行过程中终端输入 `reload`),然后使用机器人客户端连接到炸毛框架,即可实现我们的第一个功能。我们在代码中编写了一个**注解事件**:`@CQCommand`,此注解事件是用于接收用户的普通消息并切分成各类命令规则的一个注解事件绑定。代码中的注解事件省去了注解中的键名,也可以写作 `@CQCommand(match="天气")`。
|
||||
|
||||
这里注解事件的概念请看 [事件和注解](/event/) 一栏描述的概念即可。到这里,我们就完成一个可以处理命令 `天气 xxx` 的方法了!
|
||||
|
||||
### 处理消息事件
|
||||
|
||||
第一行 `ctx()` 是炸毛框架内的上下文获取方式,每条用户聊天信息发过来,被炸毛框架收到,都会创建一次上下文,同时这次聊天的全部信息,比如用户的 ID(QQ 号码),发消息的时间,如果是群消息的话所在的群号等等,都被存到了上下文中。`ctx()` 获取的是一个上下文对象,内部有许多可操作上下文的方法,其中代码的 `getNextArg()`,作用是根据空格分隔获取命令中的下一个参数。
|
||||
|
||||
在之前我们知道:`天气 北京` 是我们发送的消息,我们要获取到用户发送的参数 `北京`。`getNextArg()` 是框架封装好的一个快速获取下一个参数的方法,我们这里直接使用它来获取。
|
||||
|
||||
对于 `getNextArg()` 中的文本,可为空,不为空的时候如果用户只发送天气两个字,机器人还是会响应,但是它会询问你这句话,然后你回复机器人“北京”,这里 `$city` 变量就接受到并赋值为“北京”了,代码会继续执行,和直接一次性发送机器人“天气 北京”是一个效果,此为框架封装的消息会话机制,以贴近自然会话的方式来编写代码逻辑。
|
||||
|
||||
最后,函数直接返回了一个字符串,作为事件的响应,炸毛框架会自动处理并调用机器人客户端的接口,最后返回给用户消息。这里也可以使用上下文的 `ctx()->reply("xxx")` 方法替代,不返回字符串。注意两者只能选择一种方式,取决于开发者的开发习惯。
|
||||
|
||||
<chat-box :my-chats="[
|
||||
{type:0,content:'天气 北京'},
|
||||
{type:1,content:'北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla'},
|
||||
{type:0,content:'天气'},
|
||||
{type:1,content:'请告诉我你要查询的城市'},
|
||||
{type:0,content:'北京'},
|
||||
{type:1,content:'北京 天气情况:2020年12月22日,晴,-2~9℃ blablabla'},
|
||||
]"></chat-box>
|
||||
48
docs/guide/structure.md
Normal file
48
docs/guide/structure.md
Normal file
@ -0,0 +1,48 @@
|
||||
# 目录结构
|
||||
|
||||
## 根目录
|
||||
|
||||
### Config 目录
|
||||
|
||||
`config` 目录包含框架、应用的所有配置文件。最好把这些文件都浏览一遍,并熟悉所有可用的选项。
|
||||
|
||||
### Src 目录
|
||||
|
||||
`src` 目录包含应用的核心代码,你的大部分工作都将在这里进行。
|
||||
|
||||
### Tests 目录
|
||||
|
||||
`tests` 目录通常是你编写 PHPUnit 单元测试和功能测试的地方。你可以使用 `composer test` 运行其中的测试。
|
||||
|
||||
> 该目录并不自带
|
||||
>
|
||||
|
||||
### Vendor 目录
|
||||
|
||||
`vendor` 目录包含你通过 Composer 安装的所有依赖。
|
||||
|
||||
## Src 目录
|
||||
|
||||
你的大多数代码都位于 `src` 目录中。
|
||||
|
||||
### Globals 目录
|
||||
|
||||
`globals` 目录包含你的全局定义文件,例如全局函数和常量等。
|
||||
|
||||
需要注意的是,框架本身并不会为你自动加载其中的文件,你需要自行使用 Composer 自动加载或其他方式加载其中的代码。
|
||||
|
||||
例如 `Globals/my_functions.php` 可以被添加到 `composer.json` 当中。
|
||||
|
||||
```json
|
||||
{
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/Globals/my_functions.php"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Module 目录
|
||||
|
||||
`module` 目录包含你机器人或是服务的主体代码,其中的所有类都会被框架自动扫描并解析,你可以在其中利用注解来注册事件绑定并进行相应处理。
|
||||
@ -1,37 +0,0 @@
|
||||
# 升级指南
|
||||
|
||||
因为框架在随着需求以及 Bug 在不断更新,所以在未来框架会发布新版本。为了方便从旧版本安装并使用框架的开发者无损更新到新版本,这里提供了升级版本上需要注意的事项。
|
||||
|
||||
## 版本约定
|
||||
|
||||
炸毛框架的版本号一般情况均按照 [Semantic Versioning 2.0.0](https://semver.org/) 标准进行滚动发行,规则简述如下:
|
||||
|
||||
假设版本号为 x.y.z:
|
||||
|
||||
- `x` 为大版本,一般只有在发生完全无法兼容的更新时增加,需要开发者最重视。
|
||||
- `y` 为小发行版本,默认情况下会新增组件功能,但会尽可能兼容旧版本,存在不兼容情况极少。
|
||||
- `z` 为补丁版本,在不进行任何大功能更新情况下提供 Bug 的修复,完全兼容前版本。
|
||||
|
||||
例如,炸毛框架的 `2.4.2` 版本,在 `2.5.0` 发行后,框架提供了大量新组件,但是对旧版本的配置和组件完全兼容,无任何额外的说明,则可以直接升级。
|
||||
|
||||
## 升级方法
|
||||
|
||||
根据安装方法不同,升级的方法也不同。
|
||||
|
||||
框架安装方式有多种,但主要分为三类:
|
||||
|
||||
- Composer 加载库的方式
|
||||
- 框架源码模式
|
||||
- Phar 打包模式
|
||||
|
||||
在 Composer 加载库的方式下,一般是指使用命令 `composer require zhamao/framework` 或 `composer create-project zhamao/framework-starter` 的方式安装框架,框架的核心文件都在 `vendor` 目录下。
|
||||
|
||||
此方式安装的框架升级最方便,直接执行命令 `composer update` 即可。
|
||||
|
||||
框架源码模式安装一般为直接使用 `git clone` 框架本体的 GitHub 仓库或下载 master 分支安装,这种情况不可升级版本(或使用 `git pull` 拉取)。
|
||||
|
||||
Phar 打包模式更新则必须重新自行打包新版本,例如从 Composer 加载库方式打包的框架,则需在原目录使用 `composer update` 后再次打包一个新版本。
|
||||
|
||||
## 升级提示
|
||||
|
||||
如果在升级过程中遇到了提示,则可能是需要升级某些配置文件需要手动进行合并更新。如果提示了更新,建议到 `vendor/zhamao/framework/config/global.php` 框架的最新库内配置文件与 `config/global.php` 文件进行对比。
|
||||
@ -1,75 +0,0 @@
|
||||
# 编写模块
|
||||
|
||||
到现在为止,我们还在使用框架的默认模块 `Example/Hello.php`,在开始编写自己的模块应用之前,我们先说明一些编写代码的约定。
|
||||
|
||||
## 加载模块
|
||||
|
||||
框架默认使用脚手架构建好后,目录结构大致为下面这样:
|
||||
|
||||
```
|
||||
zhamao-framework-starter/
|
||||
├── config/ # 项目的配置文件文件夹,如 global.php
|
||||
├── src/ # 项目的主要源码目录
|
||||
│ ├── Module/ # 用户编写的模块目录
|
||||
│ │ └── Example/ # 模块文件夹名称
|
||||
│ │ └── Hello.php # 模块内的类
|
||||
│ └── Custom/ # 用户自定义的全局方法、全局注解类等存放的目录
|
||||
├── vendor/ # Composer 依赖加载目录
|
||||
└── composer.json # Composer 配置文件
|
||||
```
|
||||
|
||||
其中我们脚手架包含的默认模块 `Example` 下的 `Hello` 类,就是用户写模块的位置。你也可以根据实际情况,自行添加更多的模块文件夹甚至单文件模块。
|
||||
|
||||
需要注意的是,所有文件夹名称和 `.php` 文件必须遵循 [psr-4 规范](https://learnku.com/docs/psr/psr-4-autoloader/1608),简单来说,`src/` 目录下的文件夹,子文件夹要写成命名空间,比如默认框架中 `Example/` 下的 `.php` 文件的命名空间为 `namespace Module\Example;`,且一个 `.php` 文件推荐只包含一个 `class`、`trait` 或 `interface`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\<your-module-dir>;
|
||||
class ModuleA {}
|
||||
```
|
||||
|
||||
::: danger 警告
|
||||
|
||||
如果没有遵守上方的类和文件命名规则的话(文件名、文件夹名和命名空间的统一性),在加载框架时就会报错,无法找到对应的类。因为框架的注解解析依赖于 Composer 中 psr-4 规则的自动加载。
|
||||
|
||||
:::
|
||||
|
||||
## 创建模块
|
||||
|
||||
### 标准形式
|
||||
|
||||
我们这里以 `Entertain` 娱乐模块的创建为例,新建一个内有 `Dice.php` 掷骰子功能的模块,目录结构如下,在 `Module/` 下新建文件夹 `Entertain/`,再在此子目录下新建 `Dice.php` 文件。
|
||||
|
||||
```
|
||||
zhamao-framework-starter/
|
||||
└── src/
|
||||
└── Module/
|
||||
└── Entertain/
|
||||
└── Dice.php
|
||||
```
|
||||
|
||||
新建的 PHP 文件按照如下方式编写:
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Entertain;
|
||||
class Dice {
|
||||
}
|
||||
```
|
||||
|
||||
这个时候它已经可以被称为一个模块了,尽管它还什么都没做。
|
||||
|
||||
### 单文件形式
|
||||
|
||||
如果你只开发很简单的一些功能,如一个 PHP 文件就可以实现的,可以少去创建模块文件夹的一步,直接将 `.php` 文件新建到 `Module/` 文件夹下,这时此文件的命名空间需要更正为 `namespace Module;` 即可,而文件夹结构也更加简单:
|
||||
|
||||
```
|
||||
zhamao-framework-starter/
|
||||
└── src/
|
||||
└── Module/
|
||||
└── Dice.php
|
||||
```
|
||||
|
||||
### Composer 外部引入形式
|
||||
|
||||
(暂未支持,敬请期待,TODO:已经支持了,但缺少文档编写)
|
||||
@ -1,320 +0,0 @@
|
||||
# 更新日志(master 分支 commit)
|
||||
|
||||
此文档将显示非发布版的提交版本相关更新文档,可能与发布版的更新日志有重合,在此仅作更新记录。
|
||||
|
||||
同时此处将只使用 build 版本号进行区分。
|
||||
|
||||
## build 473 (2022-5-7)
|
||||
|
||||
- 修复 `server:stop` 命令下部分情况报错的问题
|
||||
|
||||
## build 472 (2022-5-6)
|
||||
|
||||
- 修复 Container 环境继承全局变量的问题
|
||||
|
||||
## build 471 (2022-5-5)
|
||||
|
||||
- 修复 `CQ::encode()` 无法传入 `int` 的强类型解析问题(#113)
|
||||
- (内部)重构 CQ 类
|
||||
- 新增启动命令参数 `--audit-mode`,用于单次审计模式
|
||||
- 修复 `EventMapIterator` 对 PHP 8.1 的兼容性问题
|
||||
- 修复 #95 中提到的无输入流时报错的问题
|
||||
- 新增部分不可执行脚本的防呆退出功能
|
||||
- 修复 `ZMServer` 中的 typo
|
||||
|
||||
## build 470 (2022-5-4)
|
||||
|
||||
- 重构帮助生成器,将帮助生成器重构为 `CommandInfoUtil` 类
|
||||
|
||||
## build 469 (2022-5-3)
|
||||
|
||||
- 新增 `@CommandArgument` 注解,可直接通过注解添加聊天机器人命令参数
|
||||
- 修改默认 Hello 模块下随机数功能为采用 `@CommandArgument` 注解模式
|
||||
- (内部)新增 `EventManager::$event_map`,用于补充对事件对象遍历的方式
|
||||
- 新增 `EventMapIterator` 类,用于遍历注解事件对象
|
||||
- 新增 `MessageUtil::checkArguments()` 方法,用于检查 `@CommandArgument` 注解
|
||||
|
||||
## build 468 (2022-4-30)
|
||||
|
||||
- 优化单元测试流程
|
||||
- 优化上下文对象,在非协程环境下不再会抛出异常或返回 null
|
||||
|
||||
## build 467 (2022-4-29)
|
||||
|
||||
- 优化 `@RequestMapping` 注解事件的方法返回值处理,支持数组和字符串(数组自动转为 JSON 格式)
|
||||
|
||||
## build 466 (2022-4-29)
|
||||
|
||||
- 优化容器支持无名顺序参数的调用
|
||||
- 优化静态路由,支持 64 以上长度的路由
|
||||
|
||||
## build 464 (2022-4-28)
|
||||
|
||||
- 重构全局方法 `match_pattern()`,优化性能以及解决部分字符串不能匹配的 Bug
|
||||
|
||||
## build 463 (2022-4-16)
|
||||
|
||||
- 新增链式调用全局方法 `chain()`
|
||||
- 新增函数执行时间工具全局函数 `stopwatch()`
|
||||
|
||||
## build 462 (2022-4-15)
|
||||
|
||||
- 新增依赖注入、容器支持,目前对 Swoole 事件、机器人事件均支持使用依赖注入
|
||||
- 新增全局容器方法 `container()`、`resolve()`、`app()`,用于获取容器参数等
|
||||
- 新增相关容器测试
|
||||
|
||||
## build 461 (2022-4-11)
|
||||
|
||||
- 新增 AllBotsProxy、AllGroupsProxy 代理类,支持批量发送机器人动作
|
||||
- 新增全局函数 `implode_when_necessary()`,用于将可能为数组的参数转换为字符串
|
||||
|
||||
## build 460 (2022-4-3)
|
||||
|
||||
- 优化代码到 phpstan-level-4
|
||||
|
||||
## build 459 (2022-4-3)
|
||||
|
||||
- 优化代码到 phpstan-level-2
|
||||
|
||||
## build 458 (2022-4-3)
|
||||
|
||||
- 优化代码到 phpstan-level-1
|
||||
- 优化 `module:xxx` 类命令的有关实现代码
|
||||
|
||||
## build 457 (2022-4-2)
|
||||
|
||||
- 新增和优化测试用例(具体见文件)
|
||||
- 部分文件以 PHPStan Level 2 进行规范化
|
||||
|
||||
## build 456 (2022-3-30)
|
||||
|
||||
- 重构 phpunit-swoole,使其可以正常使用
|
||||
- 新增 `--private-mode` 参数,用于隐藏启动前的 MOTD 及敏感信息
|
||||
- 修复 Composer extra 配置项 `zm.exclude-annotation-path` 不能正常工作的 Bug
|
||||
- 优化注解事件加载器,防止 Master 进程中添加的事件在 Worker 中被覆盖的问题
|
||||
- 修复 `DataProvider::isRelativePath()` 方法判断有误的 Bug
|
||||
- 新增退出框架时支持以非 0 exit code 退出的功能
|
||||
- 优化 `ZMUtil::getClassesPsr4()` 方法,排除不含类的文件
|
||||
- 优化 PHP CS Fixer 的配置
|
||||
- 新增测试用例(具体见文件)
|
||||
|
||||
## build 455 (2022-3-27)
|
||||
|
||||
- 修复前几个小版本无法收发消息的 Bug
|
||||
- 新增 API Document 自动生成脚本
|
||||
|
||||
## build 454 (2022-3-27)
|
||||
|
||||
- 修复部分命令下无法杀掉进程的 Bug
|
||||
- 新增 `@Cron` 注解
|
||||
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
|
||||
|
||||
## build 453 (2022-3-25)
|
||||
|
||||
- 新增 property 注解用于 IDE 识别
|
||||
|
||||
## build 452 (2022-3-25)
|
||||
|
||||
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
|
||||
- 修复 HelpGenerator 的 Alias 不工作的 Bug
|
||||
|
||||
## build 451 (2022-3-21)
|
||||
|
||||
- 重构全局函数,统一函数命名,并补全注释
|
||||
|
||||
## build 450 (2022-3-21)
|
||||
|
||||
- 新增命令帮助生成器
|
||||
|
||||
## build 449 (2022-3-21)
|
||||
|
||||
- 新增 Composer 模块加载和分发模式
|
||||
|
||||
## build 448 (2022-3-20)
|
||||
|
||||
- 加快 build 命令的执行速度,取消进度条和提升性能
|
||||
|
||||
## build 447 (2022-3-20)
|
||||
|
||||
- 发布 2.7.0 正式版
|
||||
|
||||
## build 446 (2022-3-20)
|
||||
|
||||
- 新增 `./zhamao server` 下的 `--no-state-check` 参数,关闭“启动框架前的运行状态检查”功能
|
||||
|
||||
## build 445 (2022-3-20)
|
||||
|
||||
- 新增配置项 `runtime`.`annotation_reader_ignore`:支持注解解析器忽略注解的自定义
|
||||
|
||||
## build 444 (2022-3-20)
|
||||
|
||||
- 更改 `extra`.`exclude_annotate` 为 `zm`.`exclude-annotation-path` 项
|
||||
|
||||
## build 443 (2022-3-20)
|
||||
|
||||
- 修复注释空格的样式
|
||||
|
||||
## build 442 (2022-3-20)
|
||||
|
||||
- 修复打包模块后 `files` 的 autoload 项不能被解压和引入的 Bug
|
||||
|
||||
## build 441 (2022-3-20)
|
||||
|
||||
- 修复打包模块时命名空间与实际不一致的 Bug
|
||||
|
||||
## build 440 (2022-3-20)
|
||||
|
||||
- 新增方法宏(Macroable)
|
||||
|
||||
## build 439 (2022-3-19)
|
||||
|
||||
- 新增 PHP 8 Attribute 与注解同时支持的特性
|
||||
|
||||
## build 438 (2022-3-18)
|
||||
|
||||
- 修复 Response 类在 PHP 8.1 环境下的报错
|
||||
|
||||
## build 437 (2022-3-17)
|
||||
|
||||
- 修复 `ctx()` 可能会返回 null 的 Bug
|
||||
|
||||
## build 436 (2022-3-15)
|
||||
|
||||
- 新增 PHPStan 和 PHP CS Fixer 并优化全局代码
|
||||
|
||||
## build 435 (2022-3-13)
|
||||
|
||||
- 优化分离 WorkerManager 与 ProcessManager 的职责
|
||||
- 新增 Ctrl+C 一次无法停止框架时多次 Ctrl+C 后可强行杀掉所有进程的功能
|
||||
- `./zhamao server:stop` 新增参数 `--force`,使用 `SIGKILL` 强行杀掉所有进程
|
||||
- 新增 AnnotationParser 对 `autoload-dev` 项中的 `psr-4` 默认检索条件
|
||||
- 新增框架启动状态检测功能,如果已经启动了同样目录的框架,则会报错
|
||||
- 新增“强制启用轮询模式启动热更新”功能(参数 `--polling-watch`)
|
||||
- 修复与 PHP 8.1 的兼容性
|
||||
- 对 DaemonCommand 进行优化,与 ServerCommand 效果相同
|
||||
- 修复 `autoload`.`psr-4` 不存在时报错的 Bug
|
||||
- 新增框架停止时 Worker 退出回显状态码
|
||||
- 新增 inotify 判断模式,如果使用 `--watch` 检测到没有安装 inotify,则自动使用轮询模式
|
||||
|
||||
## build 434 (2022-1-8)
|
||||
|
||||
- 修复框架在 PHP 8.1 下运行时的一些问题
|
||||
- 新增 Console 日志输出到文件的功能
|
||||
|
||||
## build 433 (2021-12-28)
|
||||
|
||||
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
|
||||
|
||||
## build 432 (2021-12-25)
|
||||
|
||||
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot Action(API)
|
||||
- 修复 MySQL 查询器中 `fetchOne()` 方法无法正确返回值的 Bug
|
||||
- 修复 Swoole Hook 因配置不当无法正确使用的 Bug
|
||||
|
||||
## build 431 (2021-12-22)
|
||||
|
||||
- 修复 Issue #50
|
||||
- 新增 PhpStorm IDE 直接运行框架的脚本
|
||||
|
||||
## build 430 (2021-12-8)
|
||||
|
||||
- 删除调试信息
|
||||
- 修复 #56 中关于数据库组件的 Bug
|
||||
|
||||
## build 429 (2021-12-7)
|
||||
|
||||
- 新增配置项 `onebot`.`message_command_policy`
|
||||
- 新增 CQCommand 阻断策略的自定义配置功能
|
||||
- 修复 CQAfter 无法正常使用的 bug #53
|
||||
|
||||
## build 428 (2021-11-16)
|
||||
|
||||
- 修复 `ctx()->waitMessage()` 在 array 消息模式下无法正确返回消息字符串的问题
|
||||
- 新增 `ctx()->getArrayMessage()` 和 `ctx()->getStringMessage()` 两个方法
|
||||
- 修复注解事件 `CQCommand` 和 `CQMessage` 在 array 消息模式下无法正确解析的 Bug
|
||||
|
||||
## build 427 (2021-11-16)
|
||||
|
||||
- 新增全局中间件,可在全局配置文件中设置
|
||||
- 修复部分 Typo
|
||||
- 新增指令 `server:status`、`server:reload`、`server:stop` 可用在新开终端中查看框架运行状态、重启和退出
|
||||
- 新增支持 `array` 格式的消息
|
||||
- 上下文 Context 对象新增 `getOriginMessage()` 用于获取原消息,`getMessage()` 如果在设置了转换后,将默认转换消息为字符串格式保持与旧模块兼容
|
||||
- OneBot API 新增全局过滤器,可用作 Action 过滤重写等操作
|
||||
- 配置文件新增 `runtime.reload_delay_time`,用于可配置重载 Worker 等待的时间(毫秒)
|
||||
- 配置文件新增 `runtime.global_middleware_binding`,用于配置全局中间件
|
||||
- 配置文件新增 `onebot.message_convert_string`,用于配置是否转换数组格式为字符串,保证与前版本的兼容性(默认为 true)
|
||||
- MessageUtil 消息工具类新增方法:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
|
||||
- MessageUtil 消息工具类新增方法:`arrayToStr(array $array)`
|
||||
- 新增框架启动多次监测功能,无法使用同一个框架项目同时启动两个框架
|
||||
|
||||
## build 426 (2021-11-10)
|
||||
|
||||
- 修复 CQ 码的解析函数 Bug(#52)
|
||||
|
||||
## build 425 (2021-11-3)
|
||||
|
||||
- 删除未实际应用功能的配置参数
|
||||
- 修复 reload 时会断开 WebSocket 连接且导致进程崩溃的 Bug
|
||||
|
||||
## build 424 (2021-11-2)
|
||||
|
||||
- 新增 InstantModule 类、ZMServer 类、ModuleBase 类
|
||||
- 配置文件新增 `runtime.reload_kill_connect`、`runtime.global_middleware_binding` 选项
|
||||
- 修复部分情况下闭包事件分发时崩溃的 bug
|
||||
- 新增内部方法 `_zm_env_check`
|
||||
- 调整默认的 OneBot 模块对应的等级从 99999 调整为 99
|
||||
- 新增导出框架运行参数的列表功能
|
||||
|
||||
## build 423 (2021-10-17)
|
||||
|
||||
- 修复 PHP 7.2 ~ 7.3 下无法使用新版 MySQL 组件的 bug
|
||||
|
||||
## build 422 (2021-10-6)
|
||||
|
||||
- 修复 `script_` 前缀无法被排除加载模块的 bug
|
||||
- 修复 MySQL 组件的依赖问题
|
||||
|
||||
## build 421 (2021-9-11)
|
||||
|
||||
- 删除多余的调试信息
|
||||
|
||||
## build 420 (2021-9-11)
|
||||
|
||||
- 修复 OneBot 事件无法响应的 bug
|
||||
- 新增部分 EventDispatcher 触发的事件 debug 日志
|
||||
|
||||
## build 419 (2021-9-11)
|
||||
|
||||
- 修复 DB 模块在未连接数据库的时候抛出未知异常
|
||||
- 修复部分情况下打包模块出现的错误
|
||||
|
||||
## build 418 (2021-9-10)
|
||||
|
||||
- 修复 ZMAtomic 在 test 环境下的 bug
|
||||
- 修复 MessageUtil 的报错
|
||||
|
||||
## build 417 (2021-8-29)
|
||||
|
||||
- 新增 AnnotationException,统一框架内部的抛出异常的类型
|
||||
- 新增 AnnotationParser 下的 `verifyMiddlewares()` 方法
|
||||
- 私有化 CQAPI 类下的内部方法
|
||||
- 将 WebSocket API 响应超时时间从 60 秒缩短为 30 秒
|
||||
- 修复 DB 类不能使用旧查询器的 bug
|
||||
- 统一 DB 类下抛出 Exception 的类型为 ZMException 的子类
|
||||
- EventDispatcher 新增对 `middleware_error_policy` 的处理段
|
||||
- 配置文件下 `runtime` 新增 `middleware_error_policy` 字段
|
||||
- 将 LightCache 组件抛出的异常改为 LightCacheException
|
||||
- ModuleManager 修复改配置的 `load_path` 不生效的 bug
|
||||
- 修复打包时生成的 Phar Autoload 列表出错的 bug
|
||||
- 将配置的 override 改为 overwrite
|
||||
- 新增解包时忽略依赖的选项(`--ignore-depends`)
|
||||
- 删除众多调试日志,修改部分调试日志为 debug 级别的输出
|
||||
- 修改 `ZM\MySQL\MySQLManager` 下的 `getConnection()` 为 `getWrapper()`
|
||||
- MySQLPool 对象新增 `getCount()` 方法
|
||||
- 新增 MySQLQueryBuilder 类(`doctrine/dbal` 的 wrapper 类)
|
||||
- 修复 MySQLStatement 封装原 dbal 组件时与连接池不兼容的 bug
|
||||
- 新增 MySQLStatementWrapper 类
|
||||
- 完善 MySQLWrapper 类,用作主要的查询对象控制类
|
||||
- 编写外部插件加载方式(Phar 热加载功能)
|
||||
- 修复 `ZMUtil::getClassesPsr4()` 方法在遇到空扩展名文件时的报错
|
||||
@ -1,78 +0,0 @@
|
||||
# 配置文件变更记录
|
||||
|
||||
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
|
||||
|
||||
## v2.7.0 (build 447)
|
||||
|
||||
- 新增 `$config['runtime']` 下的 `annotation_reader_ignore` 项。
|
||||
|
||||
## v2.6.6 (build 434)
|
||||
|
||||
- 新增 `$config['runtime']` 下的 `save_console_log_file` 项。
|
||||
|
||||
## v2.6.0 (build 427)
|
||||
|
||||
- 新增 `$config['runtime']` 下的 `reload_delay_time`、`global_middleware_binding` 项。
|
||||
- 新增 `$config['onebot']` 下的 `message_convert_string` 项。
|
||||
|
||||
## v2.5.1 (build 417)
|
||||
|
||||
- 新增 `$config['runtime']` 下的 `middleware_error_policy` 选项。
|
||||
|
||||
## v2.5.0 (build 413)
|
||||
|
||||
- 新增 `$config['runtime']` 运行时设置。
|
||||
- 删除 `$config['server_event_handler_class']`,默认在启动时全局扫描。
|
||||
- 新增 `$config['module_loader']` 模块/插件 打包配置选项。
|
||||
- 新增 `$config['mysql_config']`,取代原先的 `$config['sql_config']`,此外废弃原先的MySQL 查询器 `\ZM\DB\DB` 类。
|
||||
|
||||
更新部分:
|
||||
|
||||
```php
|
||||
/** 一些框架与Swoole运行时设置的调整 */
|
||||
$config['runtime'] = [
|
||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
||||
'swoole_server_mode' => SWOOLE_PROCESS
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['mysql_config'] = [
|
||||
'host' => '',
|
||||
'port' => 3306,
|
||||
'unix_socket' => null,
|
||||
'username' => 'root',
|
||||
'password' => '123456',
|
||||
'dbname' => 'adb',
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64,
|
||||
'options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
];
|
||||
|
||||
/** 注册 Swoole Server 事件注解的类列表(deleted) */
|
||||
// 删除
|
||||
```
|
||||
|
||||
## 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,220 +0,0 @@
|
||||
# 更新日志(v1 版本)
|
||||
|
||||
## v1.6.5
|
||||
|
||||
> 更新时间:2020.12.9
|
||||
|
||||
- 修复:版本号显示
|
||||
- 优化:依赖问题,减少对 PHP 扩展的依赖,转变为可选
|
||||
|
||||
## v1.6.4
|
||||
|
||||
> 更新时间:2020.12.9
|
||||
|
||||
- 修复:composer require模式下自动加载的问题
|
||||
- 优化:减少不是必需的依赖问题
|
||||
|
||||
## v1.6.3
|
||||
|
||||
> 更新时间:2020.11.15
|
||||
|
||||
- 修复:Response 对象使用 redirect 造成的递归报错
|
||||
- 修复:`document_index` 配置项在 `/` 路径下无法使用的 bug
|
||||
|
||||
## v1.6.2
|
||||
|
||||
> 更新时间:2020.7.27
|
||||
|
||||
- 修复:不写配置 `server_event_handler_class` 项无法启动的 bug
|
||||
|
||||
## v1.6.1
|
||||
|
||||
> 更新时间:2020.7.26
|
||||
|
||||
- 新增:`ZMRequest::downloadFile($url, $dst)` 方法,可直接将文件下载到本地
|
||||
|
||||
## v1.6
|
||||
|
||||
> 更新时间:2020.7.14
|
||||
|
||||
- 新增:现在可以对类修饰自定义的注解了
|
||||
- 修复:数据库操作 where 对象时产生的歧义
|
||||
- 新增:支持自定义任何 Swoole Server 事件的注解绑定,详见文档
|
||||
- 修复:多个中间件注解对类只生效最后一个的 bug
|
||||
|
||||
❗ 下面是框架升级需要手动进行的变更:
|
||||
|
||||
- 新版本由于引进了自定义 Swoole Server 事件的机制,对 global.php 全局配置文件有了变动,需要添加以下内容才能正常启动(旧版本升级新版本用户,新用户无需操作):
|
||||
|
||||
```php
|
||||
/** 注册 Swoole Server 事件注解的类列表 */
|
||||
$config['server_event_handler_class'] = [
|
||||
\Framework\ServerEventHandler::class, //默认不可删除,否则会不能使用框架
|
||||
];
|
||||
```
|
||||
|
||||
## v1.5.8
|
||||
|
||||
> 更新时间:2020.6.26
|
||||
|
||||
- 新增:`@CQCommand` 注解的 fullMatch 参数(全量正则表达式匹配)
|
||||
|
||||
## v1.5.7
|
||||
|
||||
> 更新时间:2020.6.20
|
||||
|
||||
- 新增:ZM_BREAKPOINT 的短名称:BP
|
||||
- 优化:终端连接器自动重连
|
||||
- 修复:语法错误时防止循环报错
|
||||
|
||||
## v1.5.6
|
||||
|
||||
> 更新时间:2020.6.15
|
||||
|
||||
- 新增:`@CQCommand` 注解支持 `message_type`,`user_id`,`group_id`,`discuss_id` 限定条件
|
||||
- 新增:PDO 数据库支持自定义 fetch_mode,可在 `global.php` 中的 `sql_config["sql_default_fetch_mode"]` 字段设置,也可以调用时 `DB::rawQuery("语句", [], PDO::FETCH_ASSOC);` 第三个参数可选
|
||||
- 🔴 废弃:`ModBase` 基类,基类继承机制将在 1.6 版本起完全删除
|
||||
|
||||
## v1.5.5
|
||||
|
||||
> 更新时间:2020.6.13
|
||||
|
||||
- 修复:`@SwooleEventAt("close")` 下不能使用 `ctx()->getConnection()` 获取链接对象的 bug
|
||||
- 新增:init 命令,可在 `composer require zhamao/framework` 后使用 `vendor/bin/start init` 初始化项目目录结构和配置文件
|
||||
- 更新:默认模块新增机器人断开连接的回调事件
|
||||
|
||||
## v1.5.4
|
||||
|
||||
> 更新时间:2020.6.13
|
||||
|
||||
- 新增:`@CQCommand` 下支持 alias 参数
|
||||
- 更新:将 autoload 变为 composer autoload(需要重新 composer update)
|
||||
|
||||
## v1.5.3
|
||||
|
||||
> 更新时间:2020.6.10
|
||||
|
||||
- 修复:在 Linux 系统下 Terminal 无法正常使用的 bug
|
||||
|
||||
## v1.5.2
|
||||
|
||||
> 更新时间:2020.6.8
|
||||
|
||||
- 新增:`ZM_VERSION` 常量,对应为当前框架版本
|
||||
- 修复:部分链接不带 `/` 会导致 ZMRequest 模块报错的 bug
|
||||
|
||||
## v1.5.1
|
||||
|
||||
> 更新时间:2020.6.5
|
||||
|
||||
- 新增:ZMRequest::request() 自定义构建 HTTP 请求方法
|
||||
- 修复:一个不会导致崩溃的 warning 提示
|
||||
|
||||
## v1.5
|
||||
|
||||
> 更新时间:2020.6.5
|
||||
|
||||
- 重要变更:支持从 composer 使用框架
|
||||
- 新增:数据库 Select 选择器支持 `count()` 方法
|
||||
- 修复:ZMRequest 中 https 和端口的指定顺序问题
|
||||
- 新增:ZMWebSocket 创建 WS 链接的轻量级客户端
|
||||
- 修复:数据库异常的捕获更改为 PDOException
|
||||
|
||||
## v1.4
|
||||
|
||||
> 更新时间:2020.5.23
|
||||
|
||||
- 新增:自定义 motd
|
||||
- 新增:debug_mode 下断点调试功能
|
||||
- 新增:`@OnSave` 注解,储存自动保存的变量时事件激活
|
||||
- 新增:Swoole 版本检测
|
||||
- 新增:全局函数,以 `zm_` 开头的,详情见文档
|
||||
- 新增:`@LoadBuffer` 注解,只加载内存不自动保存的变量
|
||||
- 新增:局部静态文件服务
|
||||
- 新增:mysqlnd 扩展状态检测
|
||||
- 更新:将终端输入更换为多进程
|
||||
- 更新:将数据库连接池变更为 Swoole 官方的连接池,需要 Swoole 版本 >= 4.4.13
|
||||
- 更新:提升注解绑定的事件函数的执行效率
|
||||
- 修复:上下文 `getConnection()` 的 fd 无法获取的 bug
|
||||
- 修复:MySQL 长链接 gone away 自动重连的问题
|
||||
- 修复:MySQL 查询构造器无 WHERE 语句时会造成的 bug
|
||||
- 修复:调整各项资源初始化前后顺序
|
||||
|
||||
不可逆修改:你需要重新执行一次 `composer update` 或重新拉取一次 Docker Image,因为 composer 依赖发生了变化。
|
||||
|
||||
## v1.3.1
|
||||
|
||||
> 更新时间:2020.5.10
|
||||
|
||||
- 修复:DataProvider 下 setJsonData 新建文件夹的问题
|
||||
- 优化:默认 / 页面显示 `Hello Zhamao!` 文字
|
||||
- 优化:Exception 和 Fatal error 报错机制的改进
|
||||
- 修复:计时器没有上下文环境,发不了 API 的 bug
|
||||
|
||||
❗ 下面是框架升级需要手动进行的变更:
|
||||
|
||||
- 更改 MySQL 客户端为原生 PDO mysqlnd,如果之前使用 Docker 启动,则需使用新的 Dockerfile 构建。如果安装在本机,需安装 php-mysql 扩展。本次更新不影响框架内的 API,不需要更改任何代码。
|
||||
|
||||
## v1.3.0
|
||||
|
||||
> 更新时间:2020.5.8
|
||||
|
||||
- 新增:上下文,具体更新都写到了文档里了!
|
||||
- 修复:ZMRobot 的 `setPrefix()` 的严重错误
|
||||
- 优化:优化部分代码
|
||||
- 改动:现在你可以和任意事件的注解使用任意中间件啦,而且还支持多中间件
|
||||
- 新增:CQHTTP + 酷Q + 炸毛框架 的 Dockerfile
|
||||
- 新增注解:`@CQAPISend`,`@CQAPIResponse`,是 API 调用后触发的事件,具体见文档说明
|
||||
|
||||
## v1.2.1
|
||||
|
||||
> 更新时间:2020.5.2
|
||||
|
||||
- 新增:phar 启动模式构建脚本,你可以直接拉取 phar 运行框架了!
|
||||
- 优化:优化部分代码
|
||||
|
||||
## v1.2
|
||||
|
||||
> 更新时间:2020.4.29
|
||||
|
||||
- 新增:systemd 生成脚本、一键 daemonize 守护进程方式常驻后台
|
||||
- 新增:示例模块的注释
|
||||
- 重构:Console 模块,现在有准确的控制台输出分级功能了
|
||||
- 新增:`@OnTick` 注解,用于绑定定时器(毫秒级)
|
||||
- 新增:`ZMRobot` 类,比调用 `CQAPI` 类发送 API 更方便,同时兼容最新版本的 `CQHTTP` 插件
|
||||
- 优化:使用键盘中断 `Ctrl+C`,不会丢失未保存的缓存数据了
|
||||
- 优化:完善上下文对象的方法
|
||||
- 新增:终端命令:`logtest`,测试输出的 log 类型
|
||||
|
||||
:exclamation:下面是框架模块开发中需要注意的或有不兼容的修改内容:
|
||||
|
||||
- 修改:`global.php` 中原来的 `info_level` 默认数值需要改为 `2`,保证终端输出和原来一致
|
||||
|
||||
## v1.1.2
|
||||
|
||||
> 更新时间:2020.4.26
|
||||
|
||||
- 新增:静态文件服务器
|
||||
- 修复:`/` 路径的 Mapping 无法正常绑定的 bug
|
||||
|
||||
## v1.1.1
|
||||
|
||||
> 更新时间:2020.4.26
|
||||
|
||||
- 新增:中间件对类的修饰
|
||||
- 新增:上下文对象对 IDE 的支持
|
||||
- 修复:数据库插入查询的愚蠢错误
|
||||
- 修复:数据库查询的 `value()` 不支持指定参数的 bug
|
||||
|
||||
## v1.1.0
|
||||
|
||||
> 更新时间:2020.3.29
|
||||
|
||||
- 新增:中间件 `@Middleware` 功能
|
||||
- 修复:Websocket 链接关闭后未自动删除连接对象的bug
|
||||
|
||||
## v1.0.0
|
||||
|
||||
> 更新时间:2020.3.19
|
||||
|
||||
正式版发布。
|
||||
@ -1,625 +0,0 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.8.0(build 473)
|
||||
|
||||
> 更新时间:2022.5.7
|
||||
|
||||
- 新增 AllBotsProxy、AllGroupsProxy 代理类,支持批量发送机器人动作
|
||||
- 新增全局函数 `implode_when_necessary()`,用于将可能为数组的参数转换为字符串
|
||||
- 新增依赖注入、容器支持,目前对 Swoole 事件、机器人事件均支持使用依赖注入
|
||||
- 新增全局容器方法 `container()`、`resolve()`、`app()`,用于获取容器参数等
|
||||
- 新增相关容器测试
|
||||
- 新增链式调用全局方法 `chain()`
|
||||
- 新增函数执行时间工具全局函数 `stopwatch()`
|
||||
- 新增 `@CommandArgument` 注解,可直接通过注解添加聊天机器人命令参数
|
||||
- (内部)新增 `EventManager::$event_map`,用于补充对事件对象遍历的方式
|
||||
- 新增 `EventMapIterator` 类,用于遍历注解事件对象
|
||||
- 新增 `MessageUtil::checkArguments()` 方法,用于检查 `@CommandArgument` 注解
|
||||
- 新增启动命令参数 `--audit-mode`,用于单次审计模式
|
||||
- 新增部分不可执行脚本的防呆退出功能
|
||||
- 重构全局方法 `match_pattern()`,优化性能以及解决部分字符串不能匹配的 Bug
|
||||
- 优化容器支持无名顺序参数的调用
|
||||
- 优化静态路由,支持 64 以上长度的路由
|
||||
- 优化 `@RequestMapping` 注解事件的方法返回值处理,支持数组和字符串(数组自动转为 JSON 格式)
|
||||
- 优化单元测试流程
|
||||
- 优化上下文对象,在非协程环境下不再会抛出异常或返回 null
|
||||
- 修改默认 Hello 模块下随机数功能为采用 `@CommandArgument` 注解模式
|
||||
- 重构帮助生成器,将帮助生成器重构为 `CommandInfoUtil` 类
|
||||
- 修复 `CQ::encode()` 无法传入 `int` 的强类型解析问题(#113)
|
||||
- (内部)重构 CQ 类
|
||||
- 修复 `EventMapIterator` 对 PHP 8.1 的兼容性问题
|
||||
- 修复 #95 中提到的无输入流时报错的问题
|
||||
- 修复 `ZMServer` 中的 typo
|
||||
- 修复 Container 环境继承全局变量的问题
|
||||
- 修复 `server:stop` 命令下部分情况报错的问题
|
||||
|
||||
## v2.7.6(build 460)
|
||||
|
||||
> 更新时间:2022.4.3
|
||||
|
||||
- 重构 phpunit-swoole,使其可以正常使用
|
||||
- 新增 `--private-mode` 参数,用于隐藏启动前的 MOTD 及敏感信息
|
||||
- 修复 Composer extra 配置项 `zm.exclude-annotation-path` 不能正常工作的 Bug
|
||||
- 优化注解事件加载器,防止 Master 进程中添加的事件在 Worker 中被覆盖的问题
|
||||
- 修复 `DataProvider::isRelativePath()` 方法判断有误的 Bug
|
||||
- 新增退出框架时支持以非 0 exit code 退出的功能
|
||||
- 优化 `ZMUtil::getClassesPsr4()` 方法,排除不含类的文件
|
||||
- 优化 `module:xxx` 类命令的有关实现代码
|
||||
- 优化代码到 phpstan-level-4
|
||||
|
||||
## v2.7.5(build 455)
|
||||
|
||||
> 更新时间:2022.3.27
|
||||
|
||||
- 修复前几个小版本无法收发消息的 Bug
|
||||
- 新增 API Document 自动生成脚本
|
||||
|
||||
## v2.7.4(build 454)
|
||||
|
||||
> 更新时间:2022.3.27
|
||||
|
||||
- 修复部分命令下无法杀掉进程的 Bug
|
||||
- 新增 `@Cron` 注解
|
||||
- 修复全局函数 `match_pattern` 无法正常工作的 Bug
|
||||
|
||||
## v2.7.3(build 453)
|
||||
|
||||
> 更新时间:2022.3.25
|
||||
|
||||
- 新增命令帮助生成器
|
||||
- 重构全局函数,统一函数命名,并补全注释
|
||||
- 修复 OnSetup 注解无法使用 Attribute 解析的 Bug
|
||||
- 修复 HelpGenerator 的 Alias 不工作的 Bug
|
||||
- 新增 property 注解用于 IDE 识别
|
||||
|
||||
## v2.7.2(build 449)
|
||||
|
||||
> 更新时间:2022.3.21
|
||||
|
||||
- 新增 Composer 模块加载和分发模式
|
||||
|
||||
## v2.7.1(build 448)
|
||||
|
||||
> 更新时间:2022.3.20
|
||||
|
||||
- 加快 build 命令的执行速度,取消进度条和提升性能
|
||||
|
||||
## v2.7.0(build 447)
|
||||
|
||||
> 更新时间:2022.3.20
|
||||
|
||||
- 优化分离 WorkerManager 与 ProcessManager 的职责
|
||||
- 新增 Ctrl+C 一次无法停止框架时多次 Ctrl+C 后可强行杀掉所有进程的功能
|
||||
- `./zhamao server:stop` 新增参数 `--force`,使用 `SIGKILL` 强行杀掉所有进程
|
||||
- 新增 AnnotationParser 对 `autoload-dev` 项中的 `psr-4` 默认检索条件
|
||||
- 新增框架启动状态检测功能,如果已经启动了同样目录的框架,则会报错
|
||||
- 新增“强制启用轮询模式启动热更新”功能(参数 `--polling-watch`)
|
||||
- 修复与 PHP 8.1 的兼容性
|
||||
- 对 DaemonCommand 进行优化,与 ServerCommand 效果相同
|
||||
- 修复 `autoload`.`psr-4` 不存在时报错的 Bug
|
||||
- 新增框架停止时 Worker 退出回显状态码
|
||||
- 新增 inotify 判断模式,如果使用 `--watch` 检测到没有安装 inotify,则自动使用轮询模式
|
||||
- 新增 PHPStan 和 PHP CS Fixer 并优化全局代码
|
||||
- 修复 `ctx()` 可能会返回 null 的 Bug
|
||||
- 修复 Response 类在 PHP 8.1 环境下的报错
|
||||
- 新增 PHP 8 Attribute 与注解同时支持的特性
|
||||
- 新增方法宏(Macroable)
|
||||
- 修复打包模块时命名空间与实际不一致的 Bug
|
||||
- 修复打包模块后 `files` 的 autoload 项不能被解压和引入的 Bug
|
||||
- 修复注释空格的样式
|
||||
- 更改 `extra`.`exclude_annotate` 为 `zm`.`exclude-annotation-path` 项
|
||||
- 新增配置项 `runtime`.`annotation_reader_ignore`:支持注解解析器忽略注解的自定义
|
||||
- 新增 `./zhamao server` 下的 `--no-state-check` 参数,关闭“启动框架前的运行状态检查”功能
|
||||
|
||||
## v2.6.6(build 434)
|
||||
|
||||
> 更新时间:2022.1.8
|
||||
|
||||
- 修复框架在 PHP 8.1 下运行时的一些问题
|
||||
- 新增 Console 日志输出到文件的功能
|
||||
|
||||
## v2.6.5(build 433)
|
||||
|
||||
> 更新时间:2021.12.28
|
||||
|
||||
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
|
||||
|
||||
## v2.6.4(build 432)
|
||||
|
||||
> 更新时间:2021.12.25
|
||||
|
||||
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot Action(API)
|
||||
- 修复 MySQL 查询器中 `fetchOne()` 方法无法正确返回值的 Bug
|
||||
- 修复 Swoole Hook 因配置不当无法正确使用的 Bug
|
||||
- 修复 Issue #50
|
||||
- 新增 PhpStorm IDE 直接运行框架的脚本
|
||||
|
||||
## v2.6.3 (build 430)
|
||||
|
||||
> 更新时间:2021.12.8
|
||||
|
||||
- 删除调试信息
|
||||
- 修复 #56 中关于数据库组件的 Bug
|
||||
|
||||
## v2.6.2 (build 429)
|
||||
|
||||
> 更新时间:2021.12.7
|
||||
|
||||
- 新增配置项 `onebot`.`message_command_policy`
|
||||
- 新增 CQCommand 阻断策略的自定义配置功能
|
||||
- 修复 CQAfter 无法正常使用的 bug #53
|
||||
|
||||
## v2.6.1 (build 428)
|
||||
|
||||
> 更新时间:2021.11.16
|
||||
|
||||
- 修复 ctx()->waitMessage() 在 array 消息模式下无法正确返回消息字符串的问题
|
||||
- 新增 ctx()->getArrayMessage() 和 ctx()->getStringMessage() 两个方法
|
||||
- 修复注解事件 CQCommand 和 CQMessage 在 array 消息模式下无法正确解析的 Bug
|
||||
|
||||
## v2.6.0 (build 427)
|
||||
|
||||
> 更新时间:2021.11.16
|
||||
|
||||
- 新增全局中间件,可在全局配置文件中设置
|
||||
- 修复部分 Typo
|
||||
- 新增指令 `server:status`、`server:reload`、`server:stop` 可用在新开终端中查看框架运行状态、重启和退出
|
||||
- 新增支持 `array` 格式的消息
|
||||
- 上下文 Context 对象新增 `getOriginMessage()` 用于获取原消息,`getMessage()` 如果在设置了转换后,将默认转换消息为字符串格式保持与旧模块兼容
|
||||
- OneBot API 新增全局过滤器,可用作 Action 过滤重写等操作
|
||||
- 配置文件新增 `runtime.reload_delay_time`,用于可配置重载 Worker 等待的时间(毫秒)
|
||||
- 配置文件新增 `runtime.global_middleware_binding`,用于配置全局中间件
|
||||
- 配置文件新增 `onebot.message_convert_string`,用于配置是否转换数组格式为字符串,保证与前版本的兼容性(默认为 true)
|
||||
- MessageUtil 消息工具类新增方法:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
|
||||
- MessageUtil 消息工具类新增方法:`arrayToStr(array $array)`
|
||||
- 新增框架启动多次监测功能,无法使用同一个框架项目同时启动两个框架
|
||||
|
||||
## v2.5.8 (build 426)
|
||||
|
||||
> 更新时间:2021.11.10
|
||||
|
||||
- 修复 CQ 码的解析函数 Bug(#52)
|
||||
|
||||
## v2.5.7 (build 425)
|
||||
|
||||
> 更新时间:2021.11.3
|
||||
|
||||
- 调低 OneBot 相关事件在 Swoole 的优先级
|
||||
- 修复部分情况下闭包事件函数分发时引发的崩溃 bug
|
||||
- 修复 reload 时会断开 WebSocket 连接且导致进程崩溃的 bug
|
||||
|
||||
## v2.5.6 (build 423)
|
||||
|
||||
> 更新时间:2021.10.17
|
||||
|
||||
- 修复 PHP 7.2 ~ 7.3 下无法使用新版 MySQL 组件的 bug
|
||||
|
||||
## v2.5.5 (build 422)
|
||||
|
||||
> 更新时间:2021.10.6
|
||||
|
||||
- 修复 `script_` 前缀无法被排除加载模块的 bug
|
||||
- 修复 MySQL 组件的依赖问题
|
||||
|
||||
## v2.5.4 (buidl 421)
|
||||
|
||||
> 更新时间:2021.9.11
|
||||
|
||||
- 删除多余的调试信息
|
||||
|
||||
## v2.5.3 (build 420)
|
||||
|
||||
> 更新时间:2021.9.11
|
||||
|
||||
- 修复 DB 模块在未连接数据库的时候抛出未知异常
|
||||
- 修复部分情况下打包模块出现的错误
|
||||
- 修复 OneBot 事件无法响应的 bug
|
||||
- 新增部分 EventDispatcher 触发的事件 debug 日志
|
||||
|
||||
## v2.5.2 (build 418)
|
||||
|
||||
> 更新时间:2021.9.10
|
||||
|
||||
- 新增 AnnotationException,统一框架内部的抛出异常的类型
|
||||
- 新增 AnnotationParser 下的 `verifyMiddlewares()` 方法
|
||||
- 私有化 CQAPI 类下的内部方法
|
||||
- 将 WebSocket API 响应超时时间从 60 秒缩短为 30 秒
|
||||
- 修复 DB 类不能使用旧查询器的 bug
|
||||
- 统一 DB 类下抛出 Exception 的类型为 ZMException 的子类
|
||||
- EventDispatcher 新增对 `middleware_error_policy` 的处理段
|
||||
- 配置文件下 `runtime` 新增 `middleware_error_policy` 字段
|
||||
- 将 LightCache 组件抛出的异常改为 LightCacheException
|
||||
- ModuleManager 修复改配置的 `load_path` 不生效的 bug
|
||||
- 修复打包时生成的 Phar Autoload 列表出错的 bug
|
||||
- 将配置的 override 改为 overwrite
|
||||
- 新增解包时忽略依赖的选项(`--ignore-depends`)
|
||||
- 删除众多调试日志,修改部分调试日志为 debug 级别的输出
|
||||
- 修改 `ZM\MySQL\MySQLManager` 下的 `getConnection()` 为 `getWrapper()`
|
||||
- MySQLPool 对象新增 `getCount()` 方法
|
||||
- 新增 MySQLQueryBuilder 类(`doctrine/dbal` 的 wrapper 类)
|
||||
- 修复 MySQLStatement 封装原 dbal 组件时与连接池不兼容的 bug
|
||||
- 新增 MySQLStatementWrapper 类
|
||||
- 完善 MySQLWrapper 类,用作主要的查询对象控制类
|
||||
- 编写外部插件加载方式(Phar 热加载功能)
|
||||
- 修复 `ZMUtil::getClassesPsr4()` 方法在遇到空扩展名文件时的报错
|
||||
|
||||
## v2.5.1 (build 416)
|
||||
|
||||
> 更新时间:2021.7.9
|
||||
|
||||
- 修复:脚手架无法正常使用 `init` 命令的 bug。
|
||||
|
||||
## v2.5.0(build 415)
|
||||
|
||||
> 更新时间:2021.7.9
|
||||
|
||||
以下是版本**新增内容**:
|
||||
|
||||
- 新增全新的模块系统,可打包模块(src 目录下的子目录用户逻辑代码)为 phar 格式进行分发和版本备份。
|
||||
- 全局配置文件新增 `module_loader` 项,用于配置外部模块加载的一些设置。
|
||||
- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数和 Swoole Server 的启动模式。
|
||||
- 新增 `module:list` 命令,用于查看未打包和已打包的模块列表。
|
||||
- 新增 `module:pack` 命令,用于打包现有 src 目录下的模块。
|
||||
- 新增 `module:unpack` 命令,用于解包现有的 phar 模块包。
|
||||
- 新增打包框架功能,支持将用户的整个项目连同炸毛框架打包为一个 phar 便携运行,使用命令 `build`。
|
||||
- 新增快捷脚本 `./zhamao`,效果同 `vendor/bin/start` 或 `bin/start`。
|
||||
- 新增启动参数 `--interact`:又重新支持交互终端了,但还是有点问题,不推荐使用。
|
||||
- 新增启动参数 `--disable-safe-exit`:如果你的项目在 Ctrl+C 时总是卡住且项目内没有什么使用 LightCache 等缓存在内存的数据可开启防止关不掉框架。
|
||||
- 新增启动参数 `--preview`:只显示参数,不启动炸毛框架的服务器。
|
||||
- 新增启动参数 `--force-load-module`:强制打包状态下加载的模块(使用英文逗号分隔多个模块名称)。
|
||||
- `CoroutinePool` 协程池新增 `getRunningCoroutineCount` 方法,用于查看协程池中的协程数量。
|
||||
- `DataProvider` 新增 `getFrameworkRootDir()`、`getSourceRootDir()`,分别代表获取框架的根目录和用户源码根目录。(详见下方对目录的定义解释)
|
||||
- `DataProvider` 中 `getDataFolder` 新增参数 `$second = ''`,如果给定,则自动创建子目录 `$second` 并返回。
|
||||
- `DataProvider` 新增 `scanDirFiles()` 方法,用于扫描目录,可选择是否递归、是否返回相对路径,也支持扫描 Phar 文件内的路径,非常好用。
|
||||
- `DataProvider` 新增 `isRelativePath()` 方法,检查路径是否为相对路径(根据第一个字符是否是 '/' 来判断)。
|
||||
- `ZMUtil` 新增 `getClassesPsr4()` 方法,用于根据 Psr-4 标准来获取目录下的所有类文件。
|
||||
- 新增全局错误码,可以根据错误码在文档内快速定位和解决问题。
|
||||
- 中间件和注解事件支持回溯,可以快速查看调用栈(比如中间件可以知道自己是在哪个注解事件中被调用)。
|
||||
- 使用 `./zhamao build` 来构建框架的 phar 包时增加显示进度条。
|
||||
- EventDispatcher 新增方法 `getEid()` 和 `getClass()`,分别用于获取事件分发 ID 和注解事件的注解类名称。
|
||||
- 新增 EventTracer,用于追踪事件的调用栈。
|
||||
- 中间件支持传参。
|
||||
- MySQL 数据库查询器改为使用 `doctrine/dbal` 组件,更灵活和稳定。
|
||||
- 新增对 `SWOOLE_BASE` 模式的支持(支持只启动一个进程的 Server)。
|
||||
|
||||
以下是版本**修改内容**:
|
||||
|
||||
- 启动文件 `vendor/bin/start` 修改为 shell 脚本,可自动寻找 PHP 环境。
|
||||
- 全局强制依赖 `league/climate` 组件。
|
||||
- 修复框架启动时的信息显示换行问题。
|
||||
- 修复框架使用 Phar 方式启动时导致的报错。
|
||||
- 修复使用 Ctrl+C 结束时一部分用户卡住的 bug。
|
||||
- 远程和本地终端去掉 stop 命令,建议直接使用发 SIGTERM 方式结束框架。
|
||||
- 全局配置文件的 `zm_data` 根目录默认修改为 `WORKING_DIR`。
|
||||
- 命令 `systemd:generate` 修改为 `generate:systemd`。
|
||||
- 全局配置文件删除 `server_event_handler_class` 项,此项废弃。
|
||||
- 修复部分 CQ 码解析过程中没有转义的问题。
|
||||
- 将 `ZMRobot` 类转移为 `OneBotV11` 类,但提供兼容。
|
||||
- 修复在守护进程模式下使用 `daemon:reload` 和 `daemon:stop` 命令可能失效的问题。
|
||||
- 修复 systemd 生成时脚本目录错误的 bug。
|
||||
- 修复 PipeMessage 等事件未捕获错误导致崩溃的问题。
|
||||
- `ZM\Http\RouteManager` 移动到 `ZM\Utils\Manager\RouteManager`,但原地址兼容。
|
||||
- 修复 `Terminal` 类使用的一些问题。
|
||||
- 对 `pcntl` 扩展改为可选依赖,当 Swoole 版本大于等于 4.6.7 时不需要安装 `pcntl` 扩展。
|
||||
- 修正启动时框架对缺省配置项的一些默认参数。
|
||||
- 注解 `@OnSetup` 和 `@SwooleHandler` 可直接使用,无需设置 `server_event_handler_class` 即可。
|
||||
- 修复框架在一些非正常终端中运行时导致错误的问题。
|
||||
- 使用 `--debug-mode` 参数时,自动开启热更新。
|
||||
- 修复脚手架在使用 composer 更新后检查全局配置功能的 bug。
|
||||
- 修复重启和关闭框架时造成的非正常连接断开。
|
||||
- 改用独立进程监听文件变化和终端输入。
|
||||
- 修复有协程中断的任务时停止服务器会报 Swoole 警告的 bug。
|
||||
- 修复连接被反复断开的问题。
|
||||
|
||||
**对目录的定义解释**:
|
||||
|
||||
在 2.4.4 版本之前,使用炸毛框架中,只含有两种目录,`getWorkingDir` 和 `getDataFolder`,分别代表获取工作目录和数据目录。在 2.5 版本中,又新增了 `getFrameworkRootDir` 代表获取框架的根目录,`getSourceRootDir` 代表获取源码的根目录。
|
||||
|
||||
以 Composer 运行模式举例,如果你使用 `composer create-project zhamao/framework-starter` 命令新建的框架,那么假设我们从 `/app` 目录下运行此命令,然后使用 `cd framework-starter/` 进入项目目录,此时我们使用 `vendor/bin/start server` 命令运行服务器,对应的目录为:
|
||||
|
||||
- `WorkingDir`:`/app/framework-starter/`
|
||||
- `SourceRootDir`:`/app/framework-starter/`
|
||||
- `FrameworkRootDir`:`/app/framework-starter/vendor/zhamao/framework/`
|
||||
|
||||
如果以源码模式(直接克隆 `zhamao-framework.git` 仓库),启动框架,那么使用命令 `bin/start server` 启动框架后,以上三个返回的目录则完全相同。
|
||||
|
||||
如果以 2.5 版本新的项目归档模式(build)启动框架,假设我们的项目代码打包为 `server.phar`,在 `/app/` 目录,我们使用命令 `php server.phar server` 启动炸毛框架,那么它对应的目录为:
|
||||
|
||||
- `WorkingDir`:`/app/`
|
||||
- `SourceRootDir`:`phar:///app/server.phar/`
|
||||
- `FrameworkRootDir`:`phar:///app/server.phar/vendor/zhamao/framework/`
|
||||
|
||||
如果最后一种归档方式启动的框架是从源码模式打包而来,那么 `FrameworkRootDir` 就与 `SourceRootDir` 相同。
|
||||
|
||||
**版本部分兼容问题变化**:
|
||||
|
||||
理论上如果不使用框架内部未开放的接口方法的话,从 2.4 升级到 2.5 是非常自然的,但是也有一部分可能会造成不兼容的问题。
|
||||
|
||||
- 生成 systemd 配置文件的命令 `systemd:generate` 变成 `generate:systemd`。
|
||||
- 全局配置文件中的 `zm_data` 的父目录由 `__DIR__ . "/../"` 改为 `WORKING_DIR`。
|
||||
- 2.5 版本将 ZMRobot 类中的所有函数方法都移动到了 `OneBotV11` 类中,但原先的 ZMRobot 还可以使用。
|
||||
|
||||
## v2.4.4 (build 405)
|
||||
|
||||
> 更新时间:2021.3.29
|
||||
|
||||
以下是可能不兼容的变更:
|
||||
|
||||
- 新增依赖:框架需要 PHP 安装 pcntl 扩展以及开启 `pcntl_signal` 函数(一般情况下编译安装的都会有,宝塔面板请手动解除函数禁用)
|
||||
|
||||
## v2.4.3 (build 403)
|
||||
|
||||
> 更新时间:2021.3.29
|
||||
|
||||
- 新增:swoole 设置配置新增 `max_wait_time` 项,设置等待进程关闭流程最大时间(秒)
|
||||
- 新增:常量 `MAIN_WORKER`,值等同于 `worker_cache` 项中的 `worker` 参数(WorkerCache 所在的进程)
|
||||
- 新增:`LightCache` 新增 `getExpireTS()` 方法,用于返回项目过期的时间戳
|
||||
- 修复:`savePersistence()` 的部分丢失数据的 bug
|
||||
- 新增:全局方法 `zm_go()`
|
||||
- 修复:2.4.2 版本下的刷屏报错
|
||||
- 优化:Ctrl+C 响应机制,启用异步 重启/关闭 措施,防止残留僵尸进程和丢失数据
|
||||
|
||||
## v2.4.2 (build 402)
|
||||
|
||||
> 更新时间:2021.3.27
|
||||
|
||||
- 更改:`WORKING_DIR` 常量的含义
|
||||
- 修复:未指定 `--remote-terminal` 参数时还依旧开启远程终端的 bug
|
||||
- 删除:`phar_classloader()` 全局方法
|
||||
- 更改:持久化存储 LightCache 的逻辑,修复一个愚蠢的容易造成误用的方式
|
||||
- 新增:LightCache 方法 `addPersistence()` 和 `removePersistence()`
|
||||
- 新增:框架启动短指令 `./zhamao` 或 `php zhamao`
|
||||
|
||||
## v2.4.1 (build 401)
|
||||
|
||||
> 更新时间:2021.3.25
|
||||
|
||||
- 修复:开启框架时导致的报错
|
||||
|
||||
## v2.4.0(build 400)
|
||||
|
||||
> 更新时间:2021.3.25
|
||||
|
||||
- 新增:检查全局配置文件的命令
|
||||
- 新增:全局配置文件更新记录
|
||||
- 依赖变更:**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()` 函数
|
||||
- 新增:`vendor/bin/start systemd:generate` 生成 systemd 配置文件的功能
|
||||
- 新增:`vendor/bin/start check:config` 检查配置文件更新的命令
|
||||
- 新增:`vendor/bin/start init` 新增 `--force` 参数,覆盖现有文件重新生成
|
||||
- 新增:MessageUtil 新增方法:`addShortCommand()`,用于快速添加静态文本问答回复的
|
||||
|
||||
以下是需要**手动更新**或**更换新写法**的部分:
|
||||
|
||||
- 配置文件 `global.php` 中的 `modules` 字段展开,内置模块的配置一律平铺到外面。详见 [更新日志 - 配置文件变更](/update/config)。
|
||||
|
||||
以下是默认机器人直接连接产生的变更:
|
||||
|
||||
- 2.4.0 新增了默认回复其他人 at 的消息,如果不需要,请将 `Hello.php` 中的 `changeAt()` 和 `turingAPI()` 方法删除。
|
||||
|
||||
## v2.3.5 (build 398)
|
||||
|
||||
> 更新时间:2021.3.23
|
||||
|
||||
- 修复:MySQL 数据库查询导致的一系列问题
|
||||
- 修复:内存泄露问题
|
||||
|
||||
> 2.3.2-2.3.4 版本由于操作失误导致代码不完整,请直接使用 2.3.5 即可。
|
||||
|
||||
## v2.3.1
|
||||
|
||||
> 更新时间:2021.3.18
|
||||
|
||||
- 规范代码,修复一个小报错的 bug
|
||||
|
||||
## v2.3.0
|
||||
|
||||
> 更新时间:2021.3.16
|
||||
|
||||
- 新增:MessageUtil 消息处理工具类
|
||||
- 新增:TaskManager,封装了 TaskWorker 进程的应用
|
||||
- 新增:CQObject,使用 `CQ::getCQ()` 可获取对象形式的 CQ 码解析结果
|
||||
- 新增:`@OnTask` 注解,绑定任务函数
|
||||
- 新增:RouteManager 路由管理类,可快速添加路由
|
||||
- 修复:`ZM_DATA` 和 `DataProvider::getDataFolder()` 返回 false 的问题
|
||||
- 优化:关闭显示停止框架后多余的输出信息
|
||||
|
||||
注:本次升级建议升级后合并全局配置文件,有一些新加的内容。
|
||||
|
||||
## v2.2.11
|
||||
|
||||
> 更新时间:2021.3.13
|
||||
|
||||
- 新增:内部 ID 版本号(ZM_VERSION_ID)
|
||||
- 优化:启动时 log 的等级
|
||||
- 移除:终端输入命令
|
||||
- 修复:纯 HTTP 服务器的启动 bug
|
||||
- 新增:`zm_timer` 的报错处理,防止服务器直接崩掉
|
||||
|
||||
## v2.2.10
|
||||
|
||||
> 更新时间:2021.3.8
|
||||
|
||||
- 新增:用户态 php 编译脚本 `build-runtime.sh`
|
||||
- 移除:无用的调试信息
|
||||
- 新增:`--show-php-ver` 启动参数
|
||||
|
||||
## v2.2.9
|
||||
|
||||
> 更新时间:2021.3.6
|
||||
|
||||
- 更新:`reply()` 方法传入数组则变为快速相应的 API 操作
|
||||
- 修复:在 Worker 进程下调用 `ZMUtil::reload()` 会导致一些奇怪的 bug
|
||||
- 修复:`reply()` 时会 at 私聊成员的 bug(由 go-cqhttp 导致)
|
||||
|
||||
## v2.2.8
|
||||
|
||||
> 更新时间:2021.3.2
|
||||
|
||||
- 更新:MOTD 显示的方式,更加直观和炫酷
|
||||
|
||||
## v2.2.7
|
||||
|
||||
> 更新时间:2021.2.27
|
||||
|
||||
- 修复:2.2.6 版本下 `reply()` 方法在群里调用会 at 成员的 bug
|
||||
- 修复:空 `access_token` 的情况下会无法连入的 bug
|
||||
- 修复:使用 Closure 闭包函数自行编写逻辑的判断返回 false 无法阻断连接的 bug
|
||||
|
||||
## v2.2.6
|
||||
|
||||
> 更新时间:2021.2.26
|
||||
|
||||
- 新增:`uuidgen()` 全局函数,快速生成 uuid
|
||||
- 修复:MySQL `rawQuery()` 在参数为非数组时会报 Warning 的 bug
|
||||
- 新增:示例模块的 API 示例:一言查询
|
||||
- 优化:删减部分无用代码
|
||||
- 更改:`ctx()->reply()` 方法改为调用隐藏方法:`.handle_quick_operation`
|
||||
- 修复:`ctx()->finalReply()` 一直以来的 bug(未阻断事件)
|
||||
- 新增:`access_token` 配置项支持闭包函数自行设计判断方式和逻辑
|
||||
- 新增:全局函数 `working_dir()`
|
||||
|
||||
## v2.2.5
|
||||
|
||||
> 更新时间:2021.2.20
|
||||
|
||||
- 新增:`saveToJson()` 和 `loadFromJson()` 方法(DataProvider 类)
|
||||
- 修复:`@OnSave` 注解事件无法工作的 bug
|
||||
- 调整:自定义计时器创建时的性能调优
|
||||
- 新增:WorkerCache 方法:`hasKey()`
|
||||
- 新增:SpinLock 方法:`transaction()`(直接在事务中上锁)
|
||||
- 新增:CQ 方法:`getAllCQ()`,`_custom()`(获取消息中的所有 CQ 码)
|
||||
- 修复:CQ 类中的部分 bug
|
||||
|
||||
## v2.2.4
|
||||
|
||||
> 更新时间:2021.2.7
|
||||
|
||||
- 修复:终端交互导致的 ssh 断掉后 CPU 占用过高的问题
|
||||
- 修复:WorkerCache 在缺少配置文件下工作异常的问题
|
||||
- 新增:全局函数:`zm_atomic()`
|
||||
|
||||
## v2.2.3
|
||||
|
||||
> 更新时间:2021.1.30
|
||||
|
||||
- 修复:waitMessage() 在 v2.2.2 版本中不可用的 bug
|
||||
- 修复:access_token 无效的问题
|
||||
|
||||
## v2.2.2
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 修复:模块文件错误时避免循环报错
|
||||
- 优化:代码结构
|
||||
- 修复:在不同进程时调用机器人 API 无法返回且报错的 bug
|
||||
- **修复:机器人无法连接的问题(2.1.6 ~ 2.2.1 受影响)**
|
||||
|
||||
## v2.2.1
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 修复:配置文件兼容性问题
|
||||
|
||||
## v2.2.0
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 新增:`@OnPipeMessageEvent` 注解
|
||||
- 新增:进程管理器
|
||||
- 新增:`--daemon` 守护进程化后查看状态以及一系列操作的命令行
|
||||
- 新增:WorkerCache
|
||||
- 修复:路由问题
|
||||
- 修复:`http_header` 配置项不生效的 bug
|
||||
- 优化:框架内部所有异常全部基于 `ZMException`
|
||||
- 优化:SingletonTrait 支持扩展
|
||||
|
||||
## v2.1.6
|
||||
|
||||
> 更新时间:2021.1.18
|
||||
|
||||
- 优化:代码结构
|
||||
- 增加:更多提示语
|
||||
- 修复:处理空格消息时的报错
|
||||
- 修复:上下文的bug
|
||||
|
||||
## v2.1.5
|
||||
|
||||
> 更新时间:2021.1.13
|
||||
|
||||
- 优化:终端对 PHP Warning 和 PHP Notice 的报错信息显示,统一格式
|
||||
- 新增:`ctx()->getNumArg()` 上下文中快速获取数字类型的参数的方法
|
||||
- 优化:删除不必要的调试信息
|
||||
- 优化:路由组件全面替换为 `symfony/routing`,兼容性和稳定性 up!
|
||||
|
||||
## v2.1.4
|
||||
|
||||
> 更新时间:2021.1.3
|
||||
|
||||
- 修复:启动时会提示丢失类的 bug
|
||||
- 优化:HTTP 响应类如果被使用了则一律返回 false
|
||||
- 优化:PHP Warning 等报错统一样式
|
||||
|
||||
## v2.1.3
|
||||
|
||||
> 更新时间:2021.1.2
|
||||
|
||||
- 修复:注解解析器在某种特殊情况下导致的 bug
|
||||
|
||||
## v2.1.2
|
||||
|
||||
> 更新时间:2021.1.2
|
||||
|
||||
- 修复:引入包模式启动时会导致的满屏报错
|
||||
|
||||
## v2.1.1
|
||||
|
||||
> 更新时间:2021.1.2
|
||||
|
||||
- 修复:自定义加载注解选定 composer.json 文件错误的 bug
|
||||
|
||||
## v2.1.0
|
||||
|
||||
> 更新时间:2021.1.2
|
||||
|
||||
- 新增:`@OnOpenEvent`,`@OnCloseEvent`,`@OnMessageEvent`,`@OnRequestEvent`
|
||||
- 优化事件分发器,修复一些事件分发过程中的 bug
|
||||
- 修复 `@CQBefore` 事件的 bug
|
||||
|
||||
## v2.0.3
|
||||
|
||||
> 更新时间:2020.12.31
|
||||
|
||||
- 修复:CQBefore 注解事件在 level 低于 200 时无法调用的 bug
|
||||
- 修复:CQMetaEvent 注解事件调用时报错的 bug
|
||||
|
||||
## v2.0.2
|
||||
|
||||
> 更新时间:2020.12.31
|
||||
|
||||
- 更新:将 CQ 码调用类更新到与最新 OneBot 标准相兼容的状态
|
||||
|
||||
## v2.0.1
|
||||
|
||||
> 更新时间:2020.12.23
|
||||
|
||||
- 修复:开屏报错文件夹不存在
|
||||
|
||||
## v2.0
|
||||
|
||||
> 更新时间:2020.12.23
|
||||
|
||||
已发布正式版。
|
||||
Loading…
x
Reference in New Issue
Block a user