diff --git a/bin/start b/bin/start index 5a518c8f..ebb92c7f 100755 --- a/bin/start +++ b/bin/start @@ -1,28 +1,14 @@ #!/usr/bin/env php initEnv(); -$application->run(); + +(new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run(); diff --git a/composer.json b/composer.json index e1ae4c52..788f9497 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.1.5", + "version": "2.1.6", "extra": { "exclude_annotate": [ "src/ZM" diff --git a/docs/advanced/connect-ws-client.md b/docs/advanced/connect-ws-client.md new file mode 100644 index 00000000..ea08148d --- /dev/null +++ b/docs/advanced/connect-ws-client.md @@ -0,0 +1,143 @@ +# 接入 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` 类型接入。 + +!!! note "提示" + + 对于对接 OneBot 标准的机器人客户端,只要符合 OneBot 标准,即 `X-Client-Role` 会自动带上 `universal`、`qq` 等字样,就会自动标记为 `qq` 类型。 + +## 逻辑编写 + +传入连接后,我们就能通过注解事件绑定来做我们自己想做的事情了!比如下方是传入类型为 foo 连接要做的事情 + +```php +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()); +``` + diff --git a/docs/advanced/custom-start.md b/docs/advanced/custom-start.md new file mode 100644 index 00000000..369edbc7 --- /dev/null +++ b/docs/advanced/custom-start.md @@ -0,0 +1,107 @@ +# 框架高级启动 + +## 框架下载方式 + +从前面的几章中,我们了解到框架有多种下载到本地的方式。 + +- Composer 依赖模式 +- Starter 从模板创建模式 +- 源码模式 + +### 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-console-input`:关闭终端交互,如果你使用的不是 tmux、screen 而是直接将进程使用 systemd 等方式运行到 init 守护进程下,则需要关闭终端交互输入,关闭后不可以使用 `stop, reload, logtest` 等交互命令。 +- `--disable-coroutine`:关闭一键协程化。 +- `--daemon`:以守护进程方式运行框架,此参数将直接在输出 motd 后将进程挂到 init 下运行,后台常驻。 +- `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展:inotify。使用 pecl 就可以安装:`pecl install inotify`。 +- `--env`:设置运行环境,设置运行环境后将优先加载指定环境的配置文件,支持 `--env=production`,`--env=staging`,`--env=development`,见 [基本配置](/guide/basic-config/#_2)。 + +## 独立启动其他组件 + +框架默认不止启动框架的 `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` 配置文件中的配置。 \ No newline at end of file diff --git a/docs/advanced/framework-structure.md b/docs/advanced/framework-structure.md new file mode 100644 index 00000000..8511afc1 --- /dev/null +++ b/docs/advanced/framework-structure.md @@ -0,0 +1,6 @@ +# 框架剖析 + +## 框架运行总结构图 + +![](../assets/img/framework-structure.png) + diff --git a/docs/advanced/index.md b/docs/advanced/index.md index fa8e1a9d..06229b11 100644 --- a/docs/advanced/index.md +++ b/docs/advanced/index.md @@ -1,3 +1,9 @@ # 进阶开发 -## 深入 -还没填坑,敬请期待! \ No newline at end of file +在本章,下面的部分将详细说明一些具体的案例和自定义框架的操作。 + +- 如何自定义修改框架本身?- [框架启动方式](/advanced/custom-start/) +- 如何接入一个自己的 WebSocket 客户端?- [接入 WebSocket 客户端](/advanced/connect-ws-client/) +- 框架到底是怎么工作的?- [框架结构剖析](/advanced/framework-structure/) + +> 更多进阶教程敬请期待....(或者你可以选择提 Issue 到框架 GitHub,有需求就写入文档) + diff --git a/docs/assets/img/framework-structure.png b/docs/assets/img/framework-structure.png new file mode 100644 index 00000000..6a59290e Binary files /dev/null and b/docs/assets/img/framework-structure.png differ diff --git a/docs/component/console.md b/docs/component/console.md index f1d2fff1..23606f03 100644 --- a/docs/component/console.md +++ b/docs/component/console.md @@ -158,4 +158,20 @@ color green 我是绿色的字 在 1.4 版本开始,框架支持启动时的 motd 内容修改。 -文件位置:`config/motd.txt` \ No newline at end of file +文件位置:`config/motd.txt` + +## 设置输出主题 + +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 带有任何颜色,使用无色主题 +``` + diff --git a/docs/component/context.md b/docs/component/context.md index e4db3258..8d0629db 100644 --- a/docs/component/context.md +++ b/docs/component/context.md @@ -114,7 +114,7 @@ public function ping() { ## getConnection() - WS 连接对象 -返回此上下文相关联的 WebSocket 连接对象。 +返回此上下文相关联的 WebSocket 连接对象。详见 [进阶 - 接入 WebSocket 客户端](/advanced/connect-ws-client)。 可以使用的事件:所有 **getFrame()** 可以使用的都可以使用。 diff --git a/docs/event/framework-annotations.md b/docs/event/framework-annotations.md index 242e51b3..908ff9ed 100644 --- a/docs/event/framework-annotations.md +++ b/docs/event/framework-annotations.md @@ -2,20 +2,133 @@ 框架核心注解事件区别于机器人和路由注解事件,这里框架注解事件都是**直接**或封装调用 Swoole 的回调事件的,所以对一些比较底层或者基础的操作都在这里做,例如收到 HTTP 或 WebSocket 连接后执行的事件函数。 -## OnSwooleEvent() +## OnOpenEvent() -绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接,HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。请尽量使用上面几个新的注解。 +当有 WebSocket 连接接入框架时,触发注解事件。 ### 属性 -| 类型 | 值 | -| ------------ | ------------------------------------------ | -| 名称 | `@OnSwooleEvent` | -| 触发前提 | 当参数指定的 `type` 对应的事件被触发后激活 | -| 命名空间 | `ZM\Annotation\Swoole\OnSwooleEvent` | -| 适用位置 | 方法 | -| 返回值处理 | 无 | -| 注解绑定参数 | | +| 类型 | 值 | +| ---------- | ------------------------------------------- | +| 名称 | `@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 连接的连接对象。 + +## OnSwooleEvent() + +绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接,HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。**请尽量使用上面几个新的注解**。 + +### 属性 + +| 类型 | 值 | +| ---------- | ------------------------------------------ | +| 名称 | `@OnSwooleEvent` | +| 触发前提 | 当参数指定的 `type` 对应的事件被触发后激活 | +| 命名空间 | `ZM\Annotation\Swoole\OnSwooleEvent` | +| 适用位置 | 方法 | +| 返回值处理 | 无 | ### 注解参数 @@ -27,9 +140,70 @@ ### 事件绑定参数 -`$conn`: [ConnectionObject](/advanced/inside-class/) 类型,返回一个当前 WS 连接的连接对象。 +`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。 -### 示例1(机器人连接框架后输出信息) +## 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 进程 | | + +## OnSetup() + +在框架加载前执行的代码。此部分代码是在主进程执行的,不可在此事件中使用任何协程相关的功能。 + +比如我们要改变所有进程的 ini 设置,这时使用 `@OnStart(-1)` 这样只设置了 Worker 进程的内容,而主进程和管理进程无法被覆盖到。如果需要设置全局的一些配置,务必在此 `@OnSetup` 注解下执行。 + +### 属性 + +| 类型 | 值 | +| ---------- | ------------------------------ | +| 名称 | `@OnSetup` | +| 触发前提 | 在框架加载前激活 | +| 命名空间 | `ZM\Annotation\Swoole\OnSetup` | +| 适用位置 | 方法 | +| 返回值处理 | 无 | + +### 注解参数 + +无。 + +## 示例1(机器人连接框架后输出信息) ```php getRequest()->server['request_uri'] == '/favicon.ico'",level=200) + * @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200) */ public function onRequest() { EventDispatcher::interrupt(); @@ -69,4 +243,107 @@ class Hello { } ``` -其中 EventDispatcher 为事件分发器,interrupt 是通用阻断方法,如果你平常只使用阻断,则只需掌握这一个方法即可,`EventDispatcher::interrupt()` 在所有事件内可用。 \ No newline at end of file +其中 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 +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] 加载题库完成! +``` + +聊天效果: + + +) 找题 1 +( 题目名称:法律与其他社会规范的区别在于( )。\nA. 是调整人们行为的规范\nB. 有约束力\nC. 由国家强制力保证执行\nD. 规定制裁措施\n正确答案:C + + +## 示例5(创建每分钟自动执行的爬虫) + +```php +/** + * @OnTick(tick_ms=60000,worker_id=0) + */ +public function onCrawl() { + $data = Foo::bar(); //这里是你自己写的要爬的接口等等一系列操作 + LightCache::set("your_data_key_name", $data); //将爬虫数据存入 LightCache 轻量缓存 +} +``` + diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index 69f16a42..ef4a3254 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -75,13 +75,13 @@ setTimeout(() => { if(j === '') continue; if(j.substr(0, 2) === ') ') { final += '
\n' + - '
' + j.substr(2) + '
\n' + + '
' + j.substr(2).replaceAll("\\n", "
") + '
\n' + ' \n' + '
'; } else if (j.substr(0, 2) === '( ') { final += '
\n' + ' \n' + - '
' + j.substr(2) + '
\n' + + '
' + j.substr(2).replaceAll("\\n", "
") + '
\n' + '
'; } else if (j.substr(0, 2) === '^ ') { final += '
' + j.substr(2) + '
'; diff --git a/mkdocs.yml b/mkdocs.yml index 3a148eac..8cec69eb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -89,8 +89,11 @@ nav: - Console 终端: component/console.md - 进阶开发: - 进阶开发: advanced/index.md + - 框架剖析: advanced/framework-structure.md + - 框架启动模式: advanced/custom-start.md - 从 v1 升级: advanced/to-v2.md - 内部类文件手册: advanced/inside-class.md + - 接入 WebSocket 客户端: advanced/connect-ws-client.md - FAQ: FAQ.md - 更新日志: - 更新日志(v2): update/v2.md diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 8e886506..5ee91712 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -55,9 +55,9 @@ class Hello */ public function randNum() { // 获取第一个数字类型的参数 - $num1 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第一个数字"); + $num1 = ctx()->getNumArg("请输入第一个数字"); // 获取第二个数字类型的参数 - $num2 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第二个数字"); + $num2 = ctx()->getNumArg("请输入第二个数字"); $a = min(intval($num1), intval($num2)); $b = max(intval($num1), intval($num2)); // 回复用户结果 diff --git a/src/ZM/Command/InitCommand.php b/src/ZM/Command/InitCommand.php index 19090668..4e837f28 100644 --- a/src/ZM/Command/InitCommand.php +++ b/src/ZM/Command/InitCommand.php @@ -74,6 +74,7 @@ class InitCommand extends Command echo("Error occurred. Please check your updates.\n"); return Command::FAILURE; } + $output->writeln("Done!"); return Command::SUCCESS; } elseif (LOAD_MODE === 2) { //从phar启动的框架包,初始化的模式 $phar_link = new Phar(__DIR__); diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 8e776c0a..748a76ae 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -75,6 +75,7 @@ class ConsoleApplication extends Application if (!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command"); $this->add($obj); }*/ + return $this; } /** diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index 00cbda60..249cf900 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -117,6 +117,8 @@ interface ContextInterface public function cloneFromParent(); + public function getNumArg($prompt_msg = ""); + public function copy(); public function getOption(); diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php index a0404a29..beaac370 100644 --- a/src/ZM/Event/EventManager.php +++ b/src/ZM/Event/EventManager.php @@ -38,7 +38,7 @@ class EventManager public static function registerTimerTick() { $dispatcher = new EventDispatcher(OnTick::class); foreach (self::$events[OnTick::class] ?? [] as $vss) { - if (server()->worker_id !== $vss->worker_id) return; + if (server()->worker_id !== $vss->worker_id && $vss->worker_id != -1) return; //echo server()->worker_id.PHP_EOL; $plain_class = $vss->class; Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method); diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 98324a3c..761b575b 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -386,7 +386,7 @@ class ServerEventHandler public function onOpen($server, Request $request) { Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); unset(Context::$context[Co::getCid()]); - $type = strtolower($request->get["type"] ?? $request->header["x-client-role"] ?? ""); + $type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? ""); $type_conn = ManagerGM::getTypeClassName($type); ManagerGM::pushConnect($request->fd, $type_conn); $conn = ManagerGM::get($request->fd); diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 2e450a5b..084ad444 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -78,6 +78,7 @@ class QQBot switch ($data["post_type"]) { case "message": $word = explodeMsg(str_replace("\r", "", context()->getMessage())); + if (empty($word)) $word = [""]; if (count(explode("\n", $word[0])) >= 2) { $enter = explode("\n", context()->getMessage()); $first = split_explode(" ", array_shift($enter));