mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-02 22:35:38 +08:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
176b690417 | ||
|
|
e22b1b90ec | ||
|
|
a3c560790c | ||
| 570e2108dd | |||
|
|
59c0d95e5d | ||
|
|
9ceaecdc02 | ||
|
|
19d50898ef | ||
|
|
5b62ca62ae | ||
| fb528d30ce | |||
| 5f2d5ed334 | |||
| c20e459900 | |||
| 09220825cf | |||
|
|
3d4db23d27 | ||
|
|
4496b67dcc | ||
|
|
2a13298384 | ||
|
|
d6ec404d76 | ||
|
|
3235fd4dc1 | ||
|
|
293740fee2 | ||
|
|
d3c420ec84 | ||
|
|
85ef09d43c | ||
|
|
e4561d69c4 | ||
|
|
6b4d206099 | ||
|
|
50843edf6a | ||
|
|
3a1686f8da | ||
|
|
66dd91bb97 | ||
|
|
e020e5d593 | ||
|
|
3d62663281 | ||
|
|
8d9485c02e | ||
|
|
9fb45dd683 | ||
|
|
8a4924dba9 | ||
|
|
beaf7be606 | ||
|
|
7dbd21bdf4 | ||
|
|
9ce3056203 |
38
.github/ISSUE_TEMPLATE/1_Bug_report.yaml
vendored
Normal file
38
.github/ISSUE_TEMPLATE/1_Bug_report.yaml
vendored
Normal 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: 其他可能有帮助的信息,如日志、截图等
|
||||
19
.github/ISSUE_TEMPLATE/2_Feature_request.yaml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/2_Feature_request.yaml
vendored
Normal 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: |
|
||||
一个简单的例子,展示该功能将如何被使用(包括代码、配置文件等)
|
||||
如果这是针对已有功能的改进,请展示改进前后使用方式(或效能)的对比
|
||||
10
README.md
10
README.md
@@ -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 等网站。
|
||||
|
||||

|
||||
感谢 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 中为项目开发规范化提出的一些建议。
|
||||
|
||||
<!--  -->
|
||||
|
||||
@@ -35,7 +35,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",
|
||||
|
||||
@@ -39,7 +39,9 @@ $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' => []
|
||||
];
|
||||
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
@@ -57,21 +59,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 +122,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(远程连接) */
|
||||
|
||||
@@ -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]"
|
||||
```
|
||||
9
docs/component/bot/robot-api-12.md
Normal file
9
docs/component/bot/robot-api-12.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# 机器人 API(OneBotV12)(待发布)
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
目前由于 OneBot 12 标准还没有定稿,处于草案阶段,故框架暂不更新。
|
||||
|
||||
在未来升级到 OneBot 12 标准后,框架会提供转换及兼容措施以及 12 版本的 API 方法。
|
||||
|
||||
见 [机器人动作(OneBot 11)](../robot-api)。
|
||||
@@ -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()` 等。
|
||||
|
||||
104
docs/component/common/event-tracer.md
Normal file
104
docs/component/common/event-tracer.md
Normal 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() - 启用事件跟踪器
|
||||
|
||||
还没写完,不着急。
|
||||
@@ -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()
|
||||
|
||||
184
docs/component/store/mysql/common-query.md
Normal file
184
docs/component/store/mysql/common-query.md
Normal 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)
|
||||
```
|
||||
|
||||
7
docs/component/store/mysql/config.md
Normal file
7
docs/component/store/mysql/config.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 配置
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `mysql_config` 段,见 [全局配置](../../../../guide/basic-config#mysql_config)。
|
||||
|
||||
如果 `mysql_config.host` 字段为空,则不创建数据库连接池,填写后将创建,且默认保持长连接。
|
||||
1
docs/component/store/mysql/mysql-statement.md
Normal file
1
docs/component/store/mysql/mysql-statement.md
Normal file
@@ -0,0 +1 @@
|
||||
你好啊,这里是 Statement。
|
||||
@@ -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 |
|
||||
|
||||
#
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
前面讲到 LightCache 轻量缓存在特定的情况下为了保证数据不被多进程的因素导致丢失或覆盖,在高并发情况下修改数据需要加锁,所以炸毛框架内置了 SpinLock 自旋锁。
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
框架单进程运行的模式下不需要任何自旋锁。
|
||||
|
||||
## 配置
|
||||
|
||||
自旋锁使用无需配置,和 LightCache 同源。
|
||||
|
||||
@@ -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 | 见配置文件 |
|
||||
@@ -56,6 +56,8 @@
|
||||
| `swoole_coroutine_hook_flags` | Swoole 启动时一键协程化 Hook 的 Flag 值,详见 [一键协程化](http://wiki.swoole.com/#/runtime?id=%e5%87%bd%e6%95%b0%e5%8e%9f%e5%9e%8b) | `SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)` |
|
||||
| `swoole_server_mode` | Swoole Server 启动的进程模式,有 `SWOOLE_PROCESS` 和 `SWOOLE_BASE` 两种,见 [启动方式](http://wiki.swoole.com/#/learn?id=swoole_process) | `SWOOLE_PROCESS` |
|
||||
| `middleware_error_policy` | 中间件错误处理策略,见 [中间件 - 错误处理策略](../../event/middleware/#_6) | 1 |
|
||||
| `reload_delay_time` | 框架 reload 重载命令接收后延迟的时间(毫秒,0 为不等待) | 800 |
|
||||
| `global_middleware_binding` | 给注解事件绑定全局中间件,见 [中间件 - 全局中间件](../../event/middleware/#_6) | `[]` |
|
||||
|
||||
### 子表 **light_cache**
|
||||
|
||||
@@ -73,17 +75,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 +108,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
|
||||
|
||||
|
||||
37
docs/guide/upgrade.md
Normal file
37
docs/guide/upgrade.md
Normal 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` 文件进行对比。
|
||||
@@ -4,6 +4,64 @@
|
||||
|
||||
同时此处将只使用 build 版本号进行区分。
|
||||
|
||||
## 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
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
|
||||
|
||||
## 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` 选项。
|
||||
|
||||
@@ -1,5 +1,65 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## 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
|
||||
@@ -421,4 +481,4 @@
|
||||
|
||||
> 更新时间:2020.12.23
|
||||
|
||||
已发布正式版。
|
||||
已发布正式版。
|
||||
|
||||
25
instant-test.php
Normal file
25
instant-test.php
Normal 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();
|
||||
48
mkdocs.yml
48
mkdocs.yml
@@ -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,54 @@ 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
|
||||
- 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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ 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 {
|
||||
|
||||
@@ -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)"),
|
||||
@@ -47,7 +49,24 @@ class RunServerCommand extends Command
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
|
||||
if (file_exists($pid_path)) {
|
||||
$pid = json_decode(file_get_contents($pid_path), true)["pid"] ?? null;
|
||||
if ($pid !== null && posix_getsid($pid) !== false) {
|
||||
$output->writeln("<error>检测到已经在 pid: $pid 进程启动了框架!</error>");
|
||||
$output->writeln("<error>不可以同时启动两个框架!</error>");
|
||||
return 1;
|
||||
} else {
|
||||
unlink($pid_path);
|
||||
}
|
||||
}
|
||||
(new Framework($input->getOptions()))->start();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function exportDefinition() {
|
||||
$cmd = new self();
|
||||
$cmd->configure();
|
||||
return $cmd->getDefinition();
|
||||
}
|
||||
}
|
||||
|
||||
25
src/ZM/Command/Server/ServerReloadCommand.php
Normal file
25
src/ZM/Command/Server/ServerReloadCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
34
src/ZM/Command/Server/ServerStatusCommand.php
Normal file
34
src/ZM/Command/Server/ServerStatusCommand.php
Normal 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;
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
}
|
||||
35
src/ZM/Command/Server/ServerStopCommand.php
Normal file
35
src/ZM/Command/Server/ServerStopCommand.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
class ServerStopCommand extends DaemonCommand
|
||||
{
|
||||
protected static $defaultName = 'server:stop';
|
||||
|
||||
protected function configure() {
|
||||
$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) {
|
||||
sleep(1);
|
||||
--$i;
|
||||
}
|
||||
if ($i === 0) {
|
||||
$output->writeln("<error>停止失败,请检查进程pid #" . $this->daemon_file["pid"] . " 是否响应!</error>");
|
||||
} else {
|
||||
$output->writeln("<info>成功停止!</info>");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -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 = 421;
|
||||
const VERSION = "2.5.4";
|
||||
const VERSION_ID = 429;
|
||||
const VERSION = "2.6.2";
|
||||
|
||||
/**
|
||||
* @throws InitException
|
||||
@@ -47,9 +49,10 @@ 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("WORKING_DIR", getcwd());
|
||||
if (Phar::running() !== "") {
|
||||
@@ -72,7 +75,7 @@ class ConsoleApplication extends Application
|
||||
$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";
|
||||
echo "成功添加!请运行 'composer dump-autoload'\n";
|
||||
exit(0);
|
||||
} else {
|
||||
echo zm_internal_errcode("E00006") . "添加失败!请按任意键继续!";
|
||||
@@ -90,6 +93,9 @@ class ConsoleApplication extends Application
|
||||
new DaemonReloadCommand(),
|
||||
new DaemonStopCommand(),
|
||||
new RunServerCommand(), //运行主服务的指令控制器
|
||||
new ServerStatusCommand(),
|
||||
new ServerStopCommand(),
|
||||
new ServerReloadCommand(),
|
||||
new PureHttpCommand(), //纯HTTP服务器指令
|
||||
new SystemdGenerateCommand()
|
||||
]);
|
||||
@@ -121,17 +127,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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"]) {
|
||||
|
||||
@@ -8,6 +8,7 @@ use Swoole\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
/**
|
||||
* Class OnShutdown
|
||||
@@ -18,5 +19,9 @@ class OnShutdown implements SwooleEvent
|
||||
{
|
||||
public function onCall(Server $server) {
|
||||
Console::verbose("正在关闭 Master 进程,pid=" . posix_getpid());
|
||||
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
|
||||
if (file_exists($pid_path)) {
|
||||
unlink($pid_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,13 +25,12 @@ 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);
|
||||
}
|
||||
$daemon_data = json_encode([
|
||||
"pid" => $server->master_pid,
|
||||
"stdout" => ZMConfig::get("global")["swoole"]["log_file"],
|
||||
"daemon" => (bool)Framework::$argv["daemon"]
|
||||
], 128 | 256);
|
||||
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ class OnWorkerStart implements SwooleEvent
|
||||
* @throws Exception
|
||||
*/
|
||||
private function loadAnnotations() {
|
||||
|
||||
if (Framework::$instant_mode) goto skip;
|
||||
//加载各个模块的注解类,以及反射
|
||||
Console::debug("Mapping annotations");
|
||||
$parser = new AnnotationParser();
|
||||
@@ -146,20 +146,6 @@ 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) {
|
||||
@@ -176,12 +162,23 @@ class OnWorkerStart implements SwooleEvent
|
||||
$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);
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
|
||||
@@ -76,9 +76,11 @@ 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"] ?? "");
|
||||
@@ -124,7 +126,9 @@ class Framework
|
||||
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
|
||||
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
|
||||
|
||||
$this->loadServerEvents();
|
||||
if ($instant_mode) {
|
||||
$this->loadServerEvents();
|
||||
}
|
||||
|
||||
$this->parseCliArgs(self::$argv, $add_port);
|
||||
|
||||
@@ -148,7 +152,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 +160,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 +186,7 @@ class Framework
|
||||
}
|
||||
|
||||
self::printProps($out, $tty_width, $args["log-theme"] === null);
|
||||
if ($args["preview"]) {
|
||||
if ($args["preview"] ?? false) {
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
18
src/ZM/Module/InstantModule.php
Normal file
18
src/ZM/Module/InstantModule.php
Normal 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;
|
||||
}
|
||||
}
|
||||
31
src/ZM/Module/ModuleBase.php
Normal file
31
src/ZM/Module/ModuleBase.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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,16 @@ 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"];
|
||||
});
|
||||
zm_dump("开始触发!", $data);
|
||||
$after->dispatchEvents($data);
|
||||
return $after;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $req
|
||||
* @throws Exception
|
||||
|
||||
@@ -7,11 +7,12 @@ 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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
59
src/ZM/ZMServer.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,19 @@ function getClassPath($class_name) {
|
||||
if (file_exists($dir2)) return $dir2;
|
||||
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)能分割函数
|
||||
@@ -331,19 +344,33 @@ 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) {
|
||||
return function () use ($reply) {
|
||||
|
||||
Reference in New Issue
Block a user