Compare commits

..

13 Commits
2.5.5 ... 2.5.7

Author SHA1 Message Date
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
40 changed files with 551 additions and 95 deletions

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 进行疑难解答。

View File

@@ -120,7 +120,7 @@ $config['static_file_server'] = [
$config['onebot'] = [
'status' => true,
'single_bot_mode' => false,
'message_level' => 99999
'message_level' => 99
];
/** 一个远程简易终端使用nc直接连接即可但是不建议开放host为0.0.0.0(远程连接) */

View File

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

@@ -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

@@ -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 | 见配置文件 |
@@ -73,17 +73,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**

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

@@ -4,6 +4,33 @@
同时此处将只使用 build 版本号进行区分。
## 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

@@ -1,5 +1,19 @@
# 更新日志v2 版本)
## 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

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,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

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

@@ -50,4 +50,10 @@ class RunServerCommand extends Command
(new Framework($input->getOptions()))->start();
return 0;
}
public static function exportDefinition() {
$cmd = new self();
$cmd->configure();
return $cmd->getDefinition();
}
}

View File

@@ -21,15 +21,14 @@ 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\Exception\InitException;
class ConsoleApplication extends Application
{
private static $obj = null;
const VERSION_ID = 422;
const VERSION = "2.5.5";
const VERSION_ID = 425;
const VERSION = "2.5.7";
/**
* @throws InitException
@@ -49,7 +48,8 @@ class ConsoleApplication extends Application
*/
public function initEnv($with_default_cmd = ""): ConsoleApplication {
if (defined("WORKDING_DIR")) throw new InitException();
$this->selfCheck();
_zm_env_check();
define("WORKING_DIR", getcwd());
if (Phar::running() !== "") {
@@ -121,17 +121,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

@@ -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

@@ -23,7 +23,11 @@ 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);
}

View File

@@ -7,7 +7,6 @@ namespace ZM\Event\SwooleEvent;
use Swoole\Process;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
@@ -23,10 +22,6 @@ 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);
}

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

@@ -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"]) {

View File

@@ -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();
@@ -182,7 +186,7 @@ class Framework
}
self::printProps($out, $tty_width, $args["log-theme"] === null);
if ($args["preview"]) {
if ($args["preview"] ?? false) {
exit();
}

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

@@ -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
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

@@ -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) {