Compare commits

...

56 Commits

Author SHA1 Message Date
crazywhalecc
d01bd69aa5 update to 2.7.0-beta1 (build 435) 2022-03-13 22:50:32 +08:00
crazywhalecc
e6b9ae3ee1 add --watch function for no-installed-inotify users 2022-03-13 22:50:01 +08:00
crazywhalecc
73b6b8045d enhancement for process state 2022-03-13 22:47:11 +08:00
crazywhalecc
3c87abc6e8 enhancement for process state 2022-03-13 22:46:22 +08:00
crazywhalecc
e95925c129 add compatibility for PHP 8.1 2022-03-13 22:16:02 +08:00
crazywhalecc
82c44d6c40 enhancement for process state 2022-03-13 22:15:27 +08:00
crazywhalecc
e0a268e05e add autoload-dev auto scanner 2022-03-13 22:11:30 +08:00
crazywhalecc
487892e1d9 add force kill framework command --force 2022-03-13 22:05:53 +08:00
crazywhalecc
7ab4e88359 add KILLER PROMPT function 2022-03-13 22:05:23 +08:00
crazywhalecc
4702b6ee75 separate ProcessManager and WorkerManager 2022-03-13 22:04:52 +08:00
crazywhalecc
20cd3aa66d update docs 2022-03-13 22:03:52 +08:00
crazywhalecc
391114bdef update docs 2022-03-13 21:57:41 +08:00
crazywhalecc
ffe1052ecc add gitignore items 2022-03-13 21:54:43 +08:00
crazywhalecc
b4159152a7 add watcher 2022-01-08 20:19:10 +08:00
crazywhalecc
8fc6e4b0f7 update docs 2022-01-08 16:26:47 +08:00
crazywhalecc
c8938b7a4b update docs 2022-01-08 16:23:10 +08:00
crazywhalecc
34db1626a5 update to 2.6.6 (build 434) 2022-01-08 16:19:43 +08:00
crazywhalecc
7f0c97c5b9 update to 2.6.5 (build 433) 2021-12-28 22:40:40 +08:00
crazywhalecc
74050c46e7 update to 2.6.4 (build 432) 2021-12-25 19:21:41 +08:00
3ed1cb665a update to build 431 2021-12-22 14:39:46 +08:00
crazywhalecc
a6f33ba69d update to build 430 2021-12-08 21:29:38 +08:00
Jerry Ma
176b690417 Update v2.md 2021-12-07 13:28:21 +08:00
Jerry Ma
e22b1b90ec Update build-update.md 2021-12-07 13:27:33 +08:00
Jerry Ma
a3c560790c Merge pull request #55 from zhamao-robot/fix/cqafter
update to build 429
2021-12-07 13:25:55 +08:00
570e2108dd - 新增配置项 onebot.message_command_policy
- 新增 CQCommand 阻断策略的自定义配置功能
- 修复 CQAfter 无法正常使用的 bug #53
2021-12-07 13:21:29 +08:00
Jerry Ma
59c0d95e5d Create 2_Feature_request.yaml 2021-12-07 12:34:18 +08:00
Jerry Ma
9ceaecdc02 Update README.md 2021-12-07 12:17:47 +08:00
Jerry Ma
19d50898ef Create 1_Bug_report.yaml 2021-12-07 12:15:49 +08:00
Jerry Ma
5b62ca62ae Update README.md 2021-11-17 00:28:54 +08:00
fb528d30ce update docs 2021-11-16 16:51:05 +08:00
5f2d5ed334 update to build 428 2021-11-16 16:49:32 +08:00
c20e459900 update docs 2021-11-16 15:44:34 +08:00
09220825cf update to build 427 2021-11-16 15:41:01 +08:00
Jerry Ma
3d4db23d27 Update v2.md 2021-11-10 14:10:08 +08:00
Jerry Ma
4496b67dcc Update build-update.md 2021-11-10 14:09:18 +08:00
Jerry Ma
2a13298384 update to 2.5.8 (build 426) 2021-11-10 14:07:21 +08:00
Jerry Ma
d6ec404d76 Merge pull request #52 from YuFengZe/master
Update CQ.php
2021-11-10 14:06:13 +08:00
YuFengZe
3235fd4dc1 Update CQ.php 2021-11-08 22:05:41 +08:00
crazywhalecc
293740fee2 update to 2.5.7 (build 425) 2021-11-03 23:26:43 +08:00
crazywhalecc
d3c420ec84 update to build 424 (2.6.0-alpha1) 2021-11-02 16:01:24 +08:00
crazywhalecc
85ef09d43c Merge remote-tracking branch 'origin/master' 2021-11-01 00:29:11 +08:00
Jerry Ma
e4561d69c4 Merge pull request #45 from YuFengZe/master
Bug Fixed
2021-10-31 22:50:49 +08:00
Jerry Ma
6b4d206099 Update Hello.php 2021-10-31 22:50:12 +08:00
YuFengZe
50843edf6a Bug Fixed
解决发送“我是谁”却返回机器人信息的奇怪问题。
2021-10-31 22:01:14 +08:00
crazywhalecc
3a1686f8da update docs 2021-10-18 22:24:28 +08:00
crazywhalecc
66dd91bb97 update to 2.5.6 (build 423) 2021-10-17 23:55:02 +08:00
crazywhalecc
e020e5d593 update docs 2021-10-17 17:00:33 +08:00
crazywhalecc
3d62663281 update docs 2021-10-17 16:56:52 +08:00
crazywhalecc
8d9485c02e update docs 2021-10-17 15:37:55 +08:00
Jerry Ma
9fb45dd683 Update README.md 2021-10-15 09:38:07 +08:00
Jerry Ma
8a4924dba9 Update light-cache.md 2021-10-08 11:41:40 +08:00
crazywhalecc
beaf7be606 update to version 2.5.5 (buid 422) 2021-10-06 18:01:40 +08:00
Jerry Ma
7dbd21bdf4 Merge pull request #44 from furleywolf/patch-1
更新message-util的文档
2021-09-15 12:46:48 +08:00
furleywolf
9ce3056203 更新message-util的文档
发现漏掉了一个方法,还是补上去吧
2021-09-13 17:35:39 +08:00
crazywhalecc
880b4e847c update to version 2.5.4 (buid 421) 2021-09-11 16:27:16 +08:00
crazywhalecc
71e83d5bc8 update docs 2021-09-11 12:06:22 +08:00
89 changed files with 2399 additions and 511 deletions

View File

@@ -0,0 +1,38 @@
name: 🐛 漏洞BUG报告
description: ⚠️ 请不要直接在此提交安全漏洞
labels: bug
body:
- type: input
id: affected-versions
attributes:
label: 受影响版本
placeholder: x.y.z
validations:
required: true
- type: textarea
id: description
attributes:
label: 描述
description: 请详细地描述您的问题
validations:
required: true
- type: textarea
id: reproduce-steps
attributes:
label: 复现步骤
description: |
请尽可能地提供可以复现此步骤的漏洞。
如果步骤过长或难以描述,您可以自行建立一个用于复现漏洞的仓库。
validations:
required: true
- type: textarea
id: possible-solution
attributes:
label: 解决方案
description: 如果您对这个漏洞的成因或修复有任何意见的话,请在此提出
- type: textarea
id: additional-context
attributes:
label: 附加信息
description: 其他可能有帮助的信息,如日志、截图等

View File

@@ -0,0 +1,19 @@
name: 🚀 功能建议
description: 新功能、改进的意见、草案
labels: enhancement
body:
- type: textarea
id: description
attributes:
label: 描述
description: 请提供简洁清楚的描述
validations:
required: true
- type: textarea
id: example
attributes:
label: 例子
description: |
一个简单的例子,展示该功能将如何被使用(包括代码、配置文件等)
如果这是针对已有功能的改进,请展示改进前后使用方式(或效能)的对比

2
.gitignore vendored
View File

@@ -12,9 +12,11 @@ composer.lock
.daemon_pid
/runtime/
/tmp/
/temp/
/ext/go-cqhttp/data/
/ext/go-cqhttp/logs/
/ext/go-cqhttp/config.hjson
/ext/go-cqhttp/device.json
/ext/go-cqhttp/go-cqhttp
/ext/go-cqhttp/session.token
.zm_worker_*.pid

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Reload Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server:reload &amp;&amp; exit" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="./zhamao" />
<option name="SCRIPT_OPTIONS" value="server" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

17
.run/Run watcher.run.xml Normal file
View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Run watcher" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="bin/watcher" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="/bin/zsh" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Stop Zhamao Server" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="./zhamao server:stop &amp;&amp; exit" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
<option name="SCRIPT_OPTIONS" value="" />
<option name="INDEPENDENT_SCRIPT_WORKING_DIRECTORY" value="true" />
<option name="SCRIPT_WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="INDEPENDENT_INTERPRETER_PATH" value="true" />
<option name="INTERPRETER_PATH" value="" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="EXECUTE_IN_TERMINAL" value="true" />
<option name="EXECUTE_SCRIPT_FILE" value="false" />
<envs />
<method v="2" />
</configuration>
</component>

View File

@@ -98,7 +98,7 @@ vendor/bin/start server
## 关于
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。
作者的炸毛机器人已从2018年初起稳定运行了**年**,并且持续迭代。
作者的炸毛机器人已从2018年初起稳定运行了**年**,并且持续迭代。
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ[627577391](http://wpa.qq.com/msgrd?v=3&uin=627577391&site=qq&menu=yes))或提交 Issue 进行疑难解答。
@@ -110,4 +110,10 @@ vendor/bin/start server
在贡献代码时,请保管好自己的全局配置文件中的敏感信息,请勿将带有个人信息的配置文件上传 GitHub 等网站。
![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg)
感谢 JetBrains 为此开源项目提供 PhpStorm 开发工具支持:
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/PhpStorm.svg" width="300">
感谢 [php-libonebot](https://github.com/botuniverse/php-libonebot) 开发者 @sunxyw 中为项目开发规范化提出的一些建议。
<!-- ![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg) -->

0
bin/start Normal file → Executable file
View File

118
bin/watcher Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env bash
_red="\e[33m"
_green="\e[32m"
_reset="\e[0m"
unix_s=$(uname -s)
unix_release=$(
marked_release=""
if [ "$unix_s" = "Linux" ]; then
echo $HOME | grep com.termux >/dev/null
if [ $? == 0 ]; then
marked_release="termux"
elif [ -f "/etc/redhat-release" ]; then
if [ "$(cat /etc/redhat-release | awk '{print $1}' | grep -v '^$')" = "CentOS" ]; then
marked_release="CentOS"
else
marked_release="unknown"
fi
elif [ -f "/etc/os-release" ]; then
cat /etc/os-release | grep Alpine > /dev/null
if [ $? == 0 ]; then
marked_release="Alpine"
fi
fi
if [ "$marked_release" = "" ]; then
if [ -f "/etc/issue" ]; then
marked_release=$(cat /etc/issue | grep -v '^$' | awk '{print $1}')
else
marked_release="unknown"
fi
fi
elif [ "$unix_s" = "Darwin" ]; then
marked_release=$(sw_vers | grep ProductName | awk '{print $2" "$3" "$4}')
fi
echo $marked_release
)
unix_release=$(echo $unix_release | xargs)
function echo_error() {
echo -e "${_red}$1${_reset}"
}
function echo_info() {
echo -e "${_green}$1${_reset}"
}
function install_test() {
which fswatch >/dev/null
if [ $? != 0 ]; then
operate_confirm "fswatch还没有安装是否确认安装" && install_fswatch
fi
}
function install_fswatch() {
if [ "$unix_s" = "Linux" ]; then
case $unix_release in
"Kali" | "Ubuntu" | "Debian" | "Raspbian" | 'Pop!_OS')
sudo apt-get install fswatch -y ;;
#"termux") pkg install $1 -y ;;
"CentOS")
curl -o fswatch.tgz https://download.fastgit.org/emcrisostomo/fswatch/releases/download/1.16.0/fswatch-1.16.0.tar.gz && \
tar -xzf fswatch.tgz && \
cd fswatch-1.16.0 && \
./configure && \
make && \
sudo make install && \
cd .. && \
rm -rf fswatch.tgz fswatch-1.16.0
;;
#"Alpine") apk add $1 ;;
*)
echo_error "不支持的 Linux 发行版:$unix_release"
exit 1
;;
esac
elif [ "$unix_s" = "Darwin" ]; then
brew install fswatch
else
echo_error "不支持的操作系统:$unix_s"
exit 1
fi
}
function operate_confirm() {
echo -n $(echo_info "$1 [Y/n] ")
read operate
operate=$(echo $operate | tr A-Z a-z)
if [[ "$operate" = "y" || "$operate" = "" ]]; then
return 0
else
return 1
fi
}
echo_info "当前系统:$unix_release"
install_test
if [ $? -ne 0 ]; then
exit 1
else
echo_info "程序路径:$(which fswatch)"
fi
watch_dir="./src"
if [ ! -d "$watch_dir" ]; then
echo_error "src目录不存在"
exit 1
else
echo_info "监听目录:$watch_dir"
fi
_pid=$(cat .daemon_pid | awk -F"\"pid\": " '{print $2}' | grep -v ^$ | sed 's/,//g')
if [ "$_pid" = "" ]; then
echo_error "未检测到框架进程"
exit 1
fi
fswatch $watch_dir | while read file
do
echo "Detect file change: $file"
kill -USR1 $_pid
done

View File

@@ -17,7 +17,8 @@
"prefer-stable": true,
"bin": [
"bin/start",
"bin/phpunit-swoole"
"bin/phpunit-swoole",
"bin/watcher"
],
"require": {
"php": ">=7.2",
@@ -35,7 +36,7 @@
"jelix/version": "^2.0",
"league/climate": "^3.6",
"psy/psysh": "@stable",
"doctrine/orm": "^2.9"
"doctrine/dbal": "^2.13.1"
},
"suggest": {
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
@@ -52,6 +53,12 @@
"src/ZM/global_functions.php"
]
},
"autoload-dev": {
"psr-4": {
"Module\\": "src/Module",
"Custom\\": "src/Custom"
}
},
"require-dev": {
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^8.5 || ^9.0"

View File

@@ -39,7 +39,10 @@ $config['swoole'] = [
$config['runtime'] = [
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
'swoole_server_mode' => SWOOLE_PROCESS,
'middleware_error_policy' => 1
'middleware_error_policy' => 1,
'reload_delay_time' => 800,
'global_middleware_binding' => [],
'save_console_log_file' => false, // 改为目标路径,则将 Console 输出的日志保存到文件
];
/** 轻量字符串缓存,默认开启 */
@@ -57,21 +60,6 @@ $config['worker_cache'] = [
'transaction_timeout' => 30000
];
/** @deprecated 放弃使用,旧版数据库,请使用 mysql_config 和 doctrine/dbal 搭配使用 */
$config['sql_config'] = [
'sql_host' => '',
'sql_port' => 3306,
'sql_username' => 'name',
'sql_database' => 'db_name',
'sql_password' => '',
'sql_options' => [
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false
],
'sql_no_exception' => false,
'sql_default_fetch_mode' => PDO::FETCH_ASSOC //added in 1.5.6
];
/** MySQL数据库连接信息host留空则启动时不创建sql连接池 */
$config['mysql_config'] = [
'host' => '',
@@ -135,7 +123,9 @@ $config['static_file_server'] = [
$config['onebot'] = [
'status' => true,
'single_bot_mode' => false,
'message_level' => 99999
'message_level' => 99,
'message_convert_string' => true,
'message_command_policy' => 'interrupt',
];
/** 一个远程简易终端使用nc直接连接即可但是不建议开放host为0.0.0.0(远程连接) */

View File

@@ -42,6 +42,21 @@ 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 码。
@@ -113,3 +128,52 @@ public function onStart() {
( 其实还是很聪明的!
</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]"
```

View File

@@ -0,0 +1,9 @@
# 机器人 APIOneBotV12待发布
!!! tip "提示"
目前由于 OneBot 12 标准还没有定稿,处于草案阶段,故框架暂不更新。
在未来升级到 OneBot 12 标准后,框架会提供转换及兼容措施以及 12 版本的 API 方法。
见 [机器人动作OneBot 11](../robot-api)。

View File

@@ -769,6 +769,21 @@ vardump($result["retcode"]); //如果成功撤回,输出 int(0)
响应数据:无
### 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。

View File

@@ -124,7 +124,7 @@ public function ping() {
## getRobot() - 获取机器人 API 对象
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](bot/robot-api.md)。
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](../bot/robot-api.md)。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。

View File

@@ -0,0 +1,104 @@
# 事件跟踪器及调试
众所周知炸毛框架中的事件由内置的事件分发器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() - 启用事件跟踪器
还没写完,不着急。

View File

@@ -2,7 +2,7 @@
此类管理的是 TaskWorker 相关工作。有关使用 TaskWorker 的教程,见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)
类定义:`\ZM\Utils\TaskManager`
类定义:`\ZM\Utils\Manager\TaskManager`
使用 TaskWorker 需要先在 `global.php` 配置文件中开启!

View File

@@ -8,6 +8,10 @@ DataProvider 是框架内提供的一个简易的文件管理类。
`working_dir()`
## DataProvider::getSourceRootDir()
获取用户的源码根目录,除 Phar 模式外与 `getWorkingDir()` 相同。
## DataProvider::getFrameworkLink()
`ZMConfig::get("global", "http_reverse_link")`,获取反向代理的链接。
@@ -46,6 +50,45 @@ DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framewor
文件名同上 `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`)。但有一点要注意,框架内最好使用**工作目录或者绝对路径**。

View File

@@ -125,9 +125,9 @@ dump(LightCache::getExpire("test")); // 返回 10
```php
$s = LightCache::set("test", "hello", 20); //假设这条代码执行时时间戳是 1616838482
zm_sleep(10);
dump(LightCache::getExpire("test")); // 返回 1616838502
dump(LightCache::getExpireTS("test")); // 返回 1616838502
zm_sleep(10);
dump(LightCache::getExpire("test")); // 返回 null
dump(LightCache::getExpireTS("test")); // 返回 null
```
### LightCache::getMemoryUsage()

View File

@@ -0,0 +1,184 @@
# 执行 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)
```

View File

@@ -0,0 +1,7 @@
# 配置
炸毛框架的数据库组件支持原生 SQL、查询构造器去掉了复杂的对象模型关联同时默认为数据库连接池使开发变得简单。
数据库的配置位于 `config/global.php` 文件的 `mysql_config` 段,见 [全局配置](../../../../guide/basic-config#mysql_config)。
如果 `mysql_config.host` 字段为空,则不创建数据库连接池,填写后将创建,且默认保持长连接。

View File

@@ -0,0 +1 @@
你好啊,这里是 Statement。

View File

@@ -1,10 +1,14 @@
# MySQL 数据库
## 简介
# MySQL 数据库简介
炸毛框架的数据库组件对接了 MySQL 连接池,在使用过程中无需配置即可实现 MySQL 查询,同时拥有高并发。
目前 2.5 版本后炸毛框架底层采用了 `doctrine/dbal` 组件,可以方便地构建 SQL 语句。
> 文档正在加急编写!!!用的话可以先用 `MySQLManager::getWrapper()` 获取 wrapper 后返回的方法。
本章大体查询内容均以下表 `users` 为基础:
| id | username | gender | update_time |
| -- | -------- | ------ | ----------- |
| 1 | jack | man | 2021-10-12 |
| 2 | rose | woman | 2021-10-11 |
#

View File

@@ -2,6 +2,10 @@
前面讲到 LightCache 轻量缓存在特定的情况下为了保证数据不被多进程的因素导致丢失或覆盖,在高并发情况下修改数据需要加锁,所以炸毛框架内置了 SpinLock 自旋锁。
!!! tip "提示"
框架单进程运行的模式下不需要任何自旋锁。
## 配置
自旋锁使用无需配置,和 LightCache 同源。

View File

@@ -6,9 +6,18 @@
但是如果因为用户的误操作,导致炸毛框架其中的一个或多个进程阻塞,或者比如将框架挂在 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` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
**一、**首先,使用 `ps``htop``netstat -nlp` 等命令确定框架的入口进程(也就是 Master 进程的 pid

View File

@@ -23,7 +23,7 @@
| `runtime` | 一些框架运行时调整的设置 | 见子表 `runtime` |
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
| `mysql_config` | MySQL 数据库连接信息 | 见子表 `mysql_config` |
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
| `access_token` | OneBot 客户端连接约定的token留空则无相关设置见 [组件 - Access Token 验证](component/access-token) | 空 |
| `http_header` | HTTP 请求自定义返回的header | 见配置文件 |
@@ -55,7 +55,10 @@
| ----------------------------- | ------------------------------------------------------------ | --------------------------------------- |
| `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 |
| `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**
@@ -73,17 +76,18 @@
| -------- | --------------------------- | ------ |
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
### 子表 **sql_config**
### 子表 **mysql_config**
| 配置名称 | 说明 | 默认值 |
| ------------------------ | ------------------------------ | ------------------------------------------------------------ |
| `sql_host` | 数据库地址(留空则不使用数据库) | 空 |
| `sql_port` | 数据库端口 | 3306 |
| `sql_username` | 连接数据库的用户名 | |
| `sql_database` | 要连接的数据库名 | |
| `sql_password` | 数据库连接密码 | |
| `sql_options` | PDO 数据库的 options 参数 | `[PDO::ATTR_STRINGIFY_FETCHES => false,PDO::ATTR_EMULATE_PREPARES => false]` |
| `sql_default_fetch_mode` | PDO 的 fetch 模式 | `PDO::FETCH_ASSOC` |
| `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**
@@ -105,11 +109,13 @@
### 子表 onebot
| 配置名称 | 说明 | 默认值 |
| ----------------- | ------------------------------------------------------------ | ------ |
| `status` | 是否开启 OneBot 标准机器人解析功能 | true |
| `single_bot_mode` | 是否开启单机器人模式 | false |
| `message_level` | 机器人的 WebSocket 事件在 Swoole 原生事件 `@OnMessageEvent` 中的等级(越高说明越被优先处理) | 99999 |
| 配置名称 | 说明 | 默认值 |
| ------------------------ | ------------------------------------------------------------ | ------ |
| `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

View File

@@ -70,5 +70,6 @@
| E00068 | 模块解包时无法正常拷贝文件 | 检查文件夹是否正常可以创建和写入。 |
| E00069 | 框架不能启动两个 ConsoleApplication 实例 | 不要重复使用 `new ConsoleApplication()`。 |
| E00070 | 框架找不到 `zm_data` 目录 | 检查配置中指定的 `zm_data` 目录是否存在。 |
| E00071 | 框架找不到对应类型的 API 调用类 | 检查 `getExtendedAPI($name)` 传入的 `$name` 是否正确 |
| E99999 | 未知错误 | |

37
docs/guide/upgrade.md Normal file
View File

@@ -0,0 +1,37 @@
# 升级指南
因为框架在随着需求以及 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` 文件进行对比。

View File

@@ -1,7 +1,5 @@
# 介绍
> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。
!!! tip "提示"
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档

View File

@@ -4,6 +4,89 @@
同时此处将只使用 build 版本号进行区分。
## build 434 (2022-1-8)
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## build 433 (2021-12-28)
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
## build 432 (2021-12-25)
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot ActionAPI
- 修复 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

View File

@@ -2,6 +2,19 @@
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
## 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']` 运行时设置。

View File

@@ -1,8 +1,104 @@
# 更新日志v2 版本)
## v2.6.6build 434
> 更新时间2022.1.8
- 修复框架在 PHP 8.1 下运行时的一些问题
- 新增 Console 日志输出到文件的功能
## v2.6.5build 433
> 更新时间2021.12.28
- 修复 OneBotV11 因 IDE 自动优化导致 API 接口发生变化的问题
## v2.6.4build 432
> 更新时间2021.12.25
- 新增 GoCqhttpAPI 包,用于支持额外的 OneBot ActionAPI
- 修复 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
> 更新时间2021.9.11
- 修复 DB 模块在未连接数据库的时候抛出未知异常
- 修复部分情况下打包模块出现的错误
@@ -415,4 +511,4 @@
> 更新时间2020.12.23
已发布正式版。
已发布正式版。

2
install-runtime.sh Normal file → Executable file
View File

@@ -55,4 +55,4 @@ if [ $? -ne 0 ]; then
fi
echo "成功下载!" && \
echo -e "PHP使用\truntime/php -v" && \
echo -e "Composer使用\truntime/composer"
echo -e "Composer使用\truntime/composer"

25
instant-test.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
require_once "vendor/autoload.php";
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Module\InstantModule;
use ZM\ZMServer;
$weather = new InstantModule("weather");
$weather->onEvent(OnOpenEvent::class, ['connect_type' => 'qq'], function(ConnectionObject $conn) {
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
});
$weather->onEvent(CQCommand::class, ['match' => '你好'], function() {
ctx()->reply("hello呀");
});
$app = new ZMServer("app-name");
$app->addModule($weather);
$app->run();

View File

@@ -14,6 +14,7 @@ theme:
accent: indigo
features:
- navigation.tabs
- navigation.sections
extra_javascript:
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/highlight.min.js
- javascripts/config.js
@@ -66,6 +67,7 @@ nav:
- 基本配置: guide/basic-config.md
- 编写模块: guide/write-module.md
- 注册事件响应: guide/register-event.md
- 升级指南: guide/upgrade.md
- 错误码对照表: guide/errcode.md
- 事件和注解:
- 事件和注解: event/index.md
@@ -77,48 +79,55 @@ nav:
- 事件分发器: event/event-dispatcher.md
- 框架组件:
- 框架组件: component/index.md
- 上下文: component/context.md
- 聊天机器人组件:
- 机器人 API: component/bot/robot-api.md
- 机器人动作V12: component/bot/robot-api-12.md
- 机器人动作V11: component/bot/robot-api.md
- CQ 码(多媒体消息): component/bot/cqcode.md
- 机器人消息处理: component/bot/message-util.md
- Token 验证: component/bot/access-token.md
- 图灵机器人 API: component/bot/turing-api.md
- 存储:
- LightCache 轻量缓存: component/store/light-cache.md
- MySQL 查询器: component/store/mysql.md
- MySQL 查询器:
- 简介: component/store/mysql/mysql.md
- 配置: component/store/mysql/config.md
- 执行 SQL 语句: component/store/mysql/common-query.md
- MySQL 查询器(废弃): component/store/mysql-db.md
- Redis 数据库: component/store/redis.md
- ZMAtomic 原子计数器: component/store/atomics.md
- SpinLock 自旋锁: component/store/spin-lock.md
- 文件管理: component/store/data-provider.md
- 通用组件:
- 上下文: component/common/context.md
- 协程池: component/common/coroutine-pool.md
- 单例类: component/common/singleton-trait.md
- ZMUtil 杂项: component/common/zmutil.md
- 全局方法: component/common/global-functions.md
- Console 终端: component/common/console.md
- TaskWorker 管理: component/common/task-worker.md
- Terminal 终端: component/common/remote-terminal.md
- EventTracer 事件追踪器: component/comon/event-tracer.md
- HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- HTTP 路由管理: component/route-manager.md
- HTTP 和 WebSocket 客户端: component/http/zmrequest.md
- HTTP 路由管理: component/http/route-manager.md
- 模块/插件管理:
- 模块打包: component/module/module-pack.md
- 模块解包: component/module/module-unpack.md
- 协程池: component/coroutine-pool.md
- 单例类: component/singleton-trait.md
- ZMUtil 杂项: component/zmutil.md
- 全局方法: component/global-functions.md
- Console 终端: component/console.md
- TaskWorker 管理: component/task-worker.md
- Terminal 终端: component/remote-terminal.md
- 进阶开发:
- 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 从 v1 升级: advanced/to-v2.md
- 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 框架多进程: advanced/multi-process.md
- TaskWorker 提高并发: advanced/task-worker.md
- 框架高级开发:
- 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 内部类文件手册: advanced/inside-class.md
- 框架多进程: advanced/multi-process.md
- TaskWorker 提高并发: advanced/task-worker.md
- 开发实战教程:
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 编写管理员才能触发的功能: advanced/example/admin.md
- FAQ:
- FAQ: faq/FAQ.md
- 从 v1 升级: faq/to-v2.md
- 框架常见问题(持续更新): faq/usual-question.md
- 启动时报错 Address already in use: faq/address-already-in-use.md
- 出现 deadlock 字样: faq/display-deadlock.md

View File

@@ -10,6 +10,7 @@ use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\API\CQ;
use ZM\API\TuringAPI;
use ZM\API\OneBotV11;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Annotation\CQ\CQCommand;
@@ -49,8 +50,12 @@ class Hello
* @CQCommand("我是谁")
*/
public function whoami() {
$user = ctx()->getRobot()->getLoginInfo();
return "你是" . $user["data"]["nickname"] . "QQ号是" . $user["data"]["user_id"];
$bot = ctx()->getRobot()->getLoginInfo();
$bot_id = $bot["data"]["user_id"];
$r = OneBotV11::get($bot_id);
$QQid = ctx()->getUserId();
$nick = $r->getStrangerInfo($QQid)["data"]["nickname"];
return "你是" . $nick . "QQ号是" . $QQid;
}
/**

View File

@@ -33,7 +33,7 @@ class TimerMiddleware implements MiddlewareInterface
* @HandleAfter()
*/
public function onAfter() {
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 3) . " ms.");
}
/**
@@ -42,7 +42,7 @@ class TimerMiddleware implements MiddlewareInterface
* @throws Exception
*/
public function onException(Exception $e) {
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 3) . " ms but an Exception occurred.");
throw $e;
}
}

View File

@@ -356,7 +356,7 @@ class CQ
}
$cq["start"] = $offset + $head;
$cq["end"] = $offset + $tmpmsg + $head;
$offset += $tmpmsg + 1;
$offset += $head + $tmpmsg + 1;
$cqs[] = (!$is_object ? $cq : CQObject::fromArray($cq));
}
return $cqs;

View File

@@ -3,6 +3,7 @@
namespace ZM\API;
use Closure;
use ZM\Console\Console;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
@@ -11,6 +12,13 @@ use ZM\Utils\CoMessage;
trait CQAPI
{
/** @var null|Closure */
private static $filter = null;
public static function registerFilter(callable $callable) {
self::$filter = $callable;
}
/**
* @param $connection
* @param $reply
@@ -18,6 +26,11 @@ trait CQAPI
* @return bool|array
*/
private function processAPI($connection, $reply, $function = null) {
if (is_callable(self::$filter)) {
$reply2 = call_user_func(self::$filter, $reply);
if (is_bool($reply2)) return $reply2;
else $reply = $reply2;
}
if ($connection->getOption("type") === CONN_WEBSOCKET)
return $this->processWebsocketAPI($connection, $reply, $function);
else
@@ -67,6 +80,12 @@ trait CQAPI
return false;
}
public function getActionName($suffix, string $method) {
$postfix = ($suffix == OneBotV11::API_ASYNC ? '_async' : ($suffix == OneBotV11::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $func_name . $postfix;
}
public function __call($name, $arguments) {
return false;
}

View File

@@ -0,0 +1,111 @@
<?php
namespace ZM\API;
class GoCqhttpAPIV11
{
const SUPPORT_VERSION = '1.0.0-beta8';
use CQAPI;
private $connection;
private $callback;
private $prefix;
public function __construct($connection, $callback, $prefix)
{
$this->connection = $connection;
$this->callback = $callback;
$this->prefix = $prefix;
}
/**
* 获取频道系统内BOT的资料
* 响应字段nickname, tiny_id, avatar_url
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E7%B3%BB%E7%BB%9F%E5%86%85bot%E7%9A%84%E8%B5%84%E6%96%99
* @return array|bool
*/
public function getGuildServiceProfile()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
/**
* 获取频道列表
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8
* @return array|bool
*/
public function getGuildList() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
/**
* 通过访客获取频道元数据
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E9%80%9A%E8%BF%87%E8%AE%BF%E5%AE%A2%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E5%85%83%E6%95%B0%E6%8D%AE
* @param $guild_id
* @return array|bool
*/
public function getGuildMetaByGuest($guild_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'guild_id' => $guild_id
]
], $this->callback);
}
/**
* 获取子频道列表
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E5%AD%90%E9%A2%91%E9%81%93%E5%88%97%E8%A1%A8
* @param $guild_id
* @param false $no_cache
* @return array|bool
*/
public function getGuildChannelList($guild_id, bool $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'guild_id' => $guild_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取频道成员列表
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E8%8E%B7%E5%8F%96%E9%A2%91%E9%81%93%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
* @param $guild_id
* @return array|bool
*/
public function getGuildMembers($guild_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'guild_id' => $guild_id
]
], $this->callback);
}
/**
* 发送信息到子频道
* @link https://github.com/Mrs4s/go-cqhttp/blob/master/docs/guild.md#%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E5%88%B0%E5%AD%90%E9%A2%91%E9%81%93
* @param $guild_id
* @param $channel_id
* @param $message
* @return array|bool
*/
public function sendGuildChannelMsg($guild_id, $channel_id, $message) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'guild_id' => $guild_id,
'channel_id' => $channel_id,
'message' => $message
]
], $this->callback);
}
}

View File

@@ -1,4 +1,4 @@
<?php
<?php /** @noinspection PhpUnused */
namespace ZM\API;
@@ -6,6 +6,7 @@ namespace ZM\API;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Exception\RobotNotFoundException;
use ZM\Exception\ZMKnownException;
/**
* OneBot V11 的 API 实现类
@@ -22,17 +23,18 @@ class OneBotV11
const API_RATE_LIMITED = 2;
/** @var ConnectionObject|null */
private $connection;
protected $connection;
private $callback = true;
private $prefix = 0;
protected $callback = true;
protected $prefix = 0;
/**
* @param $robot_id
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function get($robot_id) {
public static function get($robot_id)
{
$r = ManagerGM::getAllByName('qq');
foreach ($r as $v) {
if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v);
@@ -44,7 +46,8 @@ class OneBotV11
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function getRandom() {
public static function getRandom()
{
$r = ManagerGM::getAllByName('qq');
if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
return new ZMRobot($r[array_rand($r)]);
@@ -53,7 +56,8 @@ class OneBotV11
/**
* @return ZMRobot[]
*/
public static function getAllRobot() {
public static function getAllRobot()
{
$r = ManagerGM::getAllByName('qq');
$obj = [];
foreach ($r as $v) {
@@ -62,21 +66,25 @@ class OneBotV11
return $obj;
}
public function __construct(ConnectionObject $connection) {
public function __construct(ConnectionObject $connection)
{
$this->connection = $connection;
}
public function setCallback($callback = true) {
public function setCallback($callback = true)
{
$this->callback = $callback;
return $this;
}
public function setPrefix($prefix = self::API_NORMAL) {
public function setPrefix($prefix = self::API_NORMAL)
{
$this->prefix = $prefix;
return $this;
}
public function getSelfId() {
public function getSelfId()
{
return $this->connection->getOption('connect_id');
}
@@ -90,9 +98,10 @@ class OneBotV11
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendPrivateMsg($user_id, $message, $auto_escape = false) {
public function sendPrivateMsg($user_id, $message, bool $auto_escape = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'user_id' => $user_id,
'message' => $message,
@@ -109,9 +118,10 @@ class OneBotV11
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendGroupMsg($group_id, $message, $auto_escape = false) {
public function sendGroupMsg($group_id, $message, bool $auto_escape = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'message' => $message,
@@ -129,9 +139,10 @@ class OneBotV11
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
public function sendMsg($message_type, $target_id, $message, bool $auto_escape = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'message_type' => $message_type,
($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id,
@@ -147,9 +158,10 @@ class OneBotV11
* @param $message_id
* @return array|bool|null
*/
public function deleteMsg($message_id) {
public function deleteMsg($message_id)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'message_id' => $message_id
]
@@ -162,9 +174,10 @@ class OneBotV11
* @param $message_id
* @return array|bool|null
*/
public function getMsg($message_id) {
public function getMsg($message_id)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'message_id' => $message_id
]
@@ -177,9 +190,10 @@ class OneBotV11
* @param $id
* @return array|bool|null
*/
public function getForwardMsg($id) {
public function getForwardMsg($id)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'id' => $id
]
@@ -193,9 +207,10 @@ class OneBotV11
* @param int $times
* @return array|bool|null
*/
public function sendLike($user_id, $times = 1) {
public function sendLike($user_id, int $times = 1)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'user_id' => $user_id,
'times' => $times
@@ -211,9 +226,10 @@ class OneBotV11
* @param bool $reject_add_request
* @return array|bool|null
*/
public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
public function setGroupKick($group_id, $user_id, bool $reject_add_request = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -227,12 +243,13 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
* @param $group_id
* @param $user_id
* @param $duration
* @param int $duration
* @return array|bool|null
*/
public function setGroupBan($group_id, $user_id, $duration = 1800) {
public function setGroupBan($group_id, $user_id, int $duration = 1800)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -249,9 +266,10 @@ class OneBotV11
* @param int $duration
* @return array|bool|null
*/
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, int $duration = 1800)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
(is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag,
@@ -267,9 +285,10 @@ class OneBotV11
* @param bool $enable
* @return array|bool|null
*/
public function setGroupWholeBan($group_id, $enable = true) {
public function setGroupWholeBan($group_id, bool $enable = true)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
@@ -285,9 +304,10 @@ class OneBotV11
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAdmin($group_id, $user_id, $enable = true) {
public function setGroupAdmin($group_id, $user_id, bool $enable = true)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -303,9 +323,10 @@ class OneBotV11
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAnonymous($group_id, $enable = true) {
public function setGroupAnonymous($group_id, bool $enable = true)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
@@ -321,9 +342,10 @@ class OneBotV11
* @param string $card
* @return array|bool|null
*/
public function setGroupCard($group_id, $user_id, $card = "") {
public function setGroupCard($group_id, $user_id, string $card = '')
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -339,9 +361,10 @@ class OneBotV11
* @param $group_name
* @return array|bool|null
*/
public function setGroupName($group_id, $group_name) {
public function setGroupName($group_id, $group_name)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'group_name' => $group_name
@@ -356,9 +379,10 @@ class OneBotV11
* @param bool $is_dismiss
* @return array|bool|null
*/
public function setGroupLeave($group_id, $is_dismiss = false) {
public function setGroupLeave($group_id, bool $is_dismiss = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'is_dismiss' => $is_dismiss
@@ -375,9 +399,10 @@ class OneBotV11
* @param int $duration
* @return array|bool|null
*/
public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
public function setGroupSpecialTitle($group_id, $user_id, string $special_title = "", int $duration = -1)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -395,9 +420,10 @@ class OneBotV11
* @param string $remark
* @return array|bool|null
*/
public function setFriendAddRequest($flag, $approve = true, $remark = "") {
public function setFriendAddRequest($flag, bool $approve = true, string $remark = "")
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'flag' => $flag,
'approve' => $approve,
@@ -415,9 +441,10 @@ class OneBotV11
* @param string $reason
* @return array|bool|null
*/
public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
public function setGroupAddRequest($flag, $sub_type, bool $approve = true, string $reason = "")
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'flag' => $flag,
'sub_type' => $sub_type,
@@ -432,8 +459,9 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getLoginInfo() {
return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
public function getLoginInfo()
{
return $this->processAPI($this->connection, ['action' => $this->getActionName($this->prefix, __FUNCTION__)], $this->callback);
}
/**
@@ -443,9 +471,10 @@ class OneBotV11
* @param bool $no_cache
* @return array|bool|null
*/
public function getStrangerInfo($user_id, $no_cache = false) {
public function getStrangerInfo($user_id, bool $no_cache = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'user_id' => $user_id,
'no_cache' => $no_cache
@@ -458,9 +487,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getFriendList() {
public function getFriendList()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -471,9 +501,10 @@ class OneBotV11
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupInfo($group_id, $no_cache = false) {
public function getGroupInfo($group_id, bool $no_cache = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'no_cache' => $no_cache
@@ -486,9 +517,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getGroupList() {
public function getGroupList()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -500,9 +532,10 @@ class OneBotV11
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
public function getGroupMemberInfo($group_id, $user_id, bool $no_cache = false)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
@@ -517,9 +550,10 @@ class OneBotV11
* @param $group_id
* @return array|bool|null
*/
public function getGroupMemberList($group_id) {
public function getGroupMemberList($group_id)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id
]
@@ -533,9 +567,10 @@ class OneBotV11
* @param $type
* @return array|bool|null
*/
public function getGroupHonorInfo($group_id, $type) {
public function getGroupHonorInfo($group_id, $type)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'group_id' => $group_id,
'type' => $type
@@ -548,9 +583,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token
* @return array|bool|null
*/
public function getCsrfToken() {
public function getCsrfToken()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -560,9 +596,10 @@ class OneBotV11
* @param string $domain
* @return array|bool|null
*/
public function getCredentials($domain = "") {
public function getCredentials(string $domain = "")
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'domain' => $domain
]
@@ -576,9 +613,10 @@ class OneBotV11
* @param $out_format
* @return array|bool|null
*/
public function getRecord($file, $out_format) {
public function getRecord($file, $out_format)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'file' => $file,
'out_format' => $out_format
@@ -592,9 +630,10 @@ class OneBotV11
* @param $file
* @return array|bool|null
*/
public function getImage($file) {
public function getImage($file)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'file' => $file
]
@@ -606,9 +645,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
* @return array|bool|null
*/
public function canSendImage() {
public function canSendImage()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -617,9 +657,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
* @return array|bool|null
*/
public function canSendRecord() {
public function canSendRecord()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -628,9 +669,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81
* @return array|bool|null
*/
public function getStatus() {
public function getStatus()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -639,9 +681,10 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getVersionInfo() {
public function getVersionInfo()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
@@ -651,9 +694,10 @@ class OneBotV11
* @param int $delay
* @return array|bool|null
*/
public function setRestart($delay = 0) {
public function setRestart(int $delay = 0)
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'action' => $this->getActionName($this->prefix, __FUNCTION__),
'params' => [
'delay' => $delay
]
@@ -665,22 +709,37 @@ class OneBotV11
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98
* @return array|bool|null
*/
public function cleanCache() {
public function cleanCache()
{
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
'action' => $this->getActionName($this->prefix, __FUNCTION__)
], $this->callback);
}
public function callExtendedAPI($action, $params = []) {
/**
* 获取内置支持的扩展API对象
* 现支持 go-cqhttp 的扩展API
* @param string $package_name
* @return mixed
* @throws ZMKnownException
*/
public function getExtendedAPI(string $package_name = 'go-cqhttp')
{
$table = [
'go-cqhttp' => GoCqhttpAPIV11::class,
];
if (isset($table[$package_name])) {
return new $table[$package_name]($this->connection, $this->callback, $this->prefix);
} else {
throw new ZMKnownException(zm_internal_errcode('E00071'), '无法找到对应的调用扩展类');
}
}
public function callExtendedAPI($action, $params = [])
{
return $this->processAPI($this->connection, [
'action' => $action,
'params' => $params
], $this->callback);
}
private function getActionName(string $method) {
$prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $func_name . $prefix;
}
}

View File

@@ -4,6 +4,7 @@
namespace ZM\Annotation;
use Doctrine\Common\Annotations\AnnotationReader;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Interfaces\ErgodicAnnotation;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
@@ -98,7 +99,6 @@ class AnnotationParser
if ($vs instanceof ErgodicAnnotation) {
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
$copy = clone $vs;
/** @noinspection PhpUndefinedFieldInspection */
$copy->method = $method->getName();
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
}
@@ -116,12 +116,27 @@ class AnnotationParser
}
}
$inserted = [];
//预处理3处理每个函数上面的特殊注解就是需要操作一些东西的
foreach (($this->annotation_map[$v]["methods_annotations"] ?? []) as $method_name => $methods_annotations) {
foreach ($methods_annotations as $method_anno) {
/** @var AnnotationBase $method_anno */
$method_anno->class = $v;
$method_anno->method = $method_name;
if (!($method_anno instanceof Middleware) && ($middlewares = ZMConfig::get("global", "runtime")["global_middleware_binding"][get_class($method_anno)] ?? []) !== []) {
if (!isset($inserted[$v][$method_name])) {
// 在这里在其他中间件前插入插入全局的中间件
foreach ($middlewares as $middleware) {
$mid_class = new Middleware();
$mid_class->middleware = $middleware;
$mid_class->class = $v;
$mid_class->method = $method_name;
$this->middleware_map[$v][$method_name][] = $mid_class;
}
$inserted[$v][$method_name] = true;
}
}
if ($method_anno instanceof RequestMapping) {
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
} elseif ($method_anno instanceof Middleware) {
@@ -134,9 +149,10 @@ class AnnotationParser
Console::debug("解析注解完毕!");
}
public function generateAnnotationEvents() {
public function generateAnnotationEvents(): array {
$o = [];
foreach ($this->annotation_map as $obj) {
// 这里的ErgodicAnnotation是为了解决类上的注解可穿透到方法上的问题
foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
if ($class_annotation instanceof ErgodicAnnotation) continue;
else $o[get_class($class_annotation)][] = $class_annotation;

View File

@@ -7,22 +7,17 @@ namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
abstract class DaemonCommand extends Command
{
protected $daemon_file = null;
protected function execute(InputInterface $input, OutputInterface $output): int {
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (!file_exists($pid_path)) {
$output->writeln("<comment>没有检测到正在运行的守护进程或框架进程!</comment>");
die();
}
$file = json_decode(file_get_contents($pid_path), true);
if ($file === null || posix_getsid(intval($file["pid"])) === false) {
$file = Framework::getProcessState(ZM_PROCESS_MASTER);
if ($file === false || posix_getsid(intval($file["pid"])) === false) {
$output->writeln("<comment>未检测到正在运行的守护进程或框架进程!</comment>");
unlink($pid_path);
Framework::removeProcessState(ZM_PROCESS_MASTER);
die();
}
$this->daemon_file = $file;

View File

@@ -12,7 +12,7 @@ class DaemonReloadCommand extends DaemonCommand
protected static $defaultName = 'daemon:reload';
protected function configure() {
$this->setDescription("重载守护进程下的用户代码(仅限--daemon模式可用");
$this->setDescription("重载框架");
}
protected function execute(InputInterface $input, OutputInterface $output): int {

View File

@@ -11,18 +11,20 @@ class DaemonStatusCommand extends DaemonCommand
protected static $defaultName = 'daemon:status';
protected function configure() {
$this->setDescription("查看守护进程框架的运行状态(仅限--daemon模式可用");
$this->setDescription("查看框架的运行状态");
}
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
$output->writeln("<info>框架运行中pid" . $this->daemon_file["pid"] . "</info>");
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
$stdout = file_get_contents($this->daemon_file["stdout"]);
$stdout = explode("\n", $stdout);
for ($i = 15; $i > 0; --$i) {
if (isset($stdout[count($stdout) - $i]))
echo $stdout[count($stdout) - $i] . PHP_EOL;
$output->writeln("<info>框架" . ($this->daemon_file["daemon"] ? "以守护进程模式" : "") . "运行中pid" . $this->daemon_file["pid"] . "</info>");
if ($this->daemon_file["daemon"]) {
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
$stdout = file_get_contents($this->daemon_file["stdout"]);
$stdout = explode("\n", $stdout);
for ($i = 15; $i > 0; --$i) {
if (isset($stdout[count($stdout) - $i]))
echo $stdout[count($stdout) - $i] . PHP_EOL;
}
}
return 0;
}

View File

@@ -6,21 +6,21 @@ namespace ZM\Command\Daemon;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
use ZM\Framework;
class DaemonStopCommand extends DaemonCommand
{
protected static $defaultName = 'daemon:stop';
protected function configure() {
$this->setDescription("停止守护进程下运行的框架(仅限--daemon模式可用");
$this->setDescription("停止运行的框架");
}
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
$i = 10;
while (file_exists(DataProvider::getWorkingDir() . "/.daemon_pid") && $i > 0) {
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
sleep(1);
--$i;
}

View File

@@ -8,12 +8,14 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Framework;
use ZM\Utils\DataProvider;
class RunServerCommand extends Command
{
protected static $defaultName = 'server';
protected function configure() {
$this->setAliases(['server:start']);
$this->setDefinition([
new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"),
new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"),
@@ -34,7 +36,8 @@ class RunServerCommand extends Command
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
new InputOption("disable-safe-exit", null, null, "关闭安全退出关闭后按CtrlC时直接杀死进程"),
new InputOption("preview", null, null, "只显示参数,不启动服务器"),
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)")
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)"),
new InputOption("polling-watch", null, null, "强制启用轮询模式监听"),
]);
$this->setDescription("Run zhamao-framework | 启动框架");
$this->setHelp("直接运行可以启动");
@@ -47,7 +50,19 @@ class RunServerCommand extends Command
return 1;
}
}
$state = Framework::getProcessState(ZM_PROCESS_MASTER);
if (is_array($state) && posix_getsid($state['pid'] ?? -1) !== false) {
$output->writeln("<error>检测到已经在 pid: {$state['pid']} 进程启动了框架!</error>");
$output->writeln("<error>不可以同时启动两个框架!</error>");
return 1;
}
(new Framework($input->getOptions()))->start();
return 0;
}
public static function exportDefinition() {
$cmd = new self();
$cmd->configure();
return $cmd->getDefinition();
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ZM\Command\Server;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\Daemon\DaemonCommand;
class ServerReloadCommand extends DaemonCommand
{
protected static $defaultName = 'server:reload';
protected function configure() {
$this->setDescription("重载框架");
}
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
Process::kill(intval($this->daemon_file["pid"]), SIGUSR1);
$output->writeln("<info>成功重载!</info>");
return 0;
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace ZM\Command\Server;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\Daemon\DaemonCommand;
class ServerStatusCommand extends DaemonCommand
{
protected static $defaultName = 'server:status';
protected function configure() {
$this->setDescription("查看框架的运行状态");
}
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
$output->writeln("<info>框架" . ($this->daemon_file["daemon"] ? "以守护进程模式" : "") . "运行中pid" . $this->daemon_file["pid"] . "</info>");
if ($this->daemon_file["daemon"]) {
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
$stdout = file_get_contents($this->daemon_file["stdout"]);
$stdout = explode("\n", $stdout);
for ($i = 15; $i > 0; --$i) {
if (isset($stdout[count($stdout) - $i]))
echo $stdout[count($stdout) - $i] . PHP_EOL;
}
}
return 0;
}
}
{
}

View File

@@ -0,0 +1,57 @@
<?php
namespace ZM\Command\Server;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\Daemon\DaemonCommand;
use ZM\Framework;
use ZM\Utils\DataProvider;
class ServerStopCommand extends DaemonCommand
{
protected static $defaultName = 'server:stop';
protected function configure() {
$this->setDescription("停止运行的框架");
$this->setDefinition([
new InputOption('force', 'f', InputOption::VALUE_NONE, '强制停止'),
]);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
if ($input->getOption('force') !== false) {
$file_path = _zm_pid_dir();
$list = DataProvider::scanDirFiles($file_path, false, true);
foreach($list as $file) {
$name = explode('.', $file);
if (end($name) == 'pid') {
$pid = file_get_contents($file_path.'/'.$file);
Process::kill($pid, SIGKILL);
} elseif ($file === 'master.json') {
$json = json_decode(file_get_contents($file_path.'/'.$file), true);
Process::kill($json['pid'], SIGKILL);
}
unlink($file_path.'/'.$file);
}
} else {
parent::execute($input, $output);
}
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
$i = 10;
while (Framework::getProcessState(ZM_PROCESS_MASTER) !== false && $i > 0) {
sleep(1);
--$i;
}
if ($i === 0) {
$output->writeln("<error>停止失败请检查进程pid #" . $this->daemon_file["pid"] . " 是否响应!</error>");
$output->writeln("<error>或者可以尝试使用参数 --force 来强行杀死所有进程</error>");
} else {
$output->writeln("<info>成功停止!</info>");
}
return 0;
}
}

View File

@@ -21,15 +21,17 @@ use ZM\Command\RunServerCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Console\Console;
use ZM\Command\Server\ServerReloadCommand;
use ZM\Command\Server\ServerStatusCommand;
use ZM\Command\Server\ServerStopCommand;
use ZM\Exception\InitException;
class ConsoleApplication extends Application
{
private static $obj = null;
const VERSION_ID = 420;
const VERSION = "2.5.3";
const VERSION_ID = 435;
const VERSION = "2.7.0-beta1";
/**
* @throws InitException
@@ -47,11 +49,25 @@ class ConsoleApplication extends Application
* @return ConsoleApplication
* @throws InitException
*/
public function initEnv($with_default_cmd = ""): ConsoleApplication {
if (defined("WORKDING_DIR")) throw new InitException();
$this->selfCheck();
public function initEnv(string $with_default_cmd = ""): ConsoleApplication {
if (defined("WORKING_DIR")) throw new InitException();
_zm_env_check();
// 定义多进程的全局变量
define('ZM_PROCESS_MASTER', 1);
define('ZM_PROCESS_MANAGER', 2);
define('ZM_PROCESS_WORKER', 4);
define('ZM_PROCESS_USER', 8);
define('ZM_PROCESS_TASKWORKER', 16);
define("WORKING_DIR", getcwd());
if (!is_dir(_zm_pid_dir())) {
@mkdir(_zm_pid_dir());
}
if (Phar::running() !== "") {
echo "* Running in phar mode.\n";
define("SOURCE_ROOT_DIR", Phar::running());
@@ -62,34 +78,15 @@ class ConsoleApplication extends Application
define("LOAD_MODE", is_dir(SOURCE_ROOT_DIR . "/src/ZM") ? 0 : 1);
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../../"));
}
if (LOAD_MODE == 0) {
$composer = json_decode(file_get_contents(SOURCE_ROOT_DIR . "/composer.json"), true);
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] ";
$r = strtolower(trim(fgets(STDIN)));
if ($r === "" || $r === "y") {
$composer["autoload"]["psr-4"]["Module\\"] = "src/Module";
$composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom";
$r = file_put_contents(WORKING_DIR . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
if ($r !== false) {
echo "成功添加!请运行 composer dump-autoload \n";
exit(0);
} else {
echo zm_internal_errcode("E00006") . "添加失败!请按任意键继续!";
fgets(STDIN);
exit(1);
}
} else {
exit(1);
}
}
}
$this->addCommands([
new DaemonStatusCommand(),
new DaemonReloadCommand(),
new DaemonStopCommand(),
new RunServerCommand(), //运行主服务的指令控制器
new ServerStatusCommand(),
new ServerStopCommand(),
new ServerReloadCommand(),
new PureHttpCommand(), //纯HTTP服务器指令
new SystemdGenerateCommand()
]);
@@ -121,17 +118,4 @@ class ConsoleApplication extends Application
die(zm_internal_errcode("E00005") . "{$e->getMessage()} at {$e->getFile()}({$e->getLine()})");
}
}
/**
* 启动炸毛前要做的环境检查项目
*/
private function selfCheck(): void {
if (!extension_loaded("swoole")) die(zm_internal_errcode("E00001") . "Can not find swoole extension.\n");
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die(zm_internal_errcode("E00002") . "You must install swoole version >= 4.5.0 !");
if (version_compare(PHP_VERSION, "7.2") == -1) die(zm_internal_errcode("E00003") . "PHP >= 7.2 required.");
if (version_compare(SWOOLE_VERSION, "4.6.7") < 0 && !extension_loaded("pcntl")) {
Console::error(zm_internal_errcode("E00004") . "Swoole 版本必须不低于 4.6.7 或 PHP 安装加载了 pcntl 扩展!");
die();
}
}
}

View File

@@ -9,6 +9,7 @@ use Swoole\Coroutine;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
@@ -19,6 +20,7 @@ use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
use ZM\API\ZMRobot;
use ZM\Utils\CoMessage;
use ZM\Utils\MessageUtil;
class Context implements ContextInterface
{
@@ -56,8 +58,8 @@ class Context implements ContextInterface
*/
public function getResponse(): ?Response { return self::$context[$this->cid]["response"] ?? null; }
/** @return ConnectionObject|null|Response */
public function getConnection() { return ManagerGM::get($this->getFd()); }
/** @return ConnectionObject|null */
public function getConnection(): ?ConnectionObject { return ManagerGM::get($this->getFd()); }
/**
* @return int|null
@@ -72,9 +74,20 @@ class Context implements ContextInterface
return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
}
public function getMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
public function getMessage() {
if ((ZMConfig::get("global", "onebot")["message_convert_string"] ?? true) === true && is_array($msg = $this->getOriginMessage())) {
return MessageUtil::arrayToStr($msg);
} else {
return self::$context[$this->cid]["data"]["message"] ?? null;
}
}
public function setMessage($msg) { self::$context[$this->cid]["data"]["message"] = $msg; }
public function setMessage($msg) {
if (is_string($msg) && is_array($this->getOriginMessage())) {
$msg = MessageUtil::strToArray($msg);
}
self::$context[$this->cid]["data"]["message"] = $msg;
}
public function getUserId() { return $this->getData()["user_id"] ?? null; }
@@ -168,7 +181,11 @@ class Context implements ContextInterface
if ($r === false) {
throw new WaitTimeoutException($this, $timeout_prompt);
}
return $r["message"];
if (is_array($r["message"]) && (ZMConfig::get("global", "onebot")["message_convert_string"] ?? true) === true) {
return MessageUtil::arrayToStr($r["message"]);
} else {
return $r["message"];
}
}
/**
@@ -239,4 +256,18 @@ class Context implements ContextInterface
public function copy() { return self::$context[$this->cid]; }
public function getOption() { return self::getCache("match"); }
public function getOriginMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
public function getArrayMessage(): array {
$msg = $this->getOriginMessage();
if (is_array($msg)) return $msg;
else return MessageUtil::strToArray($msg);
}
public function getStringMessage(): string {
$msg = $this->getOriginMessage();
if (is_string($msg)) return $msg;
else return MessageUtil::arrayToStr($msg);
}
}

View File

@@ -91,7 +91,7 @@ class EventDispatcher
try {
foreach ((EventManager::$events[$this->class] ?? []) as $v) {
$this->dispatchEvent($v, $this->rule, ...$params);
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。");
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . (is_string($v->method) ? $v->method : "{closure}") . " 分发结束。");
if ($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) continue;
if (is_callable($this->return_func) && $this->status === self::STATUS_NORMAL) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ...");
@@ -121,7 +121,7 @@ class EventDispatcher
public function dispatchEvent($v, $rule_func = null, ...$params) {
$q_c = $v->class;
$q_f = $v->method;
if ($q_c === "" && ($q_f instanceof Closure)) {
if (($q_c ?? "") === "" && ($q_f instanceof Closure)) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 闭包函数的事件触发过程!");
if ($rule_func !== null && !$rule_func($v)) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 闭包函数下的 ruleFunc 判断为 false, 拒绝执行此方法。");
@@ -158,6 +158,7 @@ class EventDispatcher
$r[$k]->class = $q_c;
$r[$k]->method = $q_f;
$r[$k]->middleware = $middleware;
$r[$k]->current_event = $v;
if (isset($middleware_obj["before"])) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ...");
$rs = $middleware_obj["before"];

View File

@@ -12,6 +12,7 @@ use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnTick;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Exception\AnnotationException;
use ZM\Store\LightCache;
use ZM\Store\ZMAtomic;
@@ -23,11 +24,18 @@ class EventManager
public static $req_mapping = [];
public static function addEvent($event_name, ?AnnotationBase $event_obj) {
Console::debug("Adding event $event_name at ".$event_obj->class.":".$event_obj->method);
if ($event_obj->method instanceof \Closure) {
Console::debug("Adding event $event_name at @Anonymous");
} else {
Console::debug("Adding event $event_name at " . ($event_obj->class) . ":" . ($event_obj->method));
}
self::$events[$event_name][] = $event_obj;
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
}
/**
* @throws AnnotationException
*/
public static function loadEventByParser(AnnotationParser $parser) {
self::$events = $parser->generateAnnotationEvents();
self::$middlewares = $parser->getMiddlewares();

View File

@@ -7,7 +7,7 @@ namespace ZM\Event\SwooleEvent;
use Swoole\Process;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\ConnectionManager\ManagerGM;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
@@ -23,11 +23,7 @@ class OnBeforeReload implements SwooleEvent
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1);
}
foreach ($server->connections as $fd) {
if (ManagerGM::get($fd) !== null) $server->disconnect($fd);
else $server->close($fd);
}
usleep(800 * 1000);
$conf = ZMConfig::get("global", "runtime")["reload_delay_time"] ?? 800;
if ($conf !== 0) usleep($conf * 1000);
}
}

View File

@@ -17,6 +17,7 @@ use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ProcessManager;
use ZM\Utils\SignalListener;
use ZM\Utils\Terminal;
use ZM\Utils\ZMUtil;
@@ -28,15 +29,20 @@ use ZM\Utils\ZMUtil;
*/
class OnManagerStart implements SwooleEvent
{
/** @var null|Process */
public static $process = null;
private static $last_hash = "";
private static $watch = -1;
public function onCall(Server $server) {
Console::debug("Calling onManagerStart event(1)");
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalManager();
}
self::$process = new Process(function() {
Framework::saveProcessState(ZM_PROCESS_MANAGER, $server->manager_pid);
ProcessManager::createUserProcess('monitor', function() use ($server){
Process::signal(SIGINT, function() {
Console::success("用户进程检测到了Ctrl+C");
});
if (Framework::$argv["watch"]) {
if (extension_loaded('inotify')) {
Console::info("Enabled File watcher, framework will reload automatically.");
@@ -48,10 +54,27 @@ class OnManagerStart implements SwooleEvent
Console::verbose("File updated: " . $r[0]["name"]);
ZMUtil::reload();
});
Framework::$argv["polling-watch"] = false; // 如果开启了inotify则关闭轮询热更新
} else {
Console::warning(zm_internal_errcode("E00024") . "You have not loaded \"inotify\" extension, please install first.");
Console::warning(zm_internal_errcode("E00024") . "你还没有安装或启用 inotify 扩展,将默认使用轮询检测模式开启热更新!");
Framework::$argv["polling-watch"] = true;
}
}
if (Framework::$argv["polling-watch"]) {
self::$watch = swoole_timer_tick(3000, function () use ($server) {
$data = (DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/'));
$hash = md5("");
foreach ($data as $file) {
$hash = md5($hash . md5_file($file));
}
if (self::$last_hash == "") {
self::$last_hash = $hash;
} elseif (self::$last_hash !== $hash) {
self::$last_hash = $hash;
$server->reload();
}
});
}
if (Framework::$argv["interact"]) {
Console::info("Interact mode");
ZMBuf::$terminal = $r = STDIN;
@@ -73,8 +96,16 @@ class OnManagerStart implements SwooleEvent
});
}
});
self::$process->set(['enable_coroutine' => true]);
self::$process->start();
ProcessManager::getUserProcess('monitor')->set(['enable_coroutine' => true]);
ProcessManager::getUserProcess('monitor')->start();
/*$dispatcher = new EventDispatcher(OnManagerStartEvent::class);
$dispatcher->setRuleFunction(function($v) {
return eval("return " . $v->getRule() . ";");
});
$dispatcher->dispatchEvents($server);
*/
Console::verbose("进程 Manager 已启动");
}

View File

@@ -8,7 +8,8 @@ use Swoole\Process;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\DataProvider;
use ZM\Framework;
use ZM\Utils\Manager\ProcessManager;
/**
* Class OnManagerStop
@@ -17,15 +18,12 @@ use ZM\Utils\DataProvider;
*/
class OnManagerStop implements SwooleEvent
{
public function onCall() {
if (OnManagerStart::$process !== null) {
if (Process::kill(OnManagerStart::$process->pid, 0)) {
Process::kill(OnManagerStart::$process->pid, SIGTERM);
}
public function onCall()
{
foreach (ProcessManager::$user_process as $v) {
if (posix_getsid($v->pid) !== false) Process::kill($v->pid, SIGTERM);
}
Console::verbose("进程 Manager 已停止!");
if (file_exists(DataProvider::getWorkingDir()."/.daemon_pid")) {
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
}
Framework::removeProcessState(ZM_PROCESS_MANAGER);
}
}

View File

@@ -25,9 +25,6 @@ use ZM\Event\SwooleEvent;
*/
class OnMessage implements SwooleEvent
{
/**
* @noinspection PhpUnreachableStatementInspection
*/
public function onCall($server, Frame $frame) {
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd . ": " . TermColor::ITALIC . $frame->data . TermColor::RESET);
unset(Context::$context[Coroutine::getCid()]);

View File

@@ -70,7 +70,7 @@ class OnOpen implements SwooleEvent
try {
$obb_onebot = ZMConfig::get("global", "onebot") ??
ZMConfig::get("global", "modules")["onebot"] ??
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
["status" => true, "single_bot_mode" => false, "message_level" => 99];
$onebot_status = $obb_onebot["status"];
if ($conn->getName() === 'qq' && $onebot_status === true) {
if ($obb_onebot["single_bot_mode"]) {

View File

@@ -10,7 +10,7 @@ use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\Manager\ProcessManager;
use ZM\Utils\Manager\WorkerManager;
/**
* Class OnPipeMessage
@@ -22,7 +22,7 @@ class OnPipeMessage implements SwooleEvent
public function onCall(Server $server, $src_worker_id, $data) {
$data = json_decode($data, true);
try {
ProcessManager::workerAction($src_worker_id, $data);
WorkerManager::workerAction($src_worker_id, $data);
} catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error(zm_internal_errcode("E00021") . "Uncaught exception " . get_class($e) . " when calling \"pipeMessage\": " . $error_msg);

View File

@@ -8,6 +8,8 @@ use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Utils\DataProvider;
/**
* Class OnShutdown
@@ -18,5 +20,9 @@ class OnShutdown implements SwooleEvent
{
public function onCall(Server $server) {
Console::verbose("正在关闭 Master 进程pid=" . posix_getpid());
Framework::removeProcessState(ZM_PROCESS_MASTER);
if (DataProvider::scanDirFiles(_zm_pid_dir()) == []) {
rmdir(_zm_pid_dir());
}
}
}

View File

@@ -25,14 +25,9 @@ class OnStart implements SwooleEvent
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalMaster($server);
}
if (Framework::$argv["daemon"]) {
$daemon_data = json_encode([
"pid" => $server->master_pid,
"stdout" => ZMConfig::get("global")["swoole"]["log_file"]
], 128 | 256);
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
}
Framework::saveProcessState(ZM_PROCESS_MASTER, $server->master_pid, [
'stdout' => ZMConfig::get("global")["swoole"]["log_file"],
'daemon' => (bool)Framework::$argv["daemon"]
]);
}
}

View File

@@ -43,13 +43,15 @@ use ZM\Utils\SignalListener;
*/
class OnWorkerStart implements SwooleEvent
{
public function onCall(Server $server, $worker_id) {
public function onCall(Server $server, $worker_id)
{
Console::debug("Calling onWorkerStart event(1)");
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalWorker($server, $worker_id);
}
unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) {
Framework::saveProcessState(ZM_PROCESS_WORKER, $server->worker_pid, ['worker_id' => $worker_id]);
zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid);
if (LightCacheInside::get("wait_api", "wait_api") !== null) {
LightCacheInside::unset("wait_api", "wait_api");
@@ -105,10 +107,13 @@ class OnWorkerStart implements SwooleEvent
Console::error(zm_internal_errcode("E00030") . "PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM);
if (!Framework::$argv["watch"]) { // 在热更新模式下不能退出
Process::kill($server->master_pid, SIGTERM);
}
}
} else {
// 这里是TaskWorker初始化的内容部分
Framework::saveProcessState(ZM_PROCESS_TASKWORKER, $server->worker_pid, ['worker_id' => $worker_id]);
try {
Framework::$server = $server;
$this->loadAnnotations();
@@ -131,13 +136,15 @@ class OnWorkerStart implements SwooleEvent
* @throws ReflectionException
* @throws Exception
*/
private function loadAnnotations() {
private function loadAnnotations()
{
if (Framework::$instant_mode) goto skip;
//加载各个模块的注解类,以及反射
Console::debug("Mapping annotations");
$parser = new AnnotationParser();
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
foreach ($composer["autoload"]["psr-4"] as $k => $v) {
$merge = array_merge($composer["autoload"]["psr-4"] ?? [], $composer["autoload-dev"]["psr-4"] ?? []);
foreach ($merge as $k => $v) {
if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) {
if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue;
if (trim($k, "\\") == "ZM") continue;
@@ -146,45 +153,41 @@ class OnWorkerStart implements SwooleEvent
}
}
//加载自定义的全局函数
Console::debug("Loading context class...");
$context_class = ZMConfig::get("global", "context_class");
if (!is_a($context_class, ContextInterface::class, true)) {
throw new ZMKnownException("E00032", "Context class must implemented from ContextInterface!");
}
//加载插件
$obb_onebot = ZMConfig::get("global", "onebot") ??
ZMConfig::get("global", "modules")["onebot"] ??
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
// 检查是否允许热加载phar模块允许的话将遍历phar内的文件
$plugin_enable_hotload = ZMConfig::get("global", "module_loader")["enable_hotload"] ?? false;
if ($plugin_enable_hotload) {
$list = ModuleManager::getPackedModules();
foreach($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: ".$k);
foreach ($list as $k => $v) {
if (\server()->worker_id === 0) Console::info("Loading packed module: " . $k);
require_once $v["phar-path"];
$func = "loader".$v["generated-id"];
$func = "loader" . $v["generated-id"];
$func();
$parser->addRegisterPath("phar://".$v["phar-path"]."/".$v["module-root-path"], $v["namespace"]);
$parser->addRegisterPath("phar://" . $v["phar-path"] . "/" . $v["module-root-path"], $v["namespace"]);
}
}
$parser->registerMods();
EventManager::loadEventByParser($parser); //加载事件
skip:
//加载自定义的全局函数
Console::debug("Loading context class...");
$context_class = ZMConfig::get("global", "context_class");
if (!is_a($context_class, ContextInterface::class, true)) {
throw new ZMKnownException("E00032", "Context class must implemented from ContextInterface!");
}
//加载插件
$obb_onebot = ZMConfig::get("global", "onebot") ??
ZMConfig::get("global", "modules")["onebot"] ??
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
if ($obb_onebot["status"]) {
Console::debug("OneBot support enabled, listening OneBot event(3).");
$obj = new OnMessageEvent();
$obj->class = QQBot::class;
$obj->method = 'handleByEvent';
$obj->level = $obb_onebot["message_level"] ?? 99999;
$obj->level = $obb_onebot["message_level"] ?? 99;
$obj->rule = 'connectIsQQ()';
EventManager::addEvent(OnMessageEvent::class, $obj);
zm_dump(EventManager::$events[OnMessageEvent::class]);
if ($obb_onebot["single_bot_mode"]) {
LightCacheInside::set("connect", "conn_fd", -1);
} else {
@@ -193,7 +196,8 @@ class OnWorkerStart implements SwooleEvent
}
}
private function initMySQLPool() {
private function initMySQLPool()
{
if (SqlPoolStorage::$sql_pool !== null) {
SqlPoolStorage::$sql_pool->close();
SqlPoolStorage::$sql_pool = null;

View File

@@ -9,7 +9,9 @@ use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Framework;
use ZM\Store\LightCache;
use ZM\Utils\DataProvider;
/**
* Class OnWorkerStop
@@ -22,6 +24,7 @@ class OnWorkerStop implements SwooleEvent
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
LightCache::savePersistence();
}
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止: ".$server->getWorkerStatus($worker_id));
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止 (Worker 状态码: ".$server->getWorkerStatus($worker_id).")");
Framework::removeProcessState($server->taskworker ? ZM_PROCESS_TASKWORKER : ZM_PROCESS_WORKER, $worker_id);
}
}

View File

@@ -12,6 +12,7 @@ use Throwable;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
use ZM\Exception\ZMKnownException;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
@@ -76,9 +77,12 @@ class Framework
*/
private $setup_events = [];
public function __construct($args = []) {
$tty_width = $this->getTtyWidth();
public static $instant_mode = false;
public function __construct($args = [], $instant_mode = false)
{
$tty_width = $this->getTtyWidth();
self::$instant_mode = $instant_mode;
self::$argv = $args;
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
@@ -116,6 +120,9 @@ class Framework
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
if ((ZMConfig::get("global", "runtime")["save_console_log_file"] ?? false) !== false) {
Console::setOutputFile(ZMConfig::get("global", "runtime")["save_console_log_file"]);
}
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
date_default_timezone_set($timezone);
@@ -124,7 +131,9 @@ class Framework
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
//if ($instant_mode) {
$this->loadServerEvents();
//}
$this->parseCliArgs(self::$argv, $add_port);
@@ -148,7 +157,7 @@ class Framework
} else {
$out["worker"] = $this->server_set["worker_num"];
}
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
$out["environment"] = ($args["env"] ?? null) === null ? "default" : $args["env"];
$out["log_level"] = Console::getLevel();
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
$out["master_pid"] = posix_getpid();
@@ -156,7 +165,7 @@ class Framework
if (isset($this->server_set["task_worker_num"])) {
$out["task_worker"] = $this->server_set["task_worker_num"];
}
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
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"];
}
@@ -182,7 +191,7 @@ class Framework
}
self::printProps($out, $tty_width, $args["log-theme"] === null);
if ($args["preview"]) {
if ($args["preview"] ?? false) {
exit();
}
@@ -325,7 +334,8 @@ class Framework
}
}
private static function printMotd($tty_width) {
private static function printMotd($tty_width)
{
if (file_exists(DataProvider::getSourceRootDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getSourceRootDir() . "/config/motd.txt");
} else {
@@ -337,7 +347,118 @@ class Framework
echo $motd;
}
public function start() {
/**
* 将各进程的pid写入文件以备后续崩溃及僵尸进程处理使用
* @param int $type
* @param int|string $pid
* @param array $data
* @internal
*/
public static function saveProcessState(int $type, $pid, array $data = [])
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
$json = [
'pid' => intval($pid),
'stdout' => $data['stdout'],
'daemon' => $data['daemon']
];
file_put_contents($file, json_encode($json, JSON_UNESCAPED_UNICODE));
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_WORKER:
$file = _zm_pid_dir() . '/worker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_USER:
$file = _zm_pid_dir() . '/user.' . $data['process_name'] . '.pid';
file_put_contents($file, strval($pid));
return;
case ZM_PROCESS_TASKWORKER:
$file = _zm_pid_dir() . '/taskworker.' . $data['worker_id'] . '.pid';
file_put_contents($file, strval($pid));
return;
}
}
/**
* 用于框架内部获取多进程运行状态的函数
* @param int $type
* @param null $id_or_name
* @return false|int|mixed
* @throws ZMKnownException
* @internal
*/
public static function getProcessState(int $type, $id_or_name = null)
{
$file = _zm_pid_dir();
switch ($type) {
case ZM_PROCESS_MASTER:
if (!file_exists($file . '/master.json')) return false;
$json = json_decode(file_get_contents($file . '/master.json'), true);
if ($json !== null) return $json;
else return false;
case ZM_PROCESS_MANAGER:
if (!file_exists($file . '/manager.pid')) return false;
return intval(file_get_contents($file . '/manager.pid'));
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/worker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/worker.' . $id_or_name . '.pid'));
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
if (!file_exists($file . '/user.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/user.' . $id_or_name . '.pid'));
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
if (!file_exists($file . '/taskworker.' . $id_or_name . '.pid')) return false;
return intval(file_get_contents($file . '/taskworker.' . $id_or_name . '.pid'));
default:
return false;
}
}
/**
* @param int $type
* @param null $id_or_name
* @throws ZMKnownException
* @internal
*/
public static function removeProcessState(int $type, $id_or_name = null)
{
switch ($type) {
case ZM_PROCESS_MASTER:
$file = _zm_pid_dir() . '/master.json';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_MANAGER:
$file = _zm_pid_dir() . '/manager.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_WORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/worker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_USER:
if (!is_string($id_or_name)) throw new ZMKnownException('E99999', 'process_name必须为字符串');
$file = _zm_pid_dir() . '/user.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
case ZM_PROCESS_TASKWORKER:
if (!is_int($id_or_name)) throw new ZMKnownException('E99999', 'worker_id必须为整数');
$file = _zm_pid_dir() . '/taskworker.' . $id_or_name . '.pid';
if (file_exists($file)) unlink($file);
return;
}
}
public function start()
{
try {
self::$loaded_files = get_included_files();
self::$server->start();
@@ -351,7 +472,8 @@ class Framework
/**
* @noinspection PhpIncludeInspection
*/
private function loadServerEvents() {
private function loadServerEvents()
{
if (Phar::running() !== "") {
ob_start();
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php";
@@ -376,7 +498,8 @@ class Framework
* @throws ReflectionException
* @throws ReflectionException
*/
private function registerServerEvents() {
private function registerServerEvents()
{
$reader = new AnnotationReader();
$all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
foreach ($all as $v) {
@@ -393,6 +516,7 @@ class Framework
}
foreach (($this->setup_events["setup"] ?? []) as $v) {
Console::debug('Calling @OnSetup: ' . $v["class"]);
$c = ZMUtil::getModInstance($v["class"]);
$method = $v["method"];
$c->$method();
@@ -410,7 +534,8 @@ class Framework
* @param $args
* @param $add_port
*/
private function parseCliArgs($args, &$add_port) {
private function parseCliArgs($args, &$add_port)
{
$coroutine_mode = true;
global $terminal_id;
$terminal_id = uuidgen();
@@ -436,6 +561,7 @@ class Framework
$coroutine_mode = false;
break;
case 'debug-mode':
self::$argv["disable-safe-exit"] = true;
$coroutine_mode = false;
$terminal_id = null;
self::$argv["watch"] = true;
@@ -482,11 +608,12 @@ class Framework
}
}
$global_hook = ZMConfig::get("global", 'runtime')['swoole_coroutine_hook_flags'] ?? (SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL));
if ($coroutine_mode && $global_hook === false) Runtime::enableCoroutine(true, $global_hook);
if ($coroutine_mode) Runtime::enableCoroutine(true, $global_hook);
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
}
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
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]) { //输出的内容太多了,以至于一行都放不下一个,要折行
@@ -530,7 +657,8 @@ class Framework
}
}
public static function printProps($out, $tty_width, $colorful = true) {
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;
@@ -572,20 +700,23 @@ class Framework
echo str_pad("", $max_border, "=") . PHP_EOL;
}
public static function getTtyWidth(): string {
public static function getTtyWidth(): string
{
$size = exec("stty size");
if (empty($size)) return 65;
return explode(" ", trim($size))[1];
}
public static function loadFrameworkState() {
public static function loadFrameworkState()
{
if (!file_exists(DataProvider::getDataFolder() . ".state.json")) return [];
$r = json_decode(file_get_contents(DataProvider::getDataFolder() . ".state.json"), true);
if ($r === null) $r = [];
return $r;
}
public static function saveFrameworkState($data) {
public static function saveFrameworkState($data)
{
return file_put_contents(DataProvider::getDataFolder() . ".state.json", json_encode($data, 64 | 128 | 256));
}
}

View File

@@ -119,7 +119,7 @@ class Response
* @return mixed
*/
public function header($key, $value, $ucwords = null) {
if (!$this->is_end) return $this->response->header($key, $value, $ucwords);
if (!$this->is_end) return $ucwords === null ? $this->response->header($key, $value) : $this->response->header($key, $value, $ucwords);
else return false;
}
@@ -130,7 +130,8 @@ class Response
* @return mixed
*/
public function setHeader($key, $value, $ucwords = null) {
return !$this->is_end ? $this->response->setHeader($key, $value, $ucwords) : false;
if (!$this->is_end) return $ucwords === null ? $this->response->setHeader($key, $value) : $this->response->setHeader($key, $value, $ucwords);
else return false;
}
/**

View File

@@ -0,0 +1,18 @@
<?php
namespace ZM\Module;
/**
* @since 2.6
*/
class InstantModule extends ModuleBase
{
public function onEvent($event_class, $params, callable $callable) {
$class = new $event_class();
foreach ($params as $k => $v) {
if (is_string($k)) $class->$k = $v;
}
$class->method = $callable;
$this->events[] = $class;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace ZM\Module;
/**
* @since 2.6
*/
abstract class ModuleBase
{
protected $module_name;
protected $events = [];
public function __construct($module_name) {
$this->module_name = $module_name;
}
/**
* @return mixed
*/
public function getModuleName() {
return $this->module_name;
}
/**
* @return array
*/
public function getEvents(): array {
return $this->events;
}
}

View File

@@ -4,6 +4,7 @@
namespace ZM\Module;
use Exception;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQAPIResponse;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
@@ -11,6 +12,7 @@ use ZM\Annotation\CQ\CQMessage;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Annotation\CQ\CQNotice;
use ZM\Annotation\CQ\CQRequest;
use ZM\Config\ZMConfig;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Exception\WaitTimeoutException;
@@ -59,9 +61,25 @@ class QQBot
}
if (isset($data["post_type"])) $this->dispatchEvents($data);
else $this->dispatchAPIResponse($data);
if (($data["post_type"] ?? "meta_event") != "meta_event") {
$r = $this->dispatchAfterEvents($data); // before在这里执行元事件不执行before为减少不必要的调试日志
if ($r->store === "block") EventDispatcher::interrupt();
}
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
if (($data["post_type"] ?? "meta_event") != "meta_event") {
$r = $this->dispatchAfterEvents($data); // before在这里执行元事件不执行before为减少不必要的调试日志
if ($r->store === "block") EventDispatcher::interrupt();
}
$e->module->finalReply($e->getMessage());
} catch (InterruptException $e) {
if (($data["post_type"] ?? "meta_event") != "meta_event") {
$r = $this->dispatchAfterEvents($data); // before在这里执行元事件不执行before为减少不必要的调试日志
if ($r->store === "block") EventDispatcher::interrupt();
}
throw $e;
}
// 这里修复CQAfter不能使用的问题我竟然一直没写绝了
}
/**
@@ -73,11 +91,11 @@ class QQBot
public function dispatchBeforeEvents($data, $time): EventDispatcher {
$before = new EventDispatcher(CQBefore::class);
if ($time === "pre") {
$before->setRuleFunction(function($v) use ($data){
$before->setRuleFunction(function ($v) use ($data) {
return $v->level >= 200 && $v->cq_event == $data["post_type"];
});
} else {
$before->setRuleFunction(function($v) use ($data){
$before->setRuleFunction(function ($v) use ($data) {
return $v->level < 200 && $v->cq_event == $data["post_type"];
});
}
@@ -103,22 +121,33 @@ class QQBot
if (is_string($result)) ctx()->reply($result);
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
});
$s = MessageUtil::matchCommand(ctx()->getMessage(), ctx()->getData());
$s = MessageUtil::matchCommand(ctx()->getStringMessage(), ctx()->getData());
if ($s->status !== false) {
if (!empty($s->match)) ctx()->setCache("match", $s->match);
$dispatcher->dispatchEvent($s->object, null);
if (is_string($dispatcher->store)) ctx()->reply($dispatcher->store);
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
if (ctx()->getCache("has_reply") === true) {
$policy = ZMConfig::get("global", "onebot")['message_command_policy'] ?? 'interrupt';
switch ($policy) {
case 'interrupt':
EventDispatcher::interrupt();
break;
case 'continue':
break;
default:
throw new Exception("未知的消息命令策略:" . $policy);
}
}
}
//分发CQMessage事件
$msg_dispatcher = new EventDispatcher(CQMessage::class);
$msg_dispatcher->setRuleFunction(function ($v) {
return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) &&
($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]));
return ($v->message == '' || ($v->message == ctx()->getStringMessage())) &&
($v->user_id == 0 || ($v->user_id == ctx()->getUserId())) &&
($v->group_id == 0 || ($v->group_id == (ctx()->getGroupId() ?? 0))) &&
($v->message_type == '' || ($v->message_type == ctx()->getMessageType())) &&
($v->raw_message == '' || ($v->raw_message == context()->getData()["raw_message"]));
});
$msg_dispatcher->setReturnFunction(function ($result) {
if (is_string($result)) ctx()->reply($result);
@@ -158,6 +187,15 @@ class QQBot
}
}
private function dispatchAfterEvents($data): EventDispatcher {
$after = new EventDispatcher(CQAfter::class);
$after->setRuleFunction(function ($v) use ($data) {
return $v->cq_event == $data["post_type"];
});
$after->dispatchEvents($data);
return $after;
}
/**
* @param $req
* @throws Exception

View File

@@ -7,28 +7,33 @@ namespace ZM\MySQL;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Driver\StatementIterator;
use Doctrine\DBAL\ParameterType;
use IteratorAggregate;
use PDO;
use PDOStatement;
use Swoole\Database\PDOStatementProxy;
class MySQLStatement implements Statement, \IteratorAggregate
class MySQLStatement implements IteratorAggregate, Statement
{
/** @var PDOStatement|PDOStatementProxy */
private $statement;
public function __construct($obj) {
public function __construct($obj)
{
$this->statement = $obj;
}
public function closeCursor() {
public function closeCursor()
{
return $this->statement->closeCursor();
}
public function columnCount() {
public function columnCount()
{
return $this->statement->columnCount();
}
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = []) {
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = [])
{
if ($arg2 !== null && $arg3 !== [])
return $this->statement->setFetchMode($fetchMode, $arg2, $arg3);
elseif ($arg2 !== null && $arg3 === [])
@@ -39,47 +44,63 @@ class MySQLStatement implements Statement, \IteratorAggregate
return $this->statement->setFetchMode($fetchMode);
}
public function fetch($fetchMode = PDO::FETCH_ASSOC, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) {
public function fetch($fetchMode = PDO::FETCH_ASSOC, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0)
{
return $this->statement->fetch($fetchMode, $cursorOrientation, $cursorOffset);
}
public function fetchAll($fetchMode = PDO::FETCH_ASSOC, $fetchArgument = null, $ctorArgs = null) {
return $this->statement->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
public function fetchAll($fetchMode = PDO::FETCH_ASSOC, $fetchArgument = null, $ctorArgs = null)
{
if ($fetchArgument === null && $ctorArgs === null)
return $this->statement->fetchAll($fetchMode);
elseif ($fetchArgument !== null && $ctorArgs === null)
return $this->statement->fetchAll($fetchMode, $fetchArgument);
else
return $this->statement->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
}
public function fetchColumn($columnIndex = 0) {
public function fetchColumn($columnIndex = 0)
{
return $this->statement->fetchColumn($columnIndex);
}
public function bindValue($param, $value, $type = ParameterType::STRING) {
public function bindValue($param, $value, $type = ParameterType::STRING)
{
return $this->statement->bindValue($param, $value, $type);
}
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) {
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null)
{
return $this->statement->bindParam($param, $variable, $type, $length);
}
public function errorCode() {
public function errorCode()
{
return $this->statement->errorCode();
}
public function errorInfo() {
public function errorInfo()
{
return $this->statement->errorInfo();
}
public function execute($params = null) {
public function execute($params = null)
{
return $this->statement->execute($params);
}
public function rowCount() {
public function rowCount()
{
return $this->statement->rowCount();
}
public function getIterator() {
public function getIterator(): StatementIterator
{
return new StatementIterator($this);
}
public function current() {
public function current()
{
return $this->statement->current();
}
}

View File

@@ -94,7 +94,7 @@ class MySQLWrapper
* @return false|mixed
* @throws DbException
*/
public function fetchOne(string $query, array $params = [], array $types = []): bool {
public function fetchOne(string $query, array $params = [], array $types = []) {
try {
return $this->connection->fetchOne($query, $params, $types);
} catch (Throwable $e) {

View File

@@ -4,120 +4,29 @@
namespace ZM\Utils\Manager;
use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\WorkerCache;
use Swoole\Process;
class ProcessManager
{
public static function workerAction($src_worker_id, $data) {
$server = server();
switch ($data["action"] ?? '') {
case 'add_short_command':
Console::verbose("Adding short command " . $data["data"][0]);
$obj = new CQCommand();
$obj->method = quick_reply_closure($data["data"][1]);
$obj->match = $data["data"][0];
$obj->class = "";
EventManager::addEvent(CQCommand::class, $obj);
break;
case "eval":
eval($data["data"]);
break;
case "call_static":
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
break;
case "save_persistence":
LightCache::savePersistence();
break;
case "resume_ws_message":
$obj = $data["data"];
Coroutine::resume($obj["coroutine"]);
break;
case "getWorkerCache":
$r = WorkerCache::get($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "setWorkerCache":
$r = WorkerCache::set($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "unsetWorkerCache":
$r = WorkerCache::unset($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "hasKeyWorkerCache":
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "asyncAddWorkerCache":
WorkerCache::add($data["key"], $data["value"], true);
break;
case "asyncSubWorkerCache":
WorkerCache::sub($data["key"], $data["value"], true);
break;
case "asyncSetWorkerCache":
WorkerCache::set($data["key"], $data["value"], true);
break;
case "asyncUnsetWorkerCache":
WorkerCache::unset($data["key"], true);
break;
case "addWorkerCache":
$r = WorkerCache::add($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "subWorkerCache":
$r = WorkerCache::sub($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "returnWorkerCache":
WorkerCache::$transfer[$data["cid"]] = $data["value"];
zm_resume($data["cid"]);
break;
default:
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
return $v->action == $data["action"];
});
$dispatcher->dispatchEvents($data);
break;
}
/** @var Process[] */
public static $user_process = [];
/**
* @param string $name
* @param callable $callable
* @return Process
*/
public static function createUserProcess(string $name, callable $callable): Process
{
return self::$user_process[$name] = new Process($callable);
}
public static function sendActionToWorker($worker_id, $action, $data) {
$obj = ["action" => $action, "data" => $data];
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!");
return;
}
if (server()->worker_id == $worker_id) {
self::workerAction($worker_id, $obj);
} else {
server()->sendMessage(json_encode($obj), $worker_id);
}
}
public static function resumeAllWorkerCoroutines() {
if (server()->worker_id === -1) {
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
return;
}
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
if (isset($v["coroutine"], $v["worker_id"])) {
if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]);
}
}
/**
* @param string $string
* @return Process|null
*/
public static function getUserProcess(string $string): ?Process
{
return self::$user_process[$string] ?? null;
}
}

View File

@@ -0,0 +1,141 @@
<?php
namespace ZM\Utils\Manager;
use Exception;
use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\WorkerCache;
class WorkerManager
{
/**
* Worker 进程间通信触发的动作类型函数
* @param $src_worker_id
* @param $data
* @throws Exception
*/
public static function workerAction($src_worker_id, $data)
{
$server = server();
switch ($data["action"] ?? '') {
case 'add_short_command':
Console::verbose("Adding short command " . $data["data"][0]);
$obj = new CQCommand();
$obj->method = quick_reply_closure($data["data"][1]);
$obj->match = $data["data"][0];
$obj->class = "";
EventManager::addEvent(CQCommand::class, $obj);
break;
case "eval":
eval($data["data"]);
break;
case "call_static":
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
break;
case "save_persistence":
LightCache::savePersistence();
break;
case "resume_ws_message":
$obj = $data["data"];
Coroutine::resume($obj["coroutine"]);
break;
case "getWorkerCache":
$r = WorkerCache::get($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "setWorkerCache":
$r = WorkerCache::set($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "unsetWorkerCache":
$r = WorkerCache::unset($data["key"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "hasKeyWorkerCache":
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "asyncAddWorkerCache":
WorkerCache::add($data["key"], $data["value"], true);
break;
case "asyncSubWorkerCache":
WorkerCache::sub($data["key"], $data["value"], true);
break;
case "asyncSetWorkerCache":
WorkerCache::set($data["key"], $data["value"], true);
break;
case "asyncUnsetWorkerCache":
WorkerCache::unset($data["key"], true);
break;
case "addWorkerCache":
$r = WorkerCache::add($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "subWorkerCache":
$r = WorkerCache::sub($data["key"], $data["value"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "returnWorkerCache":
WorkerCache::$transfer[$data["cid"]] = $data["value"];
zm_resume($data["cid"]);
break;
default:
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
return $v->action == $data["action"];
});
$dispatcher->dispatchEvents($data);
break;
}
}
/**
* 给 Worker 进程发送动作指令(包括自身,自身将直接执行)
* @param $worker_id
* @param $action
* @param $data
* @throws Exception
*/
public static function sendActionToWorker($worker_id, $action, $data)
{
$obj = ["action" => $action, "data" => $data];
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!");
return;
}
if (server()->worker_id == $worker_id) {
self::workerAction($worker_id, $obj);
} else {
server()->sendMessage(json_encode($obj), $worker_id);
}
}
/**
* 向所有 Worker 进程发送动作指令
*/
public static function resumeAllWorkerCoroutines()
{
if (server()->worker_id === -1) {
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
return;
}
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
if (isset($v["coroutine"], $v["worker_id"])) {
if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]);
}
}
}
}

View File

@@ -59,7 +59,7 @@ class MessageUtil
return false;
}
public static function isAtMe($msg, $me_id) {
public static function isAtMe($msg, $me_id): bool {
return strpos($msg, CQ::at($me_id)) !== false;
}
@@ -72,7 +72,7 @@ class MessageUtil
* @param int $type
* @return string
*/
public static function getImageCQFromLocal($file, $type = 0): string {
public static function getImageCQFromLocal($file, int $type = 0): string {
switch ($type) {
case 0:
return CQ::image("base64://" . base64_encode(file_get_contents($file)));
@@ -90,7 +90,7 @@ class MessageUtil
* @param $msg
* @return array|string[]
*/
public static function splitCommand($msg) {
public static function splitCommand($msg): array {
$word = explodeMsg(str_replace("\r", "", $msg));
if (empty($word)) $word = [""];
if (count(explode("\n", $word[0])) >= 2) {
@@ -98,7 +98,7 @@ class MessageUtil
$first = split_explode(" ", array_shift($enter));
$word = array_merge($first, $enter);
foreach ($word as $k => $v) {
$word[$k] = trim($word[$k]);
$word[$k] = trim($v);
}
}
return $word;
@@ -109,15 +109,18 @@ class MessageUtil
* @param $obj
* @return MatchResult
*/
public static function matchCommand($msg, $obj) {
public static function matchCommand($msg, $obj): MatchResult {
$ls = EventManager::$events[CQCommand::class] ?? [];
if (is_array($msg)) {
$msg = self::arrayToStr($msg);
}
$word = self::splitCommand($msg);
$matched = new MatchResult();
foreach ($ls as $v) {
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue;
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $obj["message_type"]))
elseif (($v->user_id == 0 || ($v->user_id == $obj["user_id"])) &&
($v->group_id == 0 || ($v->group_id == ($obj["group_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type == $obj["message_type"]))
) {
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
array_shift($word);
@@ -166,4 +169,61 @@ class MessageUtil
ProcessManager::sendActionToWorker($i, "add_short_command", [$command, $reply]);
}
}
/**
* 字符串转数组
* @param $msg
* @param bool $ignore_space
* @param false $trim_text
* @return array
*/
public static function strToArray($msg, bool $ignore_space = true, bool $trim_text = false): array {
$arr = [];
while (($rear = mb_strstr($msg, '[CQ:')) !== false && ($end = mb_strstr($rear, ']', true)) !== false) {
// 把 [CQ: 前面的文字生成段落
$front = mb_strstr($msg, '[CQ:', true);
// 如果去掉空格都还有文字,或者不去掉空格有字符,且不忽略空格,则生成段落,否则不生成
if (($trim_front = trim($front)) !== '' || ($front !== '' && !$ignore_space)) {
$arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_front : $front)]];
}
// 处理 CQ 码
$content = mb_substr($end, 4);
$cq = explode(",", $content);
$object_type = array_shift($cq);
$object_params = [];
foreach ($cq as $v) {
$key = mb_strstr($v, "=", true);
$object_params[$key] = CQ::decode(mb_substr(mb_strstr($v, "="), 1), true);
}
$arr[] = ["type" => $object_type, "data" => $object_params];
$msg = mb_substr(mb_strstr($rear, ']'), 1);
}
if (($trim_msg = trim($msg)) !== '' || ($msg !== '' && !$ignore_space)) {
$arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_msg : $msg)]];
}
return $arr;
}
/**
* 数组转字符串
* 纪念一下这段代码完全由AI生成没有人知道它是怎么写的这句话是我自己写的不知道是不是有人知道的
* @param array $array
* @return string
* @author Copilot
*/
public static function arrayToStr(array $array): string {
$str = "";
foreach ($array as $v) {
if ($v['type'] == 'text') {
$str .= $v['data']['text'];
} else {
$str .= "[CQ:" . $v['type'];
foreach ($v['data'] as $key => $value) {
$str .= "," . $key . "=" . CQ::encode($value, true);
}
$str .= "]";
}
}
return $str;
}
}

View File

@@ -16,6 +16,7 @@ use ZM\Console\Console;
*/
class SignalListener
{
private static $manager_kill_time = 0;
/**
* 监听Master进程的Ctrl+C
* @param Server $server
@@ -29,6 +30,7 @@ class SignalListener
} else {
echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
Console::warning("Server will be shutdown.");
Process::kill($server->master_pid, SIGTERM);
}
});
@@ -48,6 +50,7 @@ class SignalListener
} else {
Console::verbose("Interrupted in manager!");
}
self::processKillerPrompt();
};
Console::debug("Listening Manager SIGINT");
if (version_compare(SWOOLE_VERSION, "4.6.7") >= 0) {
@@ -65,15 +68,40 @@ class SignalListener
public static function signalWorker(Server $server, $worker_id) {
Console::debug("Listening Worker #".$worker_id." SIGINT");
Process::signal(SIGINT, function () use ($worker_id, $server) {
if ($server->master_pid == $server->worker_pid) {
if ($server->master_pid == $server->worker_pid) { // 当Swoole以单进程模型运行的时候Worker需要监听杀死的信号
echo "\r";
Console::warning("Server interrupted(SIGINT) on Worker.");
swoole_timer_after(2, function() {
Process::kill(posix_getpid(), SIGTERM);
});
self::processKillerPrompt();
}
//Console::verbose("Interrupted in worker");
// do nothing
});
}
/**
* 按5次Ctrl+C后强行杀死框架的处理函数
*/
private static function processKillerPrompt() {
if (self::$manager_kill_time > 0) {
if (self::$manager_kill_time >= 5) {
$file_path = _zm_pid_dir();
$flist = DataProvider::scanDirFiles($file_path, false, true);
foreach($flist as $file) {
$name = explode('.', $file);
if (end($name) == 'pid' && $name[0] !== 'manager') {
$pid = file_get_contents($file_path.'/'.$file);
Process::kill($pid, SIGKILL);
}
unlink($file_path.'/'.$file);
}
} else {
echo "\r";
Console::log("再按" . (5 - self::$manager_kill_time) . "次Ctrl+C所有Worker进程就会被强制杀死", 'red');
}
}
self::$manager_kill_time++;
}
}

View File

@@ -86,7 +86,7 @@ class ZMUtil
if (($pathinfo['extension'] ?? '') == 'php') {
if ($rule === null) { //规则未设置回调时候,使用默认的识别过滤规则
if (substr(file_get_contents($dir . '/' . $v), 6, 6) == '#plain') continue;
elseif (mb_substr($v, 0, 7) == 'global_' || mb_substr($v, 0, 7) == 'script_') continue;
elseif (mb_substr($pathinfo["basename"], 0, 7) == 'global_' || mb_substr($pathinfo["basename"], 0, 7) == 'script_') continue;
foreach (($composer['autoload']['files'] ?? []) as $fi) {
if (md5_file(DataProvider::getSourceRootDir().'/'.$fi) == md5_file($dir.'/'.$v)) continue 2;
}

59
src/ZM/ZMServer.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
namespace ZM;
use ZM\Command\RunServerCommand;
use ZM\Console\Console;
use ZM\Event\EventManager;
use ZM\Exception\InitException;
use ZM\Module\ModuleBase;
/**
* @since 2.6
*/
class ZMServer
{
protected $app_name;
/** @var ModuleBase[] */
protected $modules = [];
public function __construct($app_name) {
$this->app_name = $app_name;
}
public function addModule($module_class) {
$this->modules[] = $module_class;
}
public function run() {
Console::setLevel(4);
foreach ($this->modules as $module_class) {
foreach ($module_class->getEvents() as $event) {
EventManager::addEvent(get_class($event), $event);
}
}
echo "Running...\n";
if (defined("WORKDING_DIR")) throw new InitException();
_zm_env_check();
define("WORKING_DIR", getcwd());
define("SOURCE_ROOT_DIR", WORKING_DIR);
define("LOAD_MODE", is_dir(SOURCE_ROOT_DIR . "/src/ZM") ? 0 : 1);
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../../"));
define("ZM_VERSION_ID", ConsoleApplication::VERSION_ID);
define("ZM_VERSION", ConsoleApplication::VERSION);
$options = array_map(function ($x) {
return $x->getDefault();
}, RunServerCommand::exportDefinition()->getOptions());
(new Framework($options, true))->start();
}
/**
* @return mixed
*/
public function getAppName() {
return $this->app_name;
}
}

View File

@@ -21,7 +21,8 @@ use Swoole\Coroutine\System;
use ZM\Context\ContextInterface;
function getClassPath($class_name) {
function getClassPath($class_name)
{
$dir = str_replace("\\", "/", $class_name);
$dir2 = DataProvider::getSourceRootDir() . "/src/" . $dir . ".php";
//echo "@@@".$dir2.PHP_EOL;
@@ -30,13 +31,29 @@ function getClassPath($class_name) {
else return null;
}
/**
* 检查炸毛框架运行的环境
* @internal
*/
function _zm_env_check()
{
if (!extension_loaded("swoole")) die(zm_internal_errcode("E00001") . "Can not find swoole extension.\n");
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die(zm_internal_errcode("E00002") . "You must install swoole version >= 4.5.0 !");
if (version_compare(PHP_VERSION, "7.2") == -1) die(zm_internal_errcode("E00003") . "PHP >= 7.2 required.");
if (version_compare(SWOOLE_VERSION, "4.6.7") < 0 && !extension_loaded("pcntl")) {
Console::error(zm_internal_errcode("E00004") . "Swoole 版本必须不低于 4.6.7 或 PHP 安装加载了 pcntl 扩展!");
die();
}
}
/**
* 使用自己定义的万san能分割函数
* @param $msg
* @param bool $ban_comma
* @return array
*/
function explodeMsg($msg, $ban_comma = false): array {
function explodeMsg($msg, $ban_comma = false): array
{
$msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg);
@@ -52,13 +69,15 @@ function explodeMsg($msg, $ban_comma = false): array {
}
/** @noinspection PhpUnused */
function unicode_decode($str) {
function unicode_decode($str)
{
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str);
}
function matchPattern($pattern, $context): bool {
function matchPattern($pattern, $context): bool
{
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
@@ -70,7 +89,8 @@ function matchPattern($pattern, $context): bool {
return false;
}
function split_explode($del, $str, $divide_en = false): array {
function split_explode($del, $str, $divide_en = false): array
{
$str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if (
@@ -102,7 +122,8 @@ function split_explode($del, $str, $divide_en = false): array {
return $ls == [] ? [""] : $ls;
}
function matchArgs($pattern, $context) {
function matchArgs($pattern, $context)
{
$result = [];
if (matchPattern($pattern, $context)) {
if (mb_strpos($pattern, "*") === false) return [];
@@ -132,19 +153,23 @@ function matchArgs($pattern, $context) {
} else return false;
}
function connectIsQQ(): bool {
function connectIsQQ(): bool
{
return ctx()->getConnection()->getName() == 'qq';
}
function connectIsDefault(): bool {
function connectIsDefault(): bool
{
return ctx()->getConnection()->getName() == 'default';
}
function connectIs($type): bool {
function connectIs($type): bool
{
return ctx()->getConnection()->getName() == $type;
}
function getAnnotations(): array {
function getAnnotations(): array
{
$s = debug_backtrace()[1];
//echo json_encode($s, 128|256);
$list = [];
@@ -159,9 +184,10 @@ function getAnnotations(): array {
return $list;
}
function set_coroutine_params($array) {
function set_coroutine_params($array)
{
$cid = Co::getCid();
if ($cid == -1) die(zm_internal_errcode("E00061")."Cannot set coroutine params at none coroutine mode.");
if ($cid == -1) die(zm_internal_errcode("E00061") . "Cannot set coroutine params at none coroutine mode.");
if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array);
else Context::$context[$cid] = $array;
foreach (Context::$context as $c => $v) {
@@ -172,14 +198,16 @@ function set_coroutine_params($array) {
/**
* @return ContextInterface|null
*/
function context(): ?ContextInterface {
function context(): ?ContextInterface
{
return ctx();
}
/**
* @return ContextInterface|null
*/
function ctx(): ?ContextInterface {
function ctx(): ?ContextInterface
{
$cid = Co::getCid();
$c_class = ZMConfig::get("global", "context_class");
if (isset(Context::$context[$cid])) {
@@ -194,39 +222,47 @@ function ctx(): ?ContextInterface {
}
}
function onebot_target_id_name($message_type): string {
function onebot_target_id_name($message_type): string
{
return ($message_type == "group" ? "group_id" : "user_id");
}
function zm_sleep($s = 1): bool {
function zm_sleep($s = 1): bool
{
if (Coroutine::getCid() != -1) System::sleep($s);
else usleep($s * 1000 * 1000);
return true;
}
function zm_exec($cmd): array {
function zm_exec($cmd): array
{
return System::exec($cmd);
}
function zm_cid() {
function zm_cid()
{
return Co::getCid();
}
function zm_yield() {
function zm_yield()
{
Co::yield();
}
function zm_resume(int $cid) {
function zm_resume(int $cid)
{
Co::resume($cid);
}
function zm_timer_after($ms, callable $callable) {
function zm_timer_after($ms, callable $callable)
{
Swoole\Timer::after($ms, function () use ($callable) {
call_with_catch($callable);
});
}
function call_with_catch($callable) {
function call_with_catch($callable)
{
try {
$callable();
} catch (Exception $e) {
@@ -240,7 +276,8 @@ function call_with_catch($callable) {
}
}
function zm_timer_tick($ms, callable $callable) {
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");
@@ -255,24 +292,39 @@ function zm_timer_tick($ms, callable $callable) {
}
}
function zm_go(callable $callable) {
function zm_go(callable $callable)
{
call_with_catch($callable);
}
function zm_data_hash($v): string {
function zm_data_hash($v): string
{
return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"]));
}
function server(): ?Server {
function server(): ?Server
{
return Framework::$server;
}
/**
* 获取缓存当前框架pid的临时目录
* @return string
*/
function _zm_pid_dir(): string
{
global $_ZM_HASH;
if (!isset($_ZM_HASH)) $_ZM_HASH = md5(DataProvider::getWorkingDir());
return '/tmp/.zm_' . $_ZM_HASH;
}
/**
* @return OneBotV11
* @throws RobotNotFoundException
* @throws ZMException
*/
function bot() {
function bot()
{
if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) {
return OneBotV11::getRandom();
} elseif ($conn != -1) {
@@ -289,7 +341,8 @@ function bot() {
* @return array
* @author 854854321
*/
function getAllFdByConnectType(string $type = 'default'): array {
function getAllFdByConnectType(string $type = 'default'): array
{
$fds = [];
foreach (ManagerGM::getAllByName($type) as $obj) {
$fds[] = $obj->getFd();
@@ -297,11 +350,13 @@ function getAllFdByConnectType(string $type = 'default'): array {
return $fds;
}
function zm_atomic($name): ?Atomic {
function zm_atomic($name): ?Atomic
{
return ZMAtomic::get($name);
}
function uuidgen($uppercase = false): string {
function uuidgen($uppercase = false): string
{
try {
$data = random_bytes(16);
} catch (Exception $e) {
@@ -313,11 +368,13 @@ function uuidgen($uppercase = false): string {
vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
function working_dir() {
function working_dir()
{
return WORKING_DIR;
}
function zm_dump($var, ...$moreVars) {
function zm_dump($var, ...$moreVars)
{
VarDumper::dump($var);
foreach ($moreVars as $v) {
@@ -331,26 +388,49 @@ function zm_dump($var, ...$moreVars) {
return $var;
}
function zm_info($obj) { Console::info($obj); }
function zm_info($obj)
{
Console::info($obj);
}
function zm_warning($obj) { Console::warning($obj); }
function zm_warning($obj)
{
Console::warning($obj);
}
function zm_success($obj) { Console::success($obj); }
function zm_success($obj)
{
Console::success($obj);
}
function zm_debug($obj) { Console::debug($obj); }
function zm_debug($obj)
{
Console::debug($obj);
}
function zm_verbose($obj) { Console::verbose($obj); }
function zm_verbose($obj)
{
Console::verbose($obj);
}
function zm_error($obj) { Console::error($obj); }
function zm_error($obj)
{
Console::error($obj);
}
function zm_config($name, $key = null) { return ZMConfig::get($name, $key); }
function zm_config($name, $key = null)
{
return ZMConfig::get($name, $key);
}
function quick_reply_closure($reply) {
function quick_reply_closure($reply)
{
return function () use ($reply) {
return $reply;
};
}
function zm_internal_errcode($code): string {
function zm_internal_errcode($code): string
{
return "[ErrCode:$code] ";
}

View File

@@ -24,6 +24,11 @@ try {
$scan_paths[trim($k, "\\")] = $base_path . "/" . $v;
}
}
foreach (($composer["autoload-dev"]["psr-4"] ?? []) as $k => $v) {
if (is_dir($base_path . "/" . $v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) {
$scan_paths[trim($k, "\\")] = $base_path . "/" . $v;
}
}
$all_event_class = [];
foreach ($scan_paths as $namespace => $autoload_path) {
$all_event_class = array_merge($all_event_class, ZMUtil::getClassesPsr4($autoload_path, $namespace));
@@ -61,3 +66,17 @@ try {
fclose($stderr);
exit(1);
}
/*
记迷惑,这里的代码是不是可以放到一个单独的文件里面,这样就不会出现每次都要重新加载的问题了?
然后这个文件就实现了,就是这个。
但是还有个什么问题呢?为了 reload 牺牲了太多太多,但是关键时刻好像又不是很能用到。
但又不能没有。
所以我很纠结很纠结。
如何让用户的代码能像 php-fpm 那样随时重置呢?
我不知道诶。
那这段代码干了个啥?
在最开始单独启动进程,加载一遍所有类,获取需要在启动前就执行的类,然后在启动的时候执行。
这样就可以不在爷进程里面加载所有类,在爹进程里面 Fork 的子进程再加载所有类,每次 reload 时可以重新加载了。
以上均为乱写的,请勿完全当真,本人对待框架代码还是比较认真的。
*/