Compare commits

...

22 Commits

Author SHA1 Message Date
jerry
7dc39e6ada update to 2.2.11 verion
add build version (start from 384)
make 启动中 log as verbose
remove console input
fix pure http server bug
add error handler for zm_timer_tick and zm_timer_after
2021-03-13 15:16:10 +08:00
jerry
b0be53554d Merge remote-tracking branch 'origin/master' 2021-03-13 03:03:14 +08:00
jerry
b98048bd39 update docs 2021-03-13 03:03:01 +08:00
Whale
fffd3fdc95 Update README.md 2021-03-12 19:45:53 +08:00
jerry
dea9ed2ccd update docs 2021-03-08 00:56:35 +08:00
jerry
de5744c9e4 update to 2.2.10 version
add build-runtime.sh
remove debug msg when stopping
add --show-php-ver argument for server
2021-03-08 00:48:51 +08:00
jerry
a23f3d8f16 update to 2.2.9 version
update reply() to support quick operation
fix reload bug
fix reply() bug
2021-03-06 17:22:42 +08:00
jerry
0c24bfdedd fix a motd bug 2021-03-02 21:31:06 +08:00
jerry
c0b95c6840 delete workflows 2021-03-02 21:27:04 +08:00
jerry
e977b09e20 Merge remote-tracking branch 'origin/master' 2021-03-02 21:24:53 +08:00
jerry
4ff75cf199 update to 2.2.8 version
update motd message
2021-03-02 21:24:31 +08:00
Whale
24e70c70ce Update deploy-docs.yml 2021-03-02 14:27:55 +08:00
Whale
275a7bf00b Update deploy-docs.yml 2021-03-02 14:26:02 +08:00
Whale
455fc79818 Update deploy-docs.yml 2021-03-02 14:22:40 +08:00
Whale
8740c3c255 Update deploy-docs.yml 2021-03-02 14:19:51 +08:00
Whale
98bfca5bb9 Update deploy-docs.yml 2021-03-02 14:18:40 +08:00
Whale
fc8d01ad5f Update deploy-docs.yml 2021-03-02 13:53:12 +08:00
Whale
d9b8df1725 Update deploy-docs.yml 2021-03-02 13:50:52 +08:00
Whale
9b7802ac04 Update deploy-docs.yml 2021-03-02 13:50:39 +08:00
Whale
6e1f3e0406 Update deploy-docs.yml 2021-03-02 13:43:45 +08:00
Whale
a2d4bab062 Update index.md 2021-03-02 13:40:21 +08:00
Whale
f1cefad910 Create deploy-docs.yml 2021-03-02 13:37:07 +08:00
30 changed files with 636 additions and 386 deletions

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ composer.lock
/bin/.phpunit.result.cache
/resources/zhamao.service
.phpunit.result.cache
.daemon_pid
.daemon_pid
/runtime/

View File

@@ -66,6 +66,11 @@ public function index() {
如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。
## 下载源码
框架源码可直接克隆本仓库进行编辑,如果你在国内,访问 GitHub 和 clone 仓库比较慢,可以将 `github.com` 替换为 `fgit.zhamao.me` 进行加速。
例如:`git clone https://fgit.zhamao.me/zhamao-robot/zhamao-framework.git`
## 贡献和捐赠
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。

View File

@@ -1,14 +1,6 @@
#!/usr/bin/env php
<?php
<?php /** @noinspection PhpIncludeInspection */
if (!is_dir(__DIR__ . '/../vendor')) {
define("LOAD_MODE", 1); //composer项目模式
define("LOAD_MODE_COMPOSER_PATH", getcwd());
/** @noinspection PhpIncludeInspection */
require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php";
} else {
define("LOAD_MODE", 0); //源码模式
require_once __DIR__ . "/../vendor/autoload.php";
}
require_once ((!is_dir(__DIR__ . '/../vendor')) ? getcwd() : (__DIR__ . "/..")) . "/vendor/autoload.php";
(new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run();

View File

@@ -21,9 +21,9 @@ function generate($argv) {
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd();
if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd'))
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server --disable-console-input";
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server";
else
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
$s .= "\nExecStart=" . getcwd() . "/bin/start server";
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s);

226
build-runtime.sh Executable file
View File

@@ -0,0 +1,226 @@
#!/usr/bin/env bash
_php_ver="7.4.16"
_libiconv_ver="1.15"
_openssl_ver="1.1.1j"
_swoole_ver="4.6.3"
_home_dir=$(pwd)"/"
function checkEnv() {
echo -n "检测核心组件... "
_msg="请通过包管理安装此依赖!"
type git >/dev/null 2>&1 || { echo "失败git 不存在!"$_msg; return 1; }
type gcc >/dev/null 2>&1 || { echo "失败gcc 不存在!"$_msg; return 1; }
type g++ >/dev/null 2>&1 || { echo "失败g++ 不存在!"$_msg; return 1; }
type unzip >/dev/null 2>&1 || { echo "失败unzip 不存在!"$_msg; return 1; }
type autoconf >/dev/null 2>&1 || { echo "失败autoconf 不存在!"; return 1; }
type pkg-config >/dev/null 2>&1 || { echo "失败pkg-config 不存在!"$_msg; return 1; }
type wget >/dev/null 2>&1 || type curl >/dev/null 2>&1 || { echo "失败curl/wget 不存在!"$_msg; return 1; }
echo "完成!"
echo "如果下载过程中出现错误,请删除 runtime/ 文件夹重试!"
echo "此脚本安装的php/swoole均为最小版本不含其他扩展如zip、xml、gd"
echo -n "如果编译过程缺少依赖,请通过包管理安装对应的依赖![按回车继续] "
# shellcheck disable=SC2034
read ents
}
function downloadIt() {
downloader="wget"
type wget >/dev/null 2>&1 || { downloader="curl"; }
if [ "$downloader" = "wget" ]; then
_down_prefix="O"
else
_down_prefix="o"
fi
_down_symbol=0
if [ ! -f "$2" ]; then
$downloader "$1" -$_down_prefix "$2" >/dev/null 2>&1 && \
echo "完成!" && _down_symbol=1
else
echo "已存在!" && _down_symbol=1
fi
if [ $_down_symbol == 0 ]; then
echo "失败!请检查网络连接!"
rm -rf "$2"
return 1
fi
return 0
}
function downloadAll() {
# 创建文件夹
mkdir "$_home_dir""runtime" >/dev/null 2>&1
mkdir "$_home_dir""runtime/tmp_download" >/dev/null 2>&1
mkdir "$_home_dir""runtime/cellar" >/dev/null 2>&1
_down_dir=$_home_dir"runtime/tmp_download/"
# 下载PHP
echo -n "正在下载 php 源码... "
downloadIt "http://mirrors.sohu.com/php/php-$_php_ver.tar.gz" "$_down_dir""php.tar.gz" || { exit; }
# 下载libiconv
echo -n "正在下载 libiconv 源码... "
downloadIt "https://mirrors.tuna.tsinghua.edu.cn/gnu/libiconv/libiconv-$_libiconv_ver.tar.gz" "$_down_dir""libiconv.tar.gz" || { exit; }
echo -n "正在下载 openssl 源码... "
downloadIt "http://mirrors.cloud.tencent.com/openssl/source/openssl-$_openssl_ver.tar.gz" "$_down_dir""openssl.tar.gz" || { exit; }
echo -n "正在下载 swoole 源码... "
downloadIt "https://dl.zhamao.me/swoole/swoole-$_swoole_ver.tgz" "$_down_dir""swoole.tar.gz" || { exit; }
echo -n "正在下载 composer ... "
downloadIt "https://mirrors.aliyun.com/composer/composer.phar" "$_home_dir""runtime/cellar/composer" || { exit; }
#echo -n "正在下载 libcurl 源码... "
#downloadIt "https://curl.se/download/curl-7.75.0.tar.gz" "$_down_dir""libcurl.tar.gz" || { exit; }
}
function compileIt() {
_down_dir="$_home_dir""runtime/tmp_download/"
_source_dir="$_home_dir""runtime/tmp_source/"
_cellar_dir="$_home_dir""runtime/cellar/"
case $1 in
"libiconv")
if [ -f "$_cellar_dir""libiconv/bin/iconv" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libiconv.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libiconv-"$_libiconv_ver && \
./configure --prefix="$_cellar_dir""libiconv" >/dev/null 2>&1 && \
make -j4 >/dev/null 2>&1 && \
make install >/dev/null 2>&1 && \
echo "完成!"
;;
"libzip")
if [ -f "$_cellar_dir""libzip/bin/libzip" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libzip.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libzip-1.7.3" && \
./configure --prefix="$_cellar_dir""libzip" && \
make -j4 && \
make install && \
echo "完成!"
;;
"libcurl")
if [ -f "$_cellar_dir""libcurl/bin/libcurl" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libcurl.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libcurl-7.75.0" && \
./configure --prefix="$_cellar_dir""libcurl" && \
make -j4 && \
make install && \
echo "完成!"
;;
"php")
if [ -f "$_cellar_dir""php/bin/php" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""php.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""php-"$_php_ver && \
./buildconf --force && \
PKG_CONFIG_PATH="$_cellar_dir""openssl/lib/pkgconfig" ./configure --prefix="$_cellar_dir""php" \
--with-config-file-path="$_home_dir""runtime/etc" \
--disable-fpm \
--enable-cli \
--enable-posix \
--enable-ctype \
--enable-mysqlnd \
--enable-pdo \
--enable-pcntl \
--with-openssl="$_cellar_dir""openssl" \
--enable-sockets \
--disable-xml \
--disable-xmlreader \
--disable-xmlwriter \
--without-libxml \
--disable-dom \
--without-sqlite3 \
--without-pdo-sqlite \
--disable-simplexml \
--with-pdo-mysql=mysqlnd \
--with-zlib \
--with-iconv="$_cellar_dir""libiconv" \
--enable-phar && \
make -j4 && \
make install && \
cp "$_source_dir""php-$_php_ver/php.ini-production" "$_home_dir""runtime/etc/php.ini" && \
echo "完成!"
;;
"openssl")
if [ -f "$_cellar_dir""openssl/bin/openssl" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""openssl.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""openssl-""$_openssl_ver" && \
./config --prefix="$_cellar_dir""openssl" && \
make -j4 && \
make install && \
echo "完成!"
;;
"swoole")
"$_home_dir"runtime/cellar/php/bin/php --ri swoole >/dev/null 2>&1
# shellcheck disable=SC2181
if [ $? == 0 ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""swoole.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""swoole-""$_swoole_ver" && \
PATH="$_cellar_dir""php/bin:$PATH" phpize && \
PATH="$_cellar_dir""php/bin:$PATH" ./configure --prefix="$_cellar_dir""php" \
--enable-sockets \
--enable-http2 \
--enable-openssl \
--with-openssl-dir="$_cellar_dir""openssl" \
--enable-mysqlnd && \
make -j4 && \
make install && \
echo "extension=swoole.so" >> "$_home_dir""runtime/etc/php.ini" && \
echo "完成!"
;;
esac
}
function compileAll() {
_down_dir=$_home_dir"runtime/tmp_download/"
_source_dir=$_home_dir"runtime/tmp_source/"
mkdir "$_source_dir" >/dev/null 2>&1
mkdir "$_home_dir""runtime/etc" >/dev/null 2>&1
echo -n "正在编译 libiconv ... "
compileIt libiconv || { return 1; }
#echo -n "正在编译 libcurl ... "
#compileIt libcurl || { exit; }
echo -n "正在编译 openssl ... "
compileIt openssl || { return 1; }
#echo -n "正在编译 libzip ... "
#compileIt libzip || { exit; }
echo -n "正在编译 php ... "
compileIt php || { return 1; }
echo -n "正在编译 swoole ... "
compileIt swoole || { return 1; }
return 0
}
function linkBin(){
mkdir "$_home_dir""runtime/bin" >/dev/null 2>&1
ln -s "$_home_dir""runtime/cellar/php/bin/php" "$_home_dir""runtime/bin/php" >/dev/null 2>&1
echo "runtime/cellar/php/bin/php runtime/cellar/composer \$@" > "$_home_dir""runtime/bin/composer" && chmod +x "$_home_dir""runtime/bin/composer"
echo "Done!"
runtime/bin/composer config repo.packagist composer https://mirrors.aliyun.com/composer/
}
checkEnv && \
downloadAll && \
compileAll && \
linkBin && \
echo "成功部署所有环境!" && \
echo -e "composer更新依赖\t\"runtime/bin/composer update\"" && \
echo -e "启动框架(源码模式)\t\"runtime/bin/php bin/start server\"" && \
echo -e "启动框架(普通模式)\t\"runtime/bin/php vendor/bin/start server\""

View File

@@ -1,9 +1,8 @@
{
"name": "zhamao/framework",
"description": "High performance QQ robot and web server development framework",
"description": "High performance chat robot and web server development framework",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "2.2.7",
"extra": {
"exclude_annotate": [
"src/ZM"
@@ -11,12 +10,8 @@
},
"authors": [
{
"name": "whale",
"email": "crazysnowcc@gmail.com"
},
{
"name": "swift",
"email": "hugo_swift@yahoo.com"
"name": "jerry",
"email": "admin@zhamao.me"
}
],
"prefer-stable": true,
@@ -52,7 +47,6 @@
]
},
"require-dev": {
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^9.5"
"swoole/ide-helper": "@dev"
}
}

View File

@@ -27,7 +27,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/';
/** 对应swoole的server->set参数 */
$config['swoole'] = [
'log_file' => $config['crash_dir'] . 'swoole_error.log',
'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
//'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
'max_coroutine' => 300000,
//'task_worker_num' => 4,

View File

@@ -1,6 +1,6 @@
______
|__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
______
|__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/

View File

@@ -1 +1,3 @@
# FAQ
这里会写一些常见的疑难解答。

View File

@@ -85,7 +85,6 @@ 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`

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
# 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 的配置段:
```hjson
// 访问密钥, 强烈推荐在公网的服务器设置
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` 的相关章节。

View File

@@ -51,7 +51,7 @@ public function hello() {
* @CQCommand("测试fd")
*/
public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()"机器人QQ是".ctx()->getRobotId());
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()."机器人QQ是".ctx()->getRobotId());
}
```
@@ -421,4 +421,5 @@ public function argTest1() {
<chat-box>
) test abc 334 argtest
( 参数内容abc, 334, argtest
</chat-box>
</chat-box>

View File

@@ -174,7 +174,7 @@ CQ::getAllCQ("[CQ:at,qq=123]你好啊[CQ:at,qq=456]");
定义:`CQ::face($id)`
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [QQ 对应表情ID表](/assets/face_id.html)。
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [QQ 对应表情ID表](https://static.zhamao.me/face_id.html)。
```php
/**

View File

@@ -34,4 +34,21 @@ DataProvider 是框架内提供的一个简易的文件管理类。
定义:`loadFromJson($filename)`
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
## 其他文件读取
框架比较贴近原生的 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()` 获取目录后面需要加 `/` 再追加自己的文件名或子目录名。

View File

@@ -10,29 +10,29 @@
框架的全局配置文件在 `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/` |
| `swoole` | 对应 Swoole server 中 set 的参数参考Swoole文档 | 见子表 `swoole` |
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
| `access_token` | OneBot 客户端连接约定的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 事件注解的类列表 | 见配置文件 |
| `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 |
| `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` |
| 配置名称 | 说明 | 默认值 |
| :--------------------------- | ------------------------------------------------------------ | ---------------------------- |
| `host` | 框架监听的地址 | 0.0.0.0 |
| `port` | 框架监听的端口 | 20001 |
| `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 |
| `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` |
| `debug_mode` | 框架是否启动 debug 模式 | false |
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
| `swoole` | 对应 Swoole server 中 set 的参数参考Swoole文档 | 见子表 `swoole` |
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_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 事件注解的类列表 | 见配置文件 |
| `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 |
| `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` |
### 子表 **swoole**

View File

@@ -8,14 +8,13 @@
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
框架主要用途为 HTTP/WS 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块
在 HTTP 和 WebSocket 服务器上PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。
此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快,未来也会兼容微信公众号开发者模式
此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
```php
/**
@@ -39,9 +38,9 @@ public function index() {
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
1. Linux 命令行(会跑 Linux 程序)
2. php 7.2+ 开发环境
3. HTTP 协议(可选)
4. OneBot 机器人聊天接口标准(可选)
2. php 7.2+ 开发环境(项目会持续支持最新的 PHP 版本)
3. HTTP/WebSocket 协议
4. OneBot 机器人聊天接口标准
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。

View File

@@ -1,5 +1,27 @@
# 更新日志v2 版本)
## 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
@@ -7,7 +29,6 @@
- 修复2.2.6 版本下 `reply()` 方法在群里调用会 at 成员的 bug
- 修复:空 `access_token` 的情况下会无法连入的 bug
- 修复:使用 Closure 闭包函数自行编写逻辑的判断返回 false 无法阻断连接的 bug
-
## v2.2.6

View File

@@ -88,6 +88,7 @@ nav:
- 全局方法: component/global-functions.md
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- Console 终端: component/console.md
- Token 验证: component/access-token.md
- 进阶开发:
- 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md

View File

@@ -17,7 +17,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Framework;
use ZM\Store\ZMAtomic;
use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil;
class PureHttpCommand extends Command
@@ -40,17 +42,26 @@ class PureHttpCommand extends Command
$output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>");
return self::FAILURE;
}
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
$global = ZMConfig::get("global");
$host = $input->getOption("host") ?? $global["host"];
$port = $input->getOption("port") ?? $global["port"];
$index = ["index.html", "index.htm"];
$out = [
"listen" => $host.":".$port,
"version" => ZM_VERSION,
"web_root" => realpath($input->getArgument('dir') ?? '.'),
"index" => implode(",", $index)
];
Framework::printProps($out, $tty_width);
$server = new Server($host, $port);
$server->set(ZMConfig::get("global", "swoole"));
Console::init(0, $server);
Console::init(2, $server);
ZMAtomic::$atomics["request"] = [];
for ($i = 0; $i < 32; ++$i) {
ZMAtomic::$atomics["request"][$i] = new Atomic(0);
}
$index = ["index.html", "index.htm"];
$server->on("request", function (Request $request, Response $response) use ($input, $index, $server) {
ZMAtomic::$atomics["request"][$server->worker_id]->add(1);
HttpUtil::handleStaticPage(
@@ -60,10 +71,11 @@ class PureHttpCommand extends Command
"document_root" => realpath($input->getArgument('dir') ?? '.'),
"document_index" => $index
]);
echo "\r" . Coroutine::stats()["coroutine_peak_num"];
//echo "\r" . Coroutine::stats()["coroutine_peak_num"];
});
$server->on("start", function ($server) {
Process::signal(SIGINT, function () use ($server) {
echo "\r";
Console::warning("Server interrupted by keyboard.");
for ($i = 0; $i < 32; ++$i) {
$num = ZMAtomic::$atomics["request"][$i]->get();
@@ -75,13 +87,6 @@ class PureHttpCommand extends Command
});
Console::success("Server started. Use Ctrl+C to stop.");
});
$out = [
"host" => $host,
"port" => $port,
"document_root" => realpath($input->getArgument('dir') ?? '.'),
"document_index" => implode(", ", $index)
];
Console::printProps($out, $tty_width);
$server->start();
// return this if there was no problem running the command
// (it's equivalent to returning int(0))

View File

@@ -22,10 +22,11 @@ class RunServerCommand extends Command
new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"),
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"),
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"),
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
]);
$this->setDescription("Run zhamao-framework | 启动框架");
@@ -39,7 +40,6 @@ class RunServerCommand extends Command
return Command::FAILURE;
}
}
if (LOAD_MODE == 0) echo "* This is repository mode.\n";
(new Framework($input->getOptions()))->start();
return Command::SUCCESS;
}

View File

@@ -18,31 +18,33 @@ use ZM\Utils\DataProvider;
class ConsoleApplication extends Application
{
const VERSION_ID = 384;
const VERSION = "2.2.11";
public function __construct(string $name = 'UNKNOWN') {
$version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN";
parent::__construct($name, $version);
define("ZM_VERSION_ID", self::VERSION_ID);
define("ZM_VERSION", self::VERSION);
parent::__construct($name, ZM_VERSION);
}
public function initEnv() {
$this->selfCheck();
if (!is_dir(__DIR__ . '/../../vendor')) {
define("LOAD_MODE", 1); // composer项目模式
define("LOAD_MODE_COMPOSER_PATH", getcwd());
} else {
define("LOAD_MODE", 0); // 源码模式
}
//if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令
if (LOAD_MODE === 0) define("WORKING_DIR", getcwd());
elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
/** @noinspection PhpIncludeInspection */
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
}
if (LOAD_MODE == 2) {
// Phar 模式2.0 不提供哦
//require_once FRAMEWORK_DIR . "/vendor/autoload.php";
spl_autoload_register('phar_classloader');
} elseif (LOAD_MODE == 0) {
/** @noinspection PhpIncludeInspection
* @noinspection RedundantSuppression
*/
require_once WORKING_DIR . "/vendor/autoload.php";
if (LOAD_MODE == 0) {
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] ";

View File

@@ -104,25 +104,31 @@ class Context implements ContextInterface
* @param $msg
* @param bool $yield
* @return mixed
* @noinspection PhpMissingBreakStatementInspection
*/
public function reply($msg, $yield = false) {
switch ($this->getData()["message_type"]) {
case "group":
$operation["at_sender"] = false;
// no break
case "private":
case "discuss":
$this->setCache("has_reply", true);
$data = $this->getData();
$conn = $this->getConnection();
$operation["reply"] = $msg;
return (new ZMRobot($conn))->setCallback($yield)->callExtendedAPI(".handle_quick_operation", [
"context" => $data,
"operation" => $operation
]);
$data = $this->getData();
$conn = $this->getConnection();
if (!is_array($msg)) {
switch ($this->getData()["message_type"]) {
case "group":
case "private":
case "discuss":
$this->setCache("has_reply", true);
$operation["reply"] = $msg;
$operation["at_sender"] = false;
return (new ZMRobot($conn))->setCallback($yield)->callExtendedAPI(".handle_quick_operation", [
"context" => $data,
"operation" => $operation
]);
}
return false;
} else {
$operation = $msg;
return (new ZMRobot($conn))->setCallback(false)->callExtendedAPI(".handle_quick_operation", [
"context" => $data,
"operation" => $operation
]);
}
return false;
}
public function finalReply($msg, $yield = false) {

View File

@@ -47,10 +47,8 @@ use ZM\Store\LightCacheInside;
use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool;
use ZM\Store\WorkerCache;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil;
use ZM\Utils\Terminal;
use ZM\Utils\ZMUtil;
class ServerEventHandler
@@ -59,9 +57,9 @@ class ServerEventHandler
* @SwooleHandler("start")
*/
public function onStart() {
global $terminal_id;
//global $terminal_id;
$r = null;
if ($terminal_id !== null) {
/*if ($terminal_id !== null) {
ZMBuf::$terminal = $r = STDIN;
Event::add($r, function () use ($r) {
$fget = fgets($r);
@@ -78,13 +76,18 @@ class ServerEventHandler
Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
}
});
}
}*/
Process::signal(SIGINT, function () use ($r) {
echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
if ((Framework::$server->inotify ?? null) !== null)
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
ZMUtil::stop();
if (zm_atomic("_int_is_reload")->get() === 1) {
zm_atomic("_int_is_reload")->set(0);
ZMUtil::reload();
} else {
echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
if ((Framework::$server->inotify ?? null) !== null)
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
ZMUtil::stop();
}
});
if (Framework::$argv["daemon"]) {
$daemon_data = json_encode([
@@ -154,7 +157,7 @@ class ServerEventHandler
else server()->shutdown();
});
Console::info("Worker #{$server->worker_id} 启动中");
Console::verbose("Worker #{$server->worker_id} 启动中");
Framework::$server = $server;
//ZMBuf::resetCache(); //清空变量缓存
//ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
@@ -228,7 +231,7 @@ class ServerEventHandler
$this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
//echo json_encode(debug_backtrace(), 128|256);
Console::success("Worker #" . $worker_id . " 已启动");
EventManager::registerTimerTick(); //启动计时器
//ZMBuf::unsetCache("wait_start");
set_coroutine_params(["server" => $server, "worker_id" => $worker_id]);
@@ -239,6 +242,7 @@ class ServerEventHandler
$dispatcher->dispatchEvents($server, $worker_id);
if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) Console::debug("@OnStart 执行完毕");
else Console::warning("@OnStart 执行异常!");
Console::success("Worker #" . $worker_id . " 已启动");
} catch (Exception $e) {
Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
@@ -311,7 +315,7 @@ class ServerEventHandler
Console::trace();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace();
}
@@ -461,7 +465,7 @@ class ServerEventHandler
Console::trace();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace();
}
//EventHandler::callSwooleEvent("open", $server, $request);
@@ -508,7 +512,7 @@ class ServerEventHandler
Console::trace();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"close\": " . $error_msg);
Console::error("Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg);
Console::trace();
}
ManagerGM::popConnect($fd);
@@ -636,7 +640,8 @@ class ServerEventHandler
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($dir as $v) {
if (is_dir($path . "/" . $v) && isset($composer["autoload"]["psr-4"][$v . "\\"]) && !in_array($composer["autoload"]["psr-4"][$v . "\\"], $composer["extra"]["exclude_annotate"] ?? [])) {
Console::verbose("Add " . $v . " to register path");
if (\server()->worker_id == 0)
Console::verbose("Add " . $v . " to register path");
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v);
}
}

View File

@@ -9,6 +9,7 @@ use Exception;
use ZM\Annotation\Swoole\OnSetup;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
use ZM\Event\ServerEventHandler;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
@@ -73,8 +74,7 @@ class Framework
die($e->getMessage());
}
try {
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
$this->server_set = ZMConfig::get("global", "swoole");
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
self::$server,
@@ -85,37 +85,45 @@ class Framework
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
date_default_timezone_set($timezone);
$this->server_set = ZMConfig::get("global", "swoole");
$this->parseCliArgs(self::$argv);
$out = [
"host" => ZMConfig::get("global", "host"),
"port" => ZMConfig::get("global", "port"),
"log_level" => Console::getLevel(),
"version" => ZM_VERSION,
"config" => $args["env"] === null ? 'global.php' : $args["env"]
];
// 打印初始信息
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
if (!isset(ZMConfig::get("global", "swoole")["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
else $out["worker"] = ZMConfig::get("global", "swoole")["worker_num"];
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
$out["log_level"] = Console::getLevel();
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
$out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"];
$out["task_worker"] = ZMConfig::get("global", "swoole")["task_worker_num"];
}
if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) {
$out["worker_num"] = $num;
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
$conf = ZMConfig::get("global", "sql_config");
$out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"];
}
if (ZMConfig::get("global", "redis_config")["host"] !== "") {
$conf = ZMConfig::get("global", "redis_config");
$out["redis_pool"] = $conf["host"] . ":" . $conf["port"];
}
if (ZMConfig::get("global", "static_file_server")["status"] !== false) {
$out["static_file_server"] = "enabled";
}
if (self::$argv["show-php-ver"] !== false) {
$out["php_version"] = PHP_VERSION;
$out["swoole_version"] = SWOOLE_VERSION;
}
$out["working_dir"] = DataProvider::getWorkingDir();
Console::printProps($out, $tty_width);
self::printProps($out, $tty_width, $args["log-theme"] === null);
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
self::$server->set($this->server_set);
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
} else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
}
$motd = explode("\n", $motd);
foreach ($motd as $k => $v) {
$motd[$k] = substr($v, 0, $tty_width);
}
$motd = implode("\n", $motd);
echo $motd;
Console::setServer(self::$server);
self::printMotd($tty_width);
global $asd;
$asd = get_included_files();
// 注册 Swoole Server 的事件
@@ -167,10 +175,25 @@ class Framework
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage());
Console::debug($e);
die;
}
}
private static function printMotd($tty_width) {
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
} else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
}
$motd = explode("\n", $motd);
foreach ($motd as $k => $v) {
$motd[$k] = substr($v, 0, $tty_width);
}
$motd = implode("\n", $motd);
echo $motd;
}
public function start() {
self::$server->start();
}
@@ -271,6 +294,7 @@ class Framework
Console::$theme = $y;
}
break;
case 'show-php-ver':
default:
//Console::info("Calculating ".$x);
//dump($y);
@@ -280,6 +304,92 @@ class Framework
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
}
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
$tmp_line = $k . ": " . $v;
//Console::info("写入[".$tmp_line."]");
if (strlen($tmp_line) >= $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
$title_strlen = strlen($k . ": ");
$content_len = $line_width[$current_line] - $title_strlen;
$line_data[$current_line] = " " . $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= substr($v, 0, $content_len);
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
$rest = substr($v, $content_len);
++$current_line; // 带标题的第一行满了,折到第二行
do {
if ($colorful) $line_data[$current_line] = TermColor::color8(32);
$line_data[$current_line] .= " " . substr($rest, 0, $max_border - 2);
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
$rest = substr($rest, $max_border - 2);
++$current_line;
} while ($rest > $max_border - 2); // 循环,直到放完
} else { // 不需要折行
//Console::info("不需要折行");
$line_data[$current_line] = " " . $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= $v;
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
if ($max_border >= 57) {
if (strlen($tmp_line) >= intval(($max_border - 2) / 2)) { // 不需要折行,直接输出一个转下一行
//Console::info("不需要折行,直接输出一个转下一行");
++$current_line;
} else { // 输出很小,写到前面并分片
//Console::info("输出很小,写到前面并分片");
$space = intval($max_border / 2) - 2 - strlen($tmp_line);
$line_data[$current_line] .= str_pad("", $space, " ");
$line_data[$current_line] .= "| "; // 添加分片
$line_width[$current_line] -= (strlen($tmp_line) + 3 + $space);
}
} else {
++$current_line;
}
}
}
public static function printProps($out, $tty_width, $colorful = true) {
$max_border = $tty_width < 65 ? $tty_width : 65;
if (LOAD_MODE == 0) echo Console::setColor("* Framework started with source mode.\n", $colorful ? "yellow" : "");
echo str_pad("", $max_border, "=") . PHP_EOL;
$current_line = 0;
$line_width = [];
$line_data = [];
foreach ($out as $k => $v) {
start:
if (!isset($line_width[$current_line])) {
$line_width[$current_line] = $max_border - 2;
}
//Console::info("行宽[$current_line]".$line_width[$current_line]);
if ($max_border >= 57) { // 很宽的时候,一行能放两个短行
if ($line_width[$current_line] == ($max_border - 2)) { //空行
self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border);
} else { // 不是空行,已经有东西了
$tmp_line = $k . ": " . $v;
//Console::info("[$current_line]即将插入后面的东西[".$tmp_line."]");
if (strlen($tmp_line) > $line_width[$current_line]) { // 地方不够,另起一行
$line_data[$current_line] = str_replace("| ", "", $line_data[$current_line]);
++$current_line;
goto start;
} else { // 地方够,直接写到后面并另起一行
$line_data[$current_line] .= $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= $v;
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
++$current_line;
}
}
} else { // 不够宽,直接写单行
self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border);
}
}
foreach ($line_data as $v) {
echo $v . PHP_EOL;
}
echo str_pad("", $max_border, "=") . PHP_EOL;
}
public static function getTtyWidth() {
return explode(" ", trim(exec("stty size")))[1];
}

View File

@@ -16,17 +16,10 @@ class LightCacheInside
public static $last_error = '';
public static function init() {
self::$kv_table["wait_api"] = new Table(3, 0);
self::$kv_table["wait_api"]->column("value", Table::TYPE_STRING, 65536);
self::$kv_table["connect"] = new Table(8, 0);
self::$kv_table["connect"]->column("value", Table::TYPE_STRING, 256);
$result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create();
if ($result === false) {
self::$last_error = '系统内存不足,申请失败';
return false;
} else {
return true;
}
self::createTable("wait_api", 3, 65536); //用于存协程等待的状态内容的
self::createTable("connect", 3, 64); //用于存单机器人模式下的机器人fd的
//self::createTable("worker_start", 2, 1024);//用于存启动服务器时的状态的
return true;
}
/**
@@ -62,4 +55,17 @@ class LightCacheInside
public static function unset(string $table, string $key) {
return self::$kv_table[$table]->del($key);
}
/**
* @param $name
* @param $size
* @param $str_size
* @throws ZMException
*/
private static function createTable($name, $size, $str_size) {
self::$kv_table[$name] = new Table($size, 0);
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
$r = self::$kv_table[$name]->create();
if ($r === false) throw new ZMException("内存不足,创建静态表失败!");
}
}

View File

@@ -28,6 +28,7 @@ class ZMAtomic
self::$atomics[$k] = new Atomic($v);
}
self::$atomics["stop_signal"] = new Atomic(0);
self::$atomics["_int_is_reload"] = new Atomic(0);
self::$atomics["wait_msg_id"] = new Atomic(0);
self::$atomics["_event_id"] = new Atomic(0);
for ($i = 0; $i < 10; ++$i) {

View File

@@ -11,13 +11,16 @@ use Swoole\Timer;
use ZM\Console\Console;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic;
use ZM\Store\ZMBuf;
class ZMUtil
{
public static function stop() {
if (SpinLock::tryLock("_stop_signal") === false) return;
Console::warning(Console::setColor("Stopping server...", "red"));
if (Console::getLevel() >= 4) Console::trace();
LightCache::savePersistence();
if (ZMBuf::$terminal !== null)
Event::del(ZMBuf::$terminal);
@@ -31,6 +34,12 @@ class ZMUtil
}
public static function reload($delay = 800) {
if (server()->worker_id !== -1) {
Console::info(server()->worker_id);
zm_atomic("_int_is_reload")->set(1);
system("kill -INT " . intval(server()->master_pid));
return;
}
Console::info(Console::setColor("Reloading server...", "gold"));
usleep($delay * 1000);
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {

View File

@@ -5,8 +5,7 @@ use ZM\Utils\DataProvider;
define("ZM_START_TIME", microtime(true));
define("ZM_DATA", ZMConfig::get("global", "zm_data"));
define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown");
define("APP_VERSION", json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true)["version"] ?? "unknown");
define("APP_VERSION", LOAD_MODE == 1 ? (json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true)["version"] ?? "unknown") : "unknown");
define("CRASH_DIR", ZMConfig::get("global", "crash_dir"));
@mkdir(ZM_DATA);
@mkdir(CRASH_DIR);

View File

@@ -261,19 +261,33 @@ function zm_yield() { Co::yield(); }
function zm_resume(int $cid) { Co::resume($cid); }
function zm_timer_after($ms, callable $callable) {
go(function () use ($ms, $callable) {
Swoole\Timer::after($ms, $callable);
Swoole\Timer::after($ms, function () use ($callable) {
call_with_catch($callable);
});
}
function call_with_catch($callable) {
try {
$callable();
} catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace();
}
}
function zm_timer_tick($ms, callable $callable) {
if (zm_cid() === -1) {
return go(function () use ($ms, $callable) {
Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable);
Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
});
} else {
return Swoole\Timer::tick($ms, $callable);
return Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
}
}