diff --git a/docs/component/console.md b/docs/component/console.md new file mode 100644 index 00000000..f1d2fff1 --- /dev/null +++ b/docs/component/console.md @@ -0,0 +1,161 @@ +# 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` \ No newline at end of file diff --git a/docs/component/coroutine-pool.md b/docs/component/coroutine-pool.md new file mode 100644 index 00000000..0fbd256c --- /dev/null +++ b/docs/component/coroutine-pool.md @@ -0,0 +1,63 @@ +# 协程池 + +首先要声明的一点是,协程池这个概念是我自己编的。 + +因为协程的特点,它是单线程下运行的,所以在一个进程内同时实际上只会有一个协程的代码在执行逻辑,但是后面的 IO 操作、协程挂起等待的操作都是同时去做的,比如数据库的大数据读取、写入需要耗时几秒甚至几十秒,这时用基于协程的 MySQL 连接池就完全不是问题。 + +但是就拿 MySQL 举例,我们 MySQL 使用的是 TCP 连接,而无论是 MySQL 还是 TCP 连接,最大数量都是有限的。我们即使设置了允许最大协程数量非常大,比如上百万,但是也不能让数据库连接池一个池支持上百万的连接。 + +这时假设高并发进来了怎么办呢?这时就需要框架提出的一个折中方案:协程池了。 + +顾名思义,协程池是一个容纳协程的区域,而协程里又容纳着各种各样需要阻塞调用被协程调用的 IO 操作,协程池用作限制协程的数量。 + +```php +use ZM\Utils\CoroutinePool; +use ZM\DB\DB; + +// 传统写法,一旦高并发则可能导致 Too many connections +go(funuction(){ + DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]); +}); +// 协程池写法 +CoroutinePool::go(function(){ + DB::rawQuery("INSERT INTO users VALUES(?,?)", ["admin", "password"]); +}, "foo"); +``` + +参数:`go(callable $func, $name = "default")` + +`$name` 为协程池对应的名字,你可以设置多个协程池,用来支持不同的需要限制并发 IO 数量的地方,例如 Redis 和 MySQL 设置不同的名字。`$func` 可为闭包或可调用的方法名称或数组。 + +## 配置 + +默认情况下,直接调用 `CoroutinePool::go()` 时,协程池大小为 30,也就是如果有 30 个协程进入了挂起状态(比如数据库在执行查询语句),那么更多的协程执行时就会阻塞并以协程等待的方式等待,直到现有的 30 个协程中的一部分完成了它的工作。 + +## 方法 + +### CoroutinePool::go() + +将协程放入协程池运行。 + +如果不写 `$name` 参数,则使用的是默认协程池。 + +### CoroutinePool::defaultSize() + +设置默认协程池的大小(默认 30) + +```php +CoroutinePool::defaultSize(64); +for($i = 0; $i < 1000; ++$i) { + CoroutinePool::go(function(){ + DB::rawQuery("SELECT * FROM users"); + }); +} +``` + +### CoroutinePool::setSize() + +定义:`setSize($name, int $size)` + +`$name` 为字符串,是你要用的协程池的名称。 + +`$size` 为大小,最大不可超过 Swoole 配置文件中指定的最大协程数量。 + diff --git a/docs/component/global-functions.md b/docs/component/global-functions.md new file mode 100644 index 00000000..7b9bfbeb --- /dev/null +++ b/docs/component/global-functions.md @@ -0,0 +1,231 @@ +# 全局方法 + +全局方法就是 PHP 的全局函数,任意位置都可以调用,无需使用 use 字样。 + +## getClassPath() + +根据加载的用户编写的代码类名来获取类所在的文件路径。 + +=== "src/Module/Example/Hello.php" + + ```php + 你今天喝水了吗 +- `*的天气怎么样` -> 德州的天气怎么样 +- `把*翻译成*` -> 把茶翻译成英语 + +定义:`matchPattern($pattern, $context)` + +`$pattern` 为匹配模式,例如 `你今天*了吗`。 + +`$context` 为要判断是否匹配的内容。 + +返回值:`bool`,当为 true 时代表规则是匹配的,false 代表不匹配。 + +```php +matchPattern("*是个啥?", "996是个啥?"); // true +matchPattern("我想听*唱歌", "你想听谁唱歌"); // false +matchPattern("*把*翻译成*", "请把你好翻译成阿拉伯语"); // true +``` + +## split_explode() + +和 `explodeMsg()` 类似,用作分割字符串,不过此函数加入了对 `中文|数字` 两者的分割,也就是说中文和数字之间也会被分割。 + +定义:`split_explode($del, $str, $divide_en = false)` + +```php +split_explode(" ", "前进20 急啊急啊"); // ["前进","20","急啊急啊"] +``` + +`$del` 和 `explode()` 的第一个参数作用相同,作为初期分割的标志。 + +`$str` 表示待分割的内容。 + +`$divide_en` 表示是否分割中文和英文,如果为是,则中文和英文之间也会被分割开。 + +## matchArgs() + +`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_debug() + +同 `Console::debug($msg)`。 + +## 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, "你好啊!!"); +``` + + + diff --git a/docs/component/singleton-trait.md b/docs/component/singleton-trait.md new file mode 100644 index 00000000..9f07ce22 --- /dev/null +++ b/docs/component/singleton-trait.md @@ -0,0 +1,43 @@ +# 单例类(SingletonTrait) + +单例类,顾名思义,就是让用户声明的类拥有单例的特性,而这一组件引入的方式也最直接。它是一个 PHP 的 `trait`。 + +我们传统写单例类的方式很手动,比如下面这样: + +```php +test = 4; +$obj = Foo::getInstance()->test; +var_dump($obj); // 4 +``` + +这就要求我们每个需要声明为单例的类都写一个成员静态方法和成员静态变量。 + +框架使用了 PHP Trait 来快速让一个类支持这一特性: + +```php +test = 5; +var_dump(Foo::getInstance()->test); +``` + +只需要在类中使用:`use \ZM\Utils\SingletonTrait;` 一句话即可。 \ No newline at end of file diff --git a/docs/component/spin-lock.md b/docs/component/spin-lock.md index ab2d1c39..02db41b6 100644 --- a/docs/component/spin-lock.md +++ b/docs/component/spin-lock.md @@ -70,4 +70,4 @@ public function test() { ## 性能 -使用自旋锁几乎没有性能损失,相反,使用自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下,使用自旋锁和不适用自旋锁的测试成绩时间分别为:7.4s 和 6.9s。 \ No newline at end of file +使用自旋锁几乎没有性能损失,自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下,使用自旋锁和不适用自旋锁的测试成绩时间分别为:7.4s 和 6.9s。 \ No newline at end of file diff --git a/docs/component/zmrequest.md b/docs/component/zmrequest.md new file mode 100644 index 00000000..46008281 --- /dev/null +++ b/docs/component/zmrequest.md @@ -0,0 +1,272 @@ +# 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; //