mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-04 23:35:35 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5063421364 | |||
| 2b4d308783 | |||
| e2f49968b3 | |||
| 229778ebf9 | |||
|
|
d2c0972c93 | ||
|
|
d300b6e518 | ||
|
|
56cb7b2223 | ||
|
|
2fc42d5d60 | ||
|
|
999e90f709 | ||
|
|
45c6cd7d2a | ||
|
|
5aa0858021 | ||
|
|
e981da3932 | ||
|
|
2685be7306 | ||
|
|
a13c4628f5 | ||
|
|
b36417454c | ||
|
|
a6f5952dee | ||
|
|
d67dfe46f6 | ||
|
|
e57cc43500 | ||
|
|
481063285b | ||
|
|
d805523dbd | ||
|
|
58267f66fc | ||
|
|
48215f2e5e | ||
|
|
7e0fc1528a | ||
|
|
c185d20a93 | ||
|
|
7ec847e576 |
33
.github/workflows/main.yml
vendored
Normal file
33
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Docs Build
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Deploy docs to GitHub Pages
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CUSTOM_DOMAIN: framework.zhamao.me
|
||||
CONFIG_FILE: mkdocs.yml
|
||||
EXTRA_PACKAGES: build-base
|
||||
- name: Copy deployment to current folder
|
||||
run: |
|
||||
cp -r "${GITHUB_WORKSPACE}/site" "./deploy"
|
||||
- name: Deploy to Zhamao Server
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.ZHAMAO_XIN_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "deploy/"
|
||||
REMOTE_HOST: ${{ secrets.ZHAMAO_XIN_HOST }}
|
||||
REMOTE_USER: ${{ secrets.ZHAMAO_XIN_USER }}
|
||||
TARGET: ${{ secrets.ZHAMAO_XIN_TARGET }}
|
||||
@@ -7,6 +7,7 @@
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
|
||||
[](https://packagist.org/packages/zhamao/framework)
|
||||
[](https://github.com/howmanybots/onebot)
|
||||

|
||||
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=AnnotationBase)
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
|
||||
@@ -42,19 +43,19 @@ public function index() {
|
||||
## 开始
|
||||
如果你是初学者,可以直接使用以下脚本部署 PHP 环境和安装框架的脚手架:
|
||||
```bash
|
||||
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本
|
||||
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本 (仅限 x86_64 和 aarch64 平台)
|
||||
mkdir zhamao-app/
|
||||
cd zhamao-app/
|
||||
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
|
||||
|
||||
# 启动
|
||||
runtime/php vendor/bin/start server
|
||||
vendor/bin/start server
|
||||
```
|
||||
|
||||
## 文档(v2 版本)
|
||||
查看文档(国内自建):<https://docs-v2.zhamao.xin/>
|
||||
查看文档(国内自建):<https://framework.zhamao.xin/>
|
||||
|
||||
备用链接(国外托管):<https://docs-v2.zhamao.me/>
|
||||
备用链接(国外托管):<https://framework.zhamao.me/>
|
||||
|
||||
自行构建文档:`mkdocs build -d distribute`
|
||||
|
||||
|
||||
1
bin/phpunit-swoole
Executable file → Normal file
1
bin/phpunit-swoole
Executable file → Normal file
@@ -59,6 +59,7 @@ require PHPUNIT_COMPOSER_INSTALL;
|
||||
$starttime = microtime(true);
|
||||
go(function () {
|
||||
try {
|
||||
require_once __DIR__.'/../test/bootstrap.php';
|
||||
PHPUnit\TextUI\Command::main(false);
|
||||
} catch (Exception $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
|
||||
@@ -33,8 +33,9 @@
|
||||
"zhamao/request": "^1.1",
|
||||
"zhamao/connection-manager": "^1.0",
|
||||
"jelix/version": "^2.0",
|
||||
"league/climate": "^3.7",
|
||||
"psy/psysh": "^0.10.8"
|
||||
"league/climate": "^3.6",
|
||||
"psy/psysh": "@stable",
|
||||
"doctrine/orm": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
|
||||
@@ -55,4 +56,4 @@
|
||||
"swoole/ide-helper": "@dev",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,9 +34,12 @@ $config['swoole'] = [
|
||||
//'task_enable_coroutine' => true
|
||||
];
|
||||
|
||||
/** 一些框架与Swoole运行时设置的调整 */
|
||||
|
||||
/** 一些框架与框架运行时设置的调整 */
|
||||
$config['runtime'] = [
|
||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)
|
||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
||||
'swoole_server_mode' => SWOOLE_PROCESS,
|
||||
'middleware_error_policy' => 1
|
||||
];
|
||||
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
@@ -54,7 +57,7 @@ $config['worker_cache'] = [
|
||||
'transaction_timeout' => 30000
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
/** @deprecated 放弃使用,旧版数据库,请使用 mysql_config 和 doctrine/dbal 搭配使用 */
|
||||
$config['sql_config'] = [
|
||||
'sql_host' => '',
|
||||
'sql_port' => 3306,
|
||||
@@ -69,6 +72,22 @@ $config['sql_config'] = [
|
||||
'sql_default_fetch_mode' => PDO::FETCH_ASSOC //added in 1.5.6
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['mysql_config'] = [
|
||||
'host' => '',
|
||||
'port' => 3306,
|
||||
'unix_socket' => null,
|
||||
'username' => 'root',
|
||||
'password' => '123456',
|
||||
'dbname' => '',
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64,
|
||||
'options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
];
|
||||
|
||||
/** Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '',
|
||||
@@ -129,7 +148,7 @@ $config['remote_terminal'] = [
|
||||
|
||||
/** 模块(插件)加载器的相关设置 */
|
||||
$config['module_loader'] = [
|
||||
'enable_hotload' => true,
|
||||
'enable_hotload' => false,
|
||||
'load_path' => $config['zm_data'] . 'modules'
|
||||
];
|
||||
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
# 机器人 API(ZMRobot)
|
||||
# 机器人 API(OneBotV11)
|
||||
|
||||
ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
|
||||
OneBotV11 类是封装好的 OneBot 标准的 API 接口调用类,可以在机器人连接后通过连接或者机器人 QQ 号获取对象并调用接口(如发送群消息、获取群列表等操作)。
|
||||
|
||||
| 属性项 | 属性值 | 备注 |
|
||||
| -------- | ---------------- | ------------------------------ |
|
||||
| 名称 | ZMRobot | |
|
||||
| 类型 | 实例化类 | `$r = new ZMRobot($conn)` |
|
||||
| 命名空间 | `ZM\API\ZMRobot` | 使用前先 `use ZM\API\ZMRobot;` |
|
||||
| 属性项 | 属性值 | 备注 |
|
||||
| -------- | ------------------ | -------------------------------- |
|
||||
| 名称 | OneBotV11 | |
|
||||
| 类型 | 实例化类 | `$r = new OneBotV11($conn)` |
|
||||
| 命名空间 | `ZM\API\OneBotV11` | 使用前先 `use ZM\API\OneBotV11;` |
|
||||
| 别名 | `ZM\API\ZMRobot` | 此类目前是 `extends OneBotV11` |
|
||||
|
||||
> 你也可以继续使用 2.5 版本之前的别名类 `ZMRobot`,但未来框架将会优先兼容 OneBot V12 版本的协议,可能会造成更新问题,建议切换为 OneBotV11 类。
|
||||
|
||||
## 属性
|
||||
|
||||
对象属性方法是对 API 的调整,例如是否以 `_async`、`_rate_limited` 后缀发送 API、设置协程返回还是异步返回结果等。
|
||||
|
||||
### ZMRobot::API_NORMAL
|
||||
### OneBotV11::API_NORMAL
|
||||
|
||||
以默认(无后缀)方式请求 API。
|
||||
|
||||
### ZMRobot::API_ASYNC
|
||||
### OneBotV11::API_ASYNC
|
||||
|
||||
以后缀 `_async` 方式异步请求 API。
|
||||
|
||||
### ZMRobot::API_RATE_LIMITED
|
||||
### OneBotV11::API_RATE_LIMITED
|
||||
|
||||
以后缀 `_rate_limited` 方式请求 API。
|
||||
|
||||
@@ -30,7 +33,7 @@ ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机
|
||||
|
||||
设置后缀。目前支持 `_async`、`_rate_limited`。
|
||||
|
||||
- **prefix**: `int` `默认:API_NORMAL`,可选 `ZMRobot::API_NORMAL`、`ZMRobot::API_ASYNC`、`ZMRobot::API_RATE_LIMITED`
|
||||
- **prefix**: `int` `默认:API_NORMAL`,可选 `OneBotV11::API_NORMAL`、`OneBotV11::API_ASYNC`、`OneBotV11::API_RATE_LIMITED`
|
||||
|
||||
设置后缀后,请求的 API 会发生变化。例如发送私聊消息:`sendPrivateMsg()`,请求的 API 为 `send_private_msg_async`,详见 [OneBot 文档](https://github.com/howmanybots/onebot/blob/master/v11/specs/api/README.md)。
|
||||
|
||||
@@ -43,22 +46,22 @@ ZMRobot 类是封装好的 OneBot 标准的 API 接口调用类,可以在机
|
||||
获取当前对象的机器人 QQ 或 OneBot 实例的 ID。
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get(123456);
|
||||
$bot = OneBotV11::get(123456);
|
||||
echo $bot->getSelfId(); //123456
|
||||
```
|
||||
|
||||
### ZMRobot::get()
|
||||
### OneBotV11::get()
|
||||
|
||||
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 ZMRobot 对象。
|
||||
静态方法,用来通过机器人 QQ 或 OneBot 实例的 ID 获取 OneBotV11对象。
|
||||
|
||||
参数:`$robot_id`,必填。
|
||||
|
||||
```php
|
||||
$r = ZMRobot::get(123456);
|
||||
$r = OneBotV11::get(123456);
|
||||
$r->sendPrivateMsg(55555, "hello");
|
||||
```
|
||||
|
||||
### ZMRobot::getRandom()
|
||||
### OneBotV11::getRandom()
|
||||
|
||||
静态方法,随机获取一个连接到框架的机器人(多个机器人实例连接到框架时适用)。
|
||||
|
||||
@@ -66,21 +69,21 @@ $r->sendPrivateMsg(55555, "hello");
|
||||
|
||||
```php
|
||||
try {
|
||||
$bot = ZMRobot::getRandom();
|
||||
$bot = OneBotV11::getRandom();
|
||||
$bot->sendPrivateMsg(55555, "foo");
|
||||
} catch (\ZM\Exception\RobotNotFoundException $e) {
|
||||
echo "还没有机器人连接到框架!\n";
|
||||
}
|
||||
```
|
||||
|
||||
### ZMRobot::getAllRobot()
|
||||
### OneBotV11::getAllRobot()
|
||||
|
||||
获取所有连接到框架的机器人的 ZMRobot 对象。
|
||||
获取所有连接到框架的机器人的 OneBotV11 对象。
|
||||
|
||||
返回值:`ZMRobot[]`。
|
||||
返回值:`OneBotV11[]`。
|
||||
|
||||
```php
|
||||
$all = ZMRobot::getAllRobot();
|
||||
$all = OneBotV11::getAllRobot();
|
||||
foreach($all as $v) {
|
||||
$v->sendPrivateMsg(55555, "机器人轮流给一个人发消息啦!");
|
||||
}
|
||||
@@ -95,7 +98,7 @@ foreach($all as $v) {
|
||||
```php
|
||||
//从上下文获取 Websocket 连接对象
|
||||
$conn = ctx()->getConnection();
|
||||
$bot = new ZMRobot($conn);
|
||||
$bot = new OneBotV11($conn);
|
||||
```
|
||||
|
||||
## 返回结果处理
|
||||
@@ -103,7 +106,7 @@ $bot = new ZMRobot($conn);
|
||||
因为框架的机器人是兼容 OneBot 标准的(原 CQHTTP),所以每次接收发送 API 请求的结果都是大体一样的结构。我们以 `sendPrivateMsg()` 为例,因为发送出去的每一条消息都会在 OneBot 实例(如 CQHTTP 插件、go-cqhttp 等)中对应一个消息 ID,以供我们核查消息和后续撤回等操作需要。
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get("123456"); // 机器人QQ号
|
||||
$bot = OneBotV11::get("123456"); // 机器人QQ号
|
||||
$obj = $bot->sendGroupMsg("234567", "你好");
|
||||
echo json_encode($obj, 128|256);
|
||||
```
|
||||
@@ -163,7 +166,7 @@ vardump($result["retcode"]); //如果成功撤回,输出 int(0)
|
||||
=== "代码"
|
||||
|
||||
```php
|
||||
$bot = ZMRobot::get(123456); // 123456是你的机器人QQ
|
||||
$bot = OneBotV11::get(123456); // 123456是你的机器人QQ
|
||||
$bot->sendPrivateMsg("627577391", "你好啊!你好你好!");
|
||||
```
|
||||
|
||||
|
||||
181
docs/component/module/module-pack.md
Normal file
181
docs/component/module/module-pack.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 模块打包
|
||||
|
||||
从 2.5 版本起,炸毛框架的模块源码支持了打包和分发,开发者可以通过将自己的功能编写打包,并通过互联网进行分发,供其他人使用。此外,还提供了模块包热加载(不解包直接运行)和模块包解包功能。
|
||||
|
||||
## 构建模块包配置文件
|
||||
|
||||
炸毛框架的模块区分是根据 `src` 目录下的文件夹定义的,模块包的配置文件命名必须为 `zm.json`,此外,假设我们编写了一个最简单的模块,以脚手架生成的 Example 模块为例,文件夹结构如下:
|
||||
|
||||
```
|
||||
src/
|
||||
└── Module/
|
||||
├── Example/
|
||||
│ ├── Hello.php
|
||||
│ └── zm.json
|
||||
└── Middleware/
|
||||
└── TimerMiddleware.php
|
||||
|
||||
```
|
||||
|
||||
我们在 Example 目录下创建一个 `zm.json` 的文件,编写配置,即代表 `src/Module/Example/` 文件夹及里面的用户模块源码为一个模块包,也就可以被框架识别并打包处理。
|
||||
|
||||
编写的配置文件结构如下:
|
||||
|
||||
```
|
||||
{
|
||||
"name": "my-first-module"
|
||||
}
|
||||
```
|
||||
|
||||
对!你没看错,只需要定义一个 `name` 字段,即可声明这是一个模块包!
|
||||
|
||||
### 配置文件参数
|
||||
|
||||
#### - description
|
||||
|
||||
- 类型:`string`。
|
||||
|
||||
- 含义:模块的描述。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-first-module",
|
||||
"description": "这个是一个示例模块打包教程"
|
||||
}
|
||||
```
|
||||
|
||||
#### - version
|
||||
|
||||
- 类型:string。
|
||||
- 含义:模块的版本。
|
||||
|
||||
版本处理方式和 Composer 基本一致,建议使用三段式,也就是 `大版本.小版本.补丁版本`。关于三段式版本的描述和规范,见 [到底三段式版本号是什么?](https://www.chrisyue.com/what-the-hell-are-semver-and-the-difference-between-composer-version-control-sign-tilde-and-caret.html)。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "my-first-module",
|
||||
"description": "这个是一个示例模块打包教程"
|
||||
}
|
||||
```
|
||||
|
||||
#### - depends
|
||||
|
||||
- 类型:map of string(例如 `{"foo":"bar","baz":"zoo"}`)。
|
||||
- 含义:模块的依赖关系和版本依赖声明。
|
||||
|
||||
此处用作模块的依赖检测,假设模块 `foo` 依赖模块 `bar` 的 1.x 版本但是不兼容 `bar` 的 2.x 版本,可以像 Composer 的 `require` 一样编写版本依赖:`^1.0`。也可以使用 `~`、`>=`、`*` 这些与 Composer 包管理相同逻辑的版本依赖关系,详见 [Composer - 包版本](https://docs.phpcomposer.com/01-basic-usage.html#Package-Versions)。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"depends": {
|
||||
"bar": "^1.0",
|
||||
"bsr": "*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### - light-cache-store
|
||||
|
||||
- 类型:array of string(例如 `["foo","bar"]`)。
|
||||
- 含义:打包模块时要储存的持久化 LightCache 键名列表。
|
||||
|
||||
这里需要配合 LightCache 使用,如果你有一些需要全局缓存的数据,例如动态配置项,比如群服务状态列表,可以先使用 LightCache 存储并使用 `addPersistence()` 持久化,此后在使用模块打包时编写此配置项。
|
||||
|
||||
我们假设在项目模块中使用到了 `group-status` 这一个 LightCache,那么只需要写 `light-cache-store` 配置项,在模块打包时就会将持久化的数据也打包到 phar 模块包内。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"light-cache-store": [
|
||||
"group-status"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### - global-config-override
|
||||
|
||||
- 类型:string | false。
|
||||
- 含义:解包时是否需要手动编辑全局配置(`global.php`)。
|
||||
|
||||
这里如果写 string 类型的,那么就是相当于在解包时会提示此处的内容,内容推荐填写要求解包模块用户需要编辑的项目,比如 「请将 static_file_server 的 status 改为 true,以便使用静态文本功能」。
|
||||
|
||||
如果是 false,那么和不指定此参数效果是一样的,无需用户修改 global.php。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"global-config-override": "请将 static_file_server 的 status 改为 true"
|
||||
}
|
||||
```
|
||||
|
||||
#### - allow-hotload
|
||||
|
||||
- 类型:bool。
|
||||
- 含义:是否允许用户无需解压直接加载模块包文件(phar)。
|
||||
|
||||
当此项为 true 时,可以将模块包直接放入 `zm_data/modules` 文件夹下,然后将 `global.php` 中的 `module_loader` 项中的 `enable_hotload` 改为 true,启动框架即可加载。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"allow-hotload": true
|
||||
}
|
||||
```
|
||||
|
||||
!!! warning "注意"
|
||||
如果使用允许热加载,那么模块包中的配置最好不要有 `global-config-override` 和 `light-cache-store`,以此来达到最正确的效果,一般热加载更适合 Library(库)类型的模块。
|
||||
|
||||
#### - zm-data-store
|
||||
|
||||
- 类型:array of string(例如 `["foo","bar"]`)。
|
||||
- 含义:打包时要添加到模块包内的 `zm_data` 目录下的子目录或文件。
|
||||
|
||||
其中项目必须是相对路径,不能是绝对路径,且必须是在配置项 `zm_data` 指定的目录(默认会在框架项目的根目录下的 `zm_data/` 目录。
|
||||
|
||||
我们假设要打包一个 `{zm_data 目录}/config/` 目录及其目录下的文件,和一个 `main.png` 文件,下方是实例。
|
||||
|
||||
??? note "点我查看编写实例:"
|
||||
```json
|
||||
{
|
||||
"name": "foo",
|
||||
"description": "这个是一个示例模块打包教程",
|
||||
"zm-data-store": [
|
||||
"config/",
|
||||
"main.png"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
在打包时框架会自动添加这些文件到 phar 插件包内,到解包时,会自动将这些文件释放到对应框架的 `zm_data` 目录下。
|
||||
|
||||
## 打包命令
|
||||
|
||||
```bash
|
||||
# ./zhamao 和原先的 vendor/bin/start 是完全一致的
|
||||
./zhamao module:pack <module-name>
|
||||
```
|
||||
|
||||
例如,打包上面的名叫 foo 的模块:`./zhamao module:pack foo`。
|
||||
|
||||
打包命令执行后,将会在 `zm_data` 下的 `output` 目录输出一个 phar 文件。如果你指定了 `version` 参数,那么文件名将会是 `${name}_${version}.phar`,如果没有指定版本,那么只会有 `${name}.phar`,同时如果文件已经存在,将覆盖写入。
|
||||
|
||||
## 查看模块信息命令
|
||||
|
||||
```bash
|
||||
./zhamao module:list
|
||||
```
|
||||
|
||||
通过此命令可以查看模块相关的信息,如未打包但已配置的模块信息等。
|
||||
|
||||
84
docs/component/module/module-unpack.md
Normal file
84
docs/component/module/module-unpack.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# 模块解包
|
||||
|
||||
从 2.5 版本起,炸毛框架的模块源码支持了打包和分发,分发后必不可少的一步就是将其解包。
|
||||
|
||||
解包过程大致为:
|
||||
|
||||
1. 检查模块的配置文件是否正常。
|
||||
2. 检查模块的依赖问题,如果有依赖但未安装,则抛出异常。
|
||||
3. 检查 LightCache 轻量缓存是否需要写入。
|
||||
4. 检查 `zm_data` 是否有需要存入的数据。
|
||||
5. 合并 `composer.json` 文件。
|
||||
6. 拷贝 `zm_data` 相关的文件。
|
||||
7. 写入 LightCache 相关数据。
|
||||
8. 提示用户手动合并 `global.php` 全局配置文件。
|
||||
9. 拷贝模块 PHP 源文件。
|
||||
|
||||
## 解包命令
|
||||
|
||||
```bash
|
||||
./zhamao module:unpack <module-name>
|
||||
```
|
||||
|
||||
首先将待解包的 phar 文件放入 `zm_data` 目录下的 `modules` 文件夹(如果不存在需要手动创建),如果你手动修改过 `global.php` 下面的 `module_loader.load_path` 项,需要放入对应的目录。
|
||||
|
||||
放入后,结构如下:
|
||||
|
||||
```
|
||||
zm_data/
|
||||
zm_data/modules/
|
||||
zm_data/modules/foo.phar
|
||||
```
|
||||
|
||||
接下来,需要知道模块的名称。当然一般情况下,phar 的名称可以获取到模块的实际名称,如 `foo`,但最好用 `./zhamao module:list` 列出模块的信息来获取真实的模块名称。
|
||||
|
||||
```
|
||||
./zhamao module:list
|
||||
# 下面是输出
|
||||
[foo]
|
||||
类型: 模块包(phar)
|
||||
位置: zm_data/modules/我是假的名字.phar
|
||||
```
|
||||
|
||||
解包过程十分简单,只需要执行一次命令即可。
|
||||
|
||||
```
|
||||
./zhamao module:unpack foo
|
||||
# 下面是输出
|
||||
[10:05:40] [I] Releasing source file: src/Module/Example/Hello.php
|
||||
[10:05:40] [I] Releasing source file: src/Module/Example/zm.json
|
||||
[10:05:40] [S] 解压完成!
|
||||
```
|
||||
|
||||
### 命令参数
|
||||
|
||||
在解包时会遇到各种复杂的情况,如源码文件已存在、数据已存在、依赖问题等,通过增加参数可以控制解包时的行为。
|
||||
|
||||
#### --overwrite-light-cache
|
||||
|
||||
含义:覆盖现有的 LightCache 键值(如果存在的话)。
|
||||
|
||||
#### --overwrite-zm-data
|
||||
|
||||
含义:覆盖现有的 `zm_data` 下的文件(如果存在的话)。
|
||||
|
||||
#### --overwrite-source
|
||||
|
||||
含义:覆盖现有的 PHP 模块源文件(如果存在的话)。
|
||||
|
||||
#### --ignore-depends
|
||||
|
||||
含义:解包时忽略检查依赖。
|
||||
|
||||
### 常见问题
|
||||
|
||||
如果你解包的模块包要求修改 `global.php`,则会出现类似这样的提示:
|
||||
|
||||
```
|
||||
# ./zhamao module:unpack foo
|
||||
[14:47:39] [W] 模块作者要求用户手动修改 global.php 配置文件中的项目:
|
||||
[14:47:39] [W] *请把全局配置文件的light_cache项目中max_strlen选项调整为至少65536
|
||||
请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N]
|
||||
```
|
||||
|
||||
一般这种情况,根据第二条提示(第二条提示为打包时填入的 `global-config-override`)。如果输入 y,则会自动执行命令 `vim config/global.php`,如果输入的是 e,则会等待你手动修改完成文件,最后按回车完成修改。默认情况直接回车的话,会跳过此步骤,如果模块要求了修改但跳过修改,安装后可能会有功能缺失等问题。
|
||||
101
docs/component/store/mysql-db.md
Normal file
101
docs/component/store/mysql-db.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# MySQL 数据库(旧版组件)
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
此 MySQL 组件为旧版 MySQL 查询器组件,为了统一和提升对未来独立组件的兼容性,现转变为使用 `doctrine/dbal` 和 `doctrine/orm` 库来实现查询器,请转到 [MySQL 查询器]()。
|
||||
|
||||
## 配置
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
|
||||
|
||||
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类。
|
||||
|
||||
## 查询构造器
|
||||
|
||||
在 炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串。
|
||||
|
||||
### 新增数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
|
||||
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
|
||||
```
|
||||
|
||||
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name`,`password` 两列。
|
||||
|
||||
> 自增 ID 插入 0 即可。
|
||||
|
||||
### 删除数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->delete()->where("name", "admin_name")->save();
|
||||
// DELETE FROM admin WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=`,`!=`,`LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`。
|
||||
|
||||
### 更新数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
|
||||
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`。
|
||||
|
||||
### 查询数据
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
|
||||
// SELECT name FROM admin WHERE name = 'fake_admin'
|
||||
echo $r["name"];
|
||||
echo DB::table("admin")->where("name", "fake_admin")->value("name");
|
||||
// SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
|
||||
|
||||
其中 `fetchFirst()` 获取第一行数据。
|
||||
|
||||
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
|
||||
|
||||
多列数据获取需要使用 `fetchAll()`
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select()->fetchAll();
|
||||
// SELECT * FROM admin WHERE 1
|
||||
foreach($r as $k => $v) {
|
||||
echo $v["name"].PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### 查询条数
|
||||
|
||||
```php
|
||||
DB::table("admin")->where("name", "fake_admin")->count();
|
||||
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 直接执行 SQL
|
||||
|
||||
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
|
||||
|
||||
### DB::rawQuery()
|
||||
|
||||
- 用途:直接执行模板查询的裸 SQL 语句。
|
||||
- 参数:`$line`,`$params`
|
||||
- 返回:查到的行的数组
|
||||
|
||||
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
|
||||
|
||||
```php
|
||||
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
|
||||
//SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
echo $r[0]["password"];
|
||||
```
|
||||
|
||||
> 参数查询已经从根本上杜绝了 SQL 注入的问题。
|
||||
@@ -1,97 +1,10 @@
|
||||
# MySQL 数据库
|
||||
|
||||
## 配置
|
||||
## 简介
|
||||
|
||||
炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。
|
||||
炸毛框架的数据库组件对接了 MySQL 连接池,在使用过程中无需配置即可实现 MySQL 查询,同时拥有高并发。
|
||||
|
||||
数据库的配置位于 `config/global.php` 文件的 `sql_config` 段。
|
||||
目前 2.5 版本后炸毛框架底层采用了 `doctrine/dbal` 组件,可以方便地构建 SQL 语句。
|
||||
|
||||
数据库操作的唯一核心工具类和功能类为 `\ZM\DB\DB`,使用前需要配置 host 和 use 此类。
|
||||
> 文档正在加急编写!!!用的话可以先用 `MySQLManager::getWrapper()` 获取 wrapper 后返回的方法。
|
||||
|
||||
## 查询构造器
|
||||
|
||||
在 炸毛框架 中,数据库查询构造器为创建和执行数据库查询提供了一个方便的接口,它可用于执行应用程序中大部分数据库操作。同时,查询构造器使用 `prepare` 预处理来保护程序免受 SQL 注入攻击,因此没有必要转义任何传入的字符串。
|
||||
|
||||
### 新增数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->insert(['admin_name', 'admin_password'])->save();
|
||||
// INSERT INTO admin VALUES ('admin_name', 'admin_password')
|
||||
```
|
||||
|
||||
其中 `insert` 的参数是插入条目的数据列表。假设 admin 表有 `name`,`password` 两列。
|
||||
|
||||
> 自增 ID 插入 0 即可。
|
||||
|
||||
### 删除数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->delete()->where("name", "admin_name")->save();
|
||||
// DELETE FROM admin WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
其中 `where` 语句的第一个参数为列名,当只有两个参数时,第二个参数为值,效果等同于 SQL 语句:`WHERE name = 'admin_name'`,当含有第三个参数且第二个参数为 `=`,`!=`,`LIKE` 的时候,效果就是 `WHERE 第一个参数 第二个参数的操作符 第三个参数`。
|
||||
|
||||
### 更新数据
|
||||
|
||||
```php
|
||||
DB::table("admin")->update(["name" => "fake_admin"])->where("name", "admin_name")->save();
|
||||
// UPDATE admin SET name = 'fake_admin' WHERE name = 'admin_name'
|
||||
```
|
||||
|
||||
`update()` 方法中是要 SET 的内容的键值对,例如上面把 `name` 列的值改为 `fake_admin`。
|
||||
|
||||
### 查询数据
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select(["name"])->where("name", "fake_admin")->fetchFirst();
|
||||
// SELECT name FROM admin WHERE name = 'fake_admin'
|
||||
echo $r["name"];
|
||||
echo DB::table("admin")->where("name", "fake_admin")->value("name");
|
||||
// SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
`select()` 方法的参数是要查询的列,默认留空为 `["*"]`,也就是所有列都获取,也可以在 table 后直接 where 查询。
|
||||
|
||||
其中 `fetchFirst()` 获取第一行数据。
|
||||
|
||||
如果只需获取一行中的一个字段值,也可以通过上面所示的 `value()` 方法直接获取。
|
||||
|
||||
多列数据获取需要使用 `fetchAll()`
|
||||
|
||||
```php
|
||||
$r = DB::table("admin")->select()->fetchAll();
|
||||
// SELECT * FROM admin WHERE 1
|
||||
foreach($r as $k => $v) {
|
||||
echo $v["name"].PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### 查询条数
|
||||
|
||||
```php
|
||||
DB::table("admin")->where("name", "fake_admin")->count();
|
||||
//SELECT count(*) FROM admin WHERE name = 'fake_admin'
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 直接执行 SQL
|
||||
|
||||
> 在查询器外执行的 SQL 语句都不会被缓存,都是一定会请求数据库的。
|
||||
|
||||
### DB::rawQuery()
|
||||
|
||||
- 用途:直接执行模板查询的裸 SQL 语句。
|
||||
- 参数:`$line`,`$params`
|
||||
- 返回:查到的行的数组
|
||||
|
||||
`$line` 为请求的 SQL 语句,`$params` 为模板参数。
|
||||
|
||||
```php
|
||||
$r = DB::rawQuery("SELECT * FROM admin WHERE name = ?", ["fake_admin"]);
|
||||
//SELECT * FROM admin WHERE name = 'fake_admin'
|
||||
echo $r[0]["password"];
|
||||
```
|
||||
|
||||
> 参数查询已经从根本上杜绝了 SQL 注入的问题。
|
||||
@@ -165,3 +165,31 @@ public function onThrowing(?Exception $e) {
|
||||
|
||||
`ctx()` 为获取当前协程空间绑定的 `request` 和 `response` 对象。
|
||||
|
||||
## 中间件加载错误处理策略
|
||||
|
||||
中间件在某些情况下可能会产生普通 PHP 异常以外的异常,不能被框架的正常错误流程捕获,所以这里额外说明了中间件异常处理的几种策略。
|
||||
|
||||
中间件异常处理策略可以在 2.5.2 版本之后通过 `global.php` 中的 `runtime` 下 `middleware_error_policy` 设置。
|
||||
|
||||
- **0**: 无论被运行事件中间件是否存在,都不抛出异常,继续执行事件。
|
||||
- **1**: 在框架启动时如果某事件被注解了一个不存在的中间件,则不抛出异常,在执行期间才检测是否存在此中间件,并抛出异常。
|
||||
- **2**: 严格的中间件检查,在框架启动时就检测所有被注解了中间件的注解事件。
|
||||
|
||||
假设我们有一个路由注解 `@RequestMapping("/test")`,同时注解了一个不存在的中间件,如下:
|
||||
|
||||
```php
|
||||
/**
|
||||
* @RequestMapping("/test")
|
||||
* @Middleware("foo")
|
||||
*/
|
||||
public function testRoute() {
|
||||
return "I am testing middleware";
|
||||
}
|
||||
```
|
||||
|
||||
配置项为 0,此中间件类不存在的话,则会报告 warning,并直接执行此函数。
|
||||
|
||||
配置项为 1,在访问此路由执行此函数时会抛出异常,中断此次事件。
|
||||
|
||||
配置项为 2,在框架启动时抛出致命异常。
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# FAQ
|
||||
|
||||
这里会写一些常见的疑难解答,点击左侧问题名称打开对应解决方法。
|
||||
|
||||
如果框架运行过程中发现带有错误码(如 `E00034` 的形式),可以到 [错误码](/guide/errcode) 查看。
|
||||
|
||||
框架的常见问题见 [常见问题汇总](/faq/usual-question)。
|
||||
57
docs/faq/usual-question.md
Normal file
57
docs/faq/usual-question.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# 框架常见问题(持续更新)
|
||||
|
||||
## 如何正确地强制退出炸毛框架?
|
||||
|
||||
首先要知道一个概念,炸毛框架和传统的 PHP 以及其他如 Python 等语言的轻量框架都不同,框架启动后会依次启动 Master、Manager、Worker 等多个进程,而用户启动时入口的 PHP 进程就是 Master 进程,在一些对框架的正常中止、热重启上,我们给 Master 进程发送相应的 Linux 信号(如 SIGTERM)即可对整个框架的多个进程生效,无需给每个进程发送。
|
||||
|
||||
但是如果因为用户的误操作,导致炸毛框架其中的一个或多个进程阻塞,或者比如将框架挂在 screen 等守护但是守护服务进程被杀掉,总之就是无法使用 Ctrl+C 的方式正常关闭框架,这时就需要正确地杀掉所有框架进程(这固然可能会造成内存的缓存数据丢失)。
|
||||
|
||||
!!! warning "注意"
|
||||
|
||||
下方涉及 `ps` 命令后使用 `grep` 过滤的框架进程方式,如果你的服务器同时有其他使用 PHP 启动的服务,命令行刚好有 `server` 字样,可能会导致误杀,如果有影响的话,建议将 `grep server` 换成你启动时命令行的特殊参数或手动排除!
|
||||
|
||||
**一、**首先,使用 `ps`、`htop`、`netstat -nlp` 等命令确定框架的入口进程(也就是 Master 进程的 pid)。
|
||||
|
||||
确认方式示例如下:
|
||||
|
||||
- 如果你使用的是 >=2.4 版本的框架,在框架启动时就会在最先开始的 motd 上方显示 `master_pid`,如果你还能找到此处的显示,那么恭喜你,可以直接进行下面的第二步。
|
||||
- 如果你不能正常通过框架的方式找到 pid,可以通过命令 `ps aux | grep php | grep server` 的方式找到框架所有的进程。其中列出的相关框架的进程,可以寻找 pid 最小的进程,即为 Master 进程。关于如何区分进程对应关系,见本页 [使用 Linux 工具辨别框架进程]()。
|
||||
- 如果你对 `ps` 不熟悉,可以使用 `htop` 工具,使用 `F5 Tree` 方式显示,并且使用 `F4` 的 Filter,过滤 `php` 或 `bin/start` 等字样,找到进程树。
|
||||
|
||||
**二、**然后,确定框架是否正常运行且正常流程关闭。
|
||||
|
||||
如果框架能正常运行,比如可以通过访问浏览器的 `http://地址:端口/httpTimer` 等 HTTP 路由,可以使用 `SIGINT` 或 `SIGTERM` 信号正常关闭框架。我们假设 Master 进程的 pid 为 31234:`kill -TERM 31234` 或 `kill -INT 31234`,如果稍后使用 `ps aux | grep php | grep server` 命令发现没有进程存在(排除掉 grep 自身的进程),说明可以正常关闭,此关闭方法为正常停止流程,即保存了 `LightCache` 等内存缓存持久化的数据。
|
||||
|
||||
如果以上方式没有任何效果,继续看第三步。
|
||||
|
||||
**三、**不能正常流程关闭,需要手动杀掉所有进程。
|
||||
|
||||
首先使用 `ps aux | grep php | grep server | grep -v grep | awk '{print $2}'` 列出框架所有进程的 pid,确认无误后,在此条命令后接 `| xargs kill -9` 即可:
|
||||
|
||||
```bash
|
||||
# 列出进程,只显示包含php,只显示包含server,排除grep本身进程,显示第二列的pid,使用xargs循环kill这里面的进程
|
||||
ps aux | grep php | grep server | grep -v grep | awk '{print $2}' | xargs kill -9
|
||||
```
|
||||
|
||||
## 如何使用 Linux 工具查看框架进程状态?
|
||||
|
||||
框架有多个进程,有时候我们需要通过监视进程状态来确定框架是否正常运行或查看框架的资源占用率。首先一个大概念,老生常谈,炸毛框架由 Master、Manager、Worker(、TaskWorker)进程组成的。
|
||||
|
||||
如果使用 htop 工具,就比较简单,比如我启动了一个应用,使用炸毛框架编写的垃圾分类小程序 API 服务器,在 htop 命令后找到如图这部分(下面的树状图是按 F5 后切换为树状显示,避免进程刷太快可以输入 `Shift+z`):
|
||||
|
||||

|
||||
|
||||
其中,`-zsh` 下有唯一一个 php 进程,在图中对应的第一列 pid 为 `16258`,代表 Master 进程。
|
||||
|
||||
Master 进程下的唯一一个子进程(白色的是进程,绿色是线程),在图中对应的 pid 为 `16263`,代表 Manager 进程,用作管理 Worker 进程。
|
||||
|
||||
Manager 进程下的子进程,连号部分为对应的 Worker 进程,比如图中的 `16266`,`16267`,`16268`,`16269` 分别代表 `Worker #0`,`Worker #1`,`Worker #2`,`Worker #3` 四个 Worker 进程。
|
||||
|
||||
如果你还设置了 TaskWorker 进程,TaskWorker 进程的 pid 会和 Worker 进程一样是连续的,一般会接在 Worker 进程后面。
|
||||
|
||||
`htop` 使用方向键选择进程,选择到对应进程后可以使用 `F9` 来选择 kill 指令,比如让框架热重启,可以将光标移到 Master 进程上,使用 `SIGUSR1`:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
|
||||
| `config_dir` | 存放 saveToJson() 方法保存的数据的目录 | `zm_data` 下的 `config/` |
|
||||
| `swoole` | 对应 Swoole server 中 set 的参数,参考Swoole文档 | 见子表 `swoole` |
|
||||
| `runtime` | 一些框架运行时调整的设置 | 见子表 `runtime` |
|
||||
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
|
||||
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
|
||||
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
|
||||
@@ -34,6 +35,7 @@
|
||||
| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表,在 Swoole 服务器启动前就被加载 | 空 |
|
||||
| `onebot` | OneBot 协议相关配置 | 见子表 `onebot` |
|
||||
| `remote_terminal` | 远程终端相关配置 | 见子表 `remote_terminal` |
|
||||
| `module_loader` | 模块/插件 加载相关配置 | 见子表 `module_loader` |
|
||||
|
||||
### 子表 **swoole**
|
||||
|
||||
@@ -47,6 +49,14 @@
|
||||
| `task_worker_num` | TaskWorker 工作进程数 | 默认不开启(此参数被注释) |
|
||||
| `task_enable_coroutine` | TaskWorker 工作进程启用协程 | 默认不开启(此参数被注释)或 `bool` |
|
||||
|
||||
### 子表 runtime
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| ----------------------------- | ------------------------------------------------------------ | --------------------------------------- |
|
||||
| `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 |
|
||||
|
||||
### 子表 **light_cache**
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
@@ -110,6 +120,13 @@
|
||||
| `port` | 远程终端监听的 TCP 端口 | 20002 |
|
||||
| `token` | 远程终端连接的令牌(如果为空("")则不验证) | "" |
|
||||
|
||||
### 子表 module_loader
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | ------------------------------------------------------------ | ----------- |
|
||||
| `enable_hotload` | 是否开启热加载模块包的功能 | false |
|
||||
| `load_path` | 模块包加载的目录地址 | `zm_data` 下的 `modules` |
|
||||
|
||||
## 多环境下的配置文件
|
||||
|
||||
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(development / staging / production)来编写配置文件。
|
||||
|
||||
74
docs/guide/errcode.md
Normal file
74
docs/guide/errcode.md
Normal file
@@ -0,0 +1,74 @@
|
||||
| 异常码 | 含义 | 解决方案 |
|
||||
| ------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
|
||||
| E00001 | 炸毛框架未检测到 PHP 安装了 Swoole 扩展 | 根据文档安装扩展去! |
|
||||
| E00002 | Swoole 扩展安装的版本过低 | 升级 Swoole 版本,最好为最新版。 |
|
||||
| E00003 | PHP 版本过低 | 升级 PHP 版本,至少为 7.2。 |
|
||||
| E00004 | Swoole 版本低于 4.6.7 且未安装 pcntl 扩展 | 安装 pcntl 扩展或升级 Swoole 至少为4.6.7。 |
|
||||
| E00005 | 在框架命令行解析过程中出现了致命错误 | 请根据提示的错误位置进行调试和修复,如果未解决请将问题反馈给作者。 |
|
||||
| E00006 | 炸毛框架在源码模式启动时未能修改 composer.json 文件 | 检查源码模式下 composer.json 文件是否正常可写可读。 |
|
||||
| E00007 | 框架在启动时未找到 global.php 全局配置文件 | 如果是使用 `composer create-project` 或用 git 来克隆 starter 仓库的,需要先使用 `vendor/bin/start init` 指令,再启动服务器。 |
|
||||
| E00008 | 框架在启动时给用于存储连接数据的共享内存表初始化失败 | 请检查系统内存是否过小,如果一切正常,此问题一般是框架内部导致的问题,请将错误日志反馈给开发者。 |
|
||||
| E00009 | 使用 `--remote-terminal` 时远程终端处理命令出现异常或致命错误 | 检查自身的远程终端是否正确配置和使用,自定义的 `@TerminalCommand` 注解是否抛出了致命错误。 |
|
||||
| E00010 | 框架在第一步的启动阶段抛出异常或致命错误,导致框架不能继续运行 | 此错误涵盖的错误内容较多,请根据实际抛出的异常内容进行处理或反馈给开发者。<br />如果你使用了 `@SwooleHandler` 或 `@OnSetup` 注解,那么可以自行检查一下注解绑定的函数有没有出错。 |
|
||||
| E00011 | 框架在调用 Swoole 服务器启动 `$server->start()` 过程中出现了异常 | 此问题未经测试,暂无解决方案,也没有遇到过,如果有发生,请将错误反馈开发者。 |
|
||||
| E00012 | 框架在启动时调用脚本解析 `@SwooleHandler` 和 `@OnSetup` 时出现了异常 | 留个坑,下次写,TODO。 |
|
||||
| E00013 | 使用命令行参数动态设置启动的 Worker/TaskWorker 进程数时输入了非法的数字 | 填写合法的数字或不使用此功能。 |
|
||||
| E00014 | 炸毛框架的启动命令报错,提示没有找到 PHP 环境 | 使用 `./install-runtime.sh` 命令安装便携的静态 PHP 环境或根据教程和 Linux 发行版安装环境。 |
|
||||
| E00015 | 启动命令启动框架找不到框架本体的入口文件 | 请检查 Composer 拉取的框架代码是否完整。 |
|
||||
| E00016 | 连接中断后 `@OnCloseEvent` 事件抛出异常 | 检查 `@OnCloseEvent` 或 `@OnSwooleEvent("close")` 注解事件。 |
|
||||
| E00017 | 框架作为 WebSocket 服务器收到客户端数据后 `@OnMessageEvent`、`@OnSwooleEvent("message")` 或 OneBot 相关事件抛出了未被捕获的异常或错误 | 检查 `@OnSwooleEvent("message")`、`@OnMessageEvent` 或 OneBot 相关注解事件。 |
|
||||
| E00018 | 框架设置 `access_token` 参数为自定义闭包函数,有新 WebSocket 连接接入但是闭包函数返回失败 | 说白了就是自定义的 `access_token` 验证失败。如果是自己的 OneBot 客户端连接,那么请检查你的函数或 OneBot 客户端那边和框架约定的 token 是否一致,如果将框架开到了公网并有人尝试连接但失败了说明是正常现象。 |
|
||||
| E00019 | 框架设置了 `access_token` 为固定字符串,但是 WebSocket 新连接验证 Token 失败 | 如果是自身行为,比如 OneBot 客户端接入,请检查 Token 是否一致。如果不需要设置 Token,请检查全局配置文件的 `access_token` 项是否为空字符串。 |
|
||||
| E00020 | 框架在收到 WebSocket 连接后触发 `@OnOpenEvent` 注解事件过程中抛出了异常 | 检查用户代码中 `@OnOpenEvent`、`@OnSwooleEvent("open")` 注解事件下的代码是否有问题。 |
|
||||
| E00021 | 框架在处理 pipeMessage 事件时出现了异常 | 如果写了 `@OnPipeMessageEvent` 注解事件,请检查对应注解事件。如果未设置,可能是框架内部错误,请将报错信息反馈开发者。 |
|
||||
| E00022 | 调用 `ProcessManager::sendActionToWorker()` 方法时,调用此方法的进程不是 Worker 或 Manager 进程 | 如果你在 Master 进程调用此方法会直接报此错误,框架不支持从 Master 进程调用此方法。 |
|
||||
| E00023 | 框架在收到 HTTP 请求后处理过程中出现了未捕获的异常 | 检查 HTTP 请求相关的注解解析代码,如果调用栈显示非用户代码所致,请将错误信息反馈开发者。 |
|
||||
| E00024 | 框架使用 `--watch` 时无法使用热更新并报错 | PHP 未安装 inotify 扩展,请使用 pecl 安装 inotify 扩展并启用后再试。 |
|
||||
| E00025 | 框架使用终端输入时产生了未捕获的异常或致命错误 | 检查 `@TerminalCommand` 注解事件或检查使用动态命令的内容(例如 bc 或 call 运行的代码或函数有没有错误)。 |
|
||||
| E00026 | 框架使用 `@OnTask` 注解在 TaskWorker 进程中执行函数抛出了异常 | 检查 TaskWorker 运行的任务代码是否会抛出未捕获的异常。 |
|
||||
| E00027 | 框架在运行过程中 Worker 进程发生未捕获的异常导致崩溃退出 | 见 [Issue #38](https://github.com/zhamao-robot/zhamao-framework/issues/38)。 |
|
||||
| E00028 | PHP 未安装 pdo_mysql(mysqlnd+PDO)扩展 | 安装 php-mysql(以 ubuntu 为例,apt install php-pdo php-mysql)。 |
|
||||
| E00029 | PHP 未安装 redis 扩展 | 安装 redis 扩展。 |
|
||||
| E00030 | 框架在 Worker 进程启动时出现错误 | 检查 `@OnStart` 相关事件的问题,或根据报错信息定位问题所在。此问题可能较常见,一般在启动时导致的。 |
|
||||
| E00031 | 框架在启动前解析代码出现错误 | 检查模块代码中是否有 PHP 语法错误。 |
|
||||
| E00032 | 上下文的 class 没有 implements ContextInterface 接口 | 如果从 global.php 设置了自定义上下文类,那么请检查上下文类有没有根据文档标准来编写接口。 |
|
||||
| E00033 | Worker 进程运行过程使用 `zm_*` 方法过程中抛出了未被捕获的异常 | 一般是由 `zm_go()` 或 `zm_timer_tik()` 造成的,协程或计时器内抛出了异常未被捕获。建议根据 trace 检查是什么地方抛出的异常。 |
|
||||
| E00034 | 由带中间件的 `@OnTick` 计时器产生了未被捕获的异常 | 建议检查计时器内的代码抛出异常位置,如果错误处理也是一部分功能,建议使用 `try catch` 自行捕获。 |
|
||||
| E00035 | CQ 码相关错误 | 根据提示检查调用 CQ 码的代码即可。 |
|
||||
| E00036 | OneBot WebSocket API 推送失败,可能是 WebSocket 客户端出现了问题 | 建议检查 OneBot 客户端和框架的连接是否正常。 |
|
||||
| E00037 | OneBot 机器人端连接未找到,或单例模式连接了多个机器人 | 根据提示信息进行修复,比如机器人 xxx 未连接到框架,就看一下 OneBot 客户端是否启用和配置正常。 |
|
||||
| E00038 | 图灵机器人 API 调用出错 | 根据提示和图灵错误码进行检查。 |
|
||||
| E00039 | 使用 build 命令时检测到目标目录不存在 | 重新指定一个存在的目录即可。 |
|
||||
| E00040 | 使用 build 命令时检测到 PHP 未设置 `phar.readonly=Off` | 修改 php.ini 将此项设置为 Off。 |
|
||||
| E00041 | 使用 init 命令时未检测到 composer.json 文件 | 检查引用框架的 composer.json 文件位置。 |
|
||||
| E00042 | 框架使用 init 命令时启动模式不是 Composer 模式 | 如果你是使用 git 且下载的仓库是 `zhamao-robot/zhamao-framework.git`,那么代表其以源码模式启动,详见[框架启动模式 - 炸毛框架 v2 (zhamao.xin)](https://framework.zhamao.xin/advanced/custom-start/)。 |
|
||||
| E00043 | MySQL 数据库出错,抛出异常 | 根据提示信息检查 MySQL 语句是否正确,数据库是否连接正常等,其他不能解决的问题建议反馈开发者。 |
|
||||
| E00044 | 打包模块过程中抛出了异常 | 根据提示文本进行修复错误的指令和代码即可。 |
|
||||
| E00045 | 打包模块过程中无法储存 `light-cache-store` 项指定的缓存数据 | 根据提示进行修复即可。 |
|
||||
| E00046 | Redis 连接池在使用过程中未提前初始化,可能是未设置全局配置文件启用 Redis 连接池 | 检查 global.php 是否设置 Redis 服务器。 |
|
||||
| E00047 | Redis 连接池初始化失败 | 根据提示报错信息进行修复,先检查 global.php 是否设置 Redis 服务器。 |
|
||||
| E00048 | LightCache 未初始化 | LightCache 会根据 global.php 初始化申请内存,如果申请出错请根据启动时的报错信息调整配置。 |
|
||||
| E00049 | LightCache 不能接收字符串、数组、int 之外的变量数据 | 检查传入的数据类型。 |
|
||||
| E00050 | 系统内存不足,LightCache 申请内存失败 | 让 PHP 可使用的内存或系统内存变大,也可以调小全局配置中设置的 LightCache 配置项。 |
|
||||
| E00051 | LightCache 的 Hash 冲突过多,导致无法动态空间分配内存 | 设置 `hash_conflict_proportion` 大一些(范围 0-1,默认是 0.6)。 |
|
||||
| E00052 | 在 /src/ 目录下不可以直接标记为模块(zm.json),因为命名空间不能为根空间 | 将模块标记文件 zm.json 放到子目录下,不能直接放在 src 目录下。 |
|
||||
| E00053 | 框架检测到了重名模块 | 更改模块名称。 |
|
||||
| E00054 | 打包好的模块文件(phar)内检测不到 zm.json 原始模块标记文件存在 | 检查 phar 模块是否完整。 |
|
||||
| E00055 | 打包好的模块文件(phar)不能正常读取模块标记文件(zm.json) | 检查 phar 模块是否完整。 |
|
||||
| E00056 | 未开启 TaskWorker 进程 | 请先修改 global 配置文件启用。 |
|
||||
| E00057 | 调用 `DataProvider::saveToJson()` 失败,因为传入了多级目录 | `saveToJson()` 方法的 `$filename` 参数只能最多到第二级子目录,不能有三级,例如 `foo/bar`。 |
|
||||
| E00058 | 调用 `DataProvider::scanDirFiles()` 失败,因为传入的 `$relative` 错误 | `$relative` 参数只能传入 `string/false` 两种类型。 |
|
||||
| E00059 | 使用 `MessageUtil::downloadCQImage()` 失败,因为指定下载的目录不存在 | 新建目录,检查目录地址是否是绝对路径,如果手动指定了目录,最好为绝对路径。 |
|
||||
| E00060 | 使用 `MessageUtil::downloadCQImage()` 失败,因为图片下载失败 | 检查下载图片的链接地址是否能正常的访问。 |
|
||||
| E00061 | 使用 `set_coroutine_params()` 失败,因为不能在非协程环境使用此函数 | 检查调用此函数的位置,注意不能在非协程环境(比如 Master 进程)下调用。 |
|
||||
| E00062 | 注解事件非法或不可回溯 | 不能在非注解调用的类中的方法调用 `EventTracer` 方法。 |
|
||||
| E00063 | 模块检测到依赖版本问题 | 检查是否部署或正确配置依赖的模块/插件版本。 |
|
||||
| E00064 | 模块系统检测到依赖的模块不存在或未安装部署 | 检查依赖的模块是否正确存在于源码目录。 |
|
||||
| E00065 | 模块系统检测到打包的模块文件中未含有 `light_cache_store.json` 文件 | 可能是打包此模块后打包的文件损坏,请询问原开发者打包一个新的没有损坏的 phar 文件。 |
|
||||
| E00066 | 模块打包时 `zmdata-store` 指定的文件或目录不存在 | 检查是否存在,检查写的相对路径是否有误(相对路径的初始路径为框架当前的 `zm_data` 配置的目录。 |
|
||||
| E00067 | 模块解包合并 `composer.json` 时没有找到项目原文件 | 检查项目的工作目录下是否有 `composer.json` 文件存在。 |
|
||||
| E00068 | 模块解包时无法正常拷贝文件 | 检查文件夹是否正常可以创建和写入。 |
|
||||
| E00069 | 框架不能启动两个 ConsoleApplication 实例 | 不要重复使用 `new ConsoleApplication()`。 |
|
||||
| E00070 | 框架找不到 `zm_data` 目录 | 检查配置中指定的 `zm_data` 目录是否存在。 |
|
||||
| E99999 | 未知错误 | |
|
||||
|
||||
@@ -42,16 +42,16 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
|
||||
delay: 3 # 首次重连延迟, 单位秒
|
||||
interval: 3 # 重连间隔
|
||||
max-times: 0 # 最大重连次数, 0为无限制
|
||||
|
||||
|
||||
# 是否使用服务器下发的新地址进行重连
|
||||
# 注意, 此设置可能导致在海外服务器上连接情况更差
|
||||
use-sso-address: true
|
||||
|
||||
|
||||
heartbeat:
|
||||
# 心跳频率, 单位秒
|
||||
# -1 为关闭心跳
|
||||
interval: 5
|
||||
|
||||
|
||||
message:
|
||||
# 上报数据类型
|
||||
# 可选: string,array
|
||||
@@ -72,13 +72,13 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
|
||||
remove-reply-at: false
|
||||
# 为Reply附加更多信息
|
||||
extra-reply-data: false
|
||||
|
||||
|
||||
output:
|
||||
# 日志等级 trace,debug,info,warn,error
|
||||
log-level: warn
|
||||
# 是否启用 DEBUG
|
||||
debug: false # 开启调试模式
|
||||
|
||||
|
||||
# 默认中间件锚点
|
||||
default-middlewares: &default
|
||||
# 访问密钥, 强烈推荐在公网的服务器设置
|
||||
@@ -94,14 +94,14 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
|
||||
enabled: false # 是否启用限速
|
||||
frequency: 1 # 令牌回复频率, 单位秒
|
||||
bucket: 1 # 令牌桶大小
|
||||
|
||||
|
||||
database: # 数据库相关设置
|
||||
leveldb:
|
||||
# 是否启用内置leveldb数据库
|
||||
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
|
||||
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
|
||||
enable: true
|
||||
|
||||
|
||||
# 连接服务列表
|
||||
servers:
|
||||
# 添加方式,同一连接方式可添加多个,具体配置说明请查看文档
|
||||
@@ -331,3 +331,10 @@ public function repeat() {
|
||||
|
||||
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。
|
||||
|
||||
|
||||
|
||||
## 使用机器人 API 和事件
|
||||
|
||||
如果你想不只是回复消息,还要做其他复杂的动作(Action),使用 OneBot Action(又名 OneBot API)进行发送即可,见 [机器人 API](/component/bot/robot-api)。
|
||||
|
||||
如果想处理其他类型的事件,比如 QQ 群通知事件等,见 [机器人注解事件](/event/robot-annotations/)。
|
||||
@@ -2,17 +2,13 @@
|
||||
|
||||
> 本文档为炸毛框架 v2 版本,如需查看 v1 版本,[点我](https://docs-v1.zhamao.xin/)。
|
||||
|
||||
> 如果是从 v1.x 版本升级到 v2.x,[点我看升级指南](/advanced/to-v2/)。
|
||||
|
||||
!!! tip "提示"
|
||||
|
||||
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request,以此来贡献此文档!
|
||||
|
||||
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
|
||||
|
||||
框架主要用途为 HTTP/WS 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
|
||||
|
||||
在 HTTP 和 WebSocket 服务器上,PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。
|
||||
框架主要用途为 HTTP/WebSocket 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
|
||||
|
||||
此外,QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
|
||||
|
||||
@@ -38,18 +34,18 @@ public function index() {
|
||||
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
|
||||
|
||||
1. Linux 命令行(会跑 Linux 程序)
|
||||
2. php 7.2+ 开发环境(项目会持续支持最新的 PHP 版本)
|
||||
3. HTTP/WebSocket 协议
|
||||
2. php >=7.2 开发环境(项目会持续支持最新的 PHP 版本)
|
||||
4. OneBot 机器人聊天接口标准
|
||||
|
||||
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
|
||||
|
||||
!!! bug "文档提示"
|
||||
|
||||
此文档采用 MkDocs 驱动,但因为本文档的搜索组件原生不支持中文搜索,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西哦!
|
||||
此文档采用 MkDocs 驱动,文档的搜索组件原生不支持中文搜索,且分词很难控制,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西,建议这种情况可以自行翻阅目录查看!
|
||||
|
||||
|
||||
## 框架特色
|
||||
|
||||
- 支持MySQL数据库(连接池),自带查询缓存提高多查询时的效率
|
||||
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
|
||||
- 支持命令、自然语言处理等多种插件形式
|
||||
@@ -61,6 +57,7 @@ public function index() {
|
||||
## 文档主题
|
||||
|
||||
### 主题
|
||||
|
||||
<div class="tx-switch">
|
||||
<button data-md-color-scheme="default"><code>默认模式</code></button>
|
||||
<button data-md-color-scheme="slate"><code>暗黑模式</code></button>
|
||||
@@ -80,6 +77,7 @@ public function index() {
|
||||
</script>
|
||||
|
||||
### 主色调
|
||||
|
||||
<div class="tx-switch">
|
||||
<button data-md-color-primary="red"><code>red</code></button>
|
||||
<button data-md-color-primary="pink"><code>pink</code></button>
|
||||
@@ -105,6 +103,7 @@ public function index() {
|
||||
</div>
|
||||
|
||||
### 辅色调
|
||||
|
||||
<div class="tx-switch"> <button data-md-color-accent="red"><code>red</code></button> <button data-md-color-accent="pink"><code>pink</code></button> <button data-md-color-accent="purple"><code>purple</code></button> <button data-md-color-accent="deep-purple"><code>deep purple</code></button> <button data-md-color-accent="indigo"><code>indigo</code></button> <button data-md-color-accent="blue"><code>blue</code></button> <button data-md-color-accent="light-blue"><code>light blue</code></button> <button data-md-color-accent="cyan"><code>cyan</code></button> <button data-md-color-accent="teal"><code>teal</code></button> <button data-md-color-accent="green"><code>green</code></button> <button data-md-color-accent="light-green"><code>light green</code></button> <button data-md-color-accent="lime"><code>lime</code></button> <button data-md-color-accent="yellow"><code>yellow</code></button> <button data-md-color-accent="amber"><code>amber</code></button> <button data-md-color-accent="orange"><code>orange</code></button> <button data-md-color-accent="deep-orange"><code>deep orange</code></button> </div>
|
||||
|
||||
<script>
|
||||
|
||||
36
docs/update/build-update.md
Normal file
36
docs/update/build-update.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# 更新日志(master 分支 commit)
|
||||
|
||||
此文档将显示非发布版的提交版本相关更新文档,可能与发布版的更新日志有重合,在此仅作更新记录。
|
||||
|
||||
同时此处将只使用 build 版本号进行区分。
|
||||
|
||||
## build 418 (2021-9-10)
|
||||
|
||||
- 修复 ZMAtomic 在 test 环境下的 bug
|
||||
- 修复 MessageUtil 的报错
|
||||
-
|
||||
|
||||
## build 417 (2021-8-29)
|
||||
|
||||
- 新增 AnnotationException,统一框架内部的抛出异常的类型
|
||||
- 新增 AnnotationParser 下的 `verifyMiddlewares()` 方法
|
||||
- 私有化 CQAPI 类下的内部方法
|
||||
- 将 WebSocket API 响应超时时间从 60 秒缩短为 30 秒
|
||||
- 修复 DB 类不能使用旧查询器的 bug
|
||||
- 统一 DB 类下抛出 Exception 的类型为 ZMException 的子类
|
||||
- EventDispatcher 新增对 `middleware_error_policy` 的处理段
|
||||
- 配置文件下 `runtime` 新增 `middleware_error_policy` 字段
|
||||
- 将 LightCache 组件抛出的异常改为 LightCacheException
|
||||
- ModuleManager 修复改配置的 `load_path` 不生效的 bug
|
||||
- 修复打包时生成的 Phar Autoload 列表出错的 bug
|
||||
- 将配置的 override 改为 overwrite
|
||||
- 新增解包时忽略依赖的选项(`--ignore-depends`)
|
||||
- 删除众多调试日志,修改部分调试日志为 debug 级别的输出
|
||||
- 修改 `ZM\MySQL\MySQLManager` 下的 `getConnection()` 为 `getWrapper()`
|
||||
- MySQLPool 对象新增 `getCount()` 方法
|
||||
- 新增 MySQLQueryBuilder 类(`doctrine/dbal` 的 wrapper 类)
|
||||
- 修复 MySQLStatement 封装原 dbal 组件时与连接池不兼容的 bug
|
||||
- 新增 MySQLStatementWrapper 类
|
||||
- 完善 MySQLWrapper 类,用作主要的查询对象控制类
|
||||
- 编写外部插件加载方式(Phar 热加载功能)
|
||||
- 修复 `ZMUtil::getClassesPsr4()` 方法在遇到空扩展名文件时的报错
|
||||
@@ -2,6 +2,42 @@
|
||||
|
||||
这里将会记录各个主版本的框架升级后,涉及 `global.php` 的更新日志,你可以根据这里描述的内容与你的旧配置文件进行合并。
|
||||
|
||||
## v2.5.0 (build 413)
|
||||
|
||||
- 新增 `$config['runtime']` 运行时设置。
|
||||
- 删除 `$config['server_event_handler_class']`,默认在启动时全局扫描。
|
||||
- 新增 `$config['module_loader']` 模块/插件 打包配置选项。
|
||||
- 新增 `$config['mysql_config']`,取代原先的 `$config['sql_config']`,此外废弃原先的MySQL 查询器 `\ZM\DB\DB` 类。
|
||||
|
||||
更新部分:
|
||||
|
||||
```php
|
||||
/** 一些框架与Swoole运行时设置的调整 */
|
||||
$config['runtime'] = [
|
||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
||||
'swoole_server_mode' => SWOOLE_PROCESS
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['mysql_config'] = [
|
||||
'host' => '',
|
||||
'port' => 3306,
|
||||
'unix_socket' => null,
|
||||
'username' => 'root',
|
||||
'password' => '123456',
|
||||
'dbname' => 'adb',
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64,
|
||||
'options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
|
||||
]
|
||||
];
|
||||
|
||||
/** 注册 Swoole Server 事件注解的类列表(deleted) */
|
||||
// 删除
|
||||
```
|
||||
|
||||
## v2.4.0 (build 400)
|
||||
- 调整 `$config['modules']['onebot']` 配置项到 `$config['onebot']`,旧版本的此段会向下兼容,建议更新,
|
||||
- 新增 `$config['remote_terminal']` 远程终端的配置项,新增此段即可。
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.5.0(未发布,测试中)
|
||||
## v2.5.1 (build 416)
|
||||
|
||||
> 更新时间:未知
|
||||
> 更新时间:2021.7.9
|
||||
|
||||
- 修复:脚手架无法正常使用 `init` 命令的 bug。
|
||||
|
||||
## v2.5.0(build 415)
|
||||
|
||||
> 更新时间:2021.7.9
|
||||
|
||||
以下是版本**新增内容**:
|
||||
|
||||
- 新增全新的模块系统,可打包模块(src 目录下的子目录用户逻辑代码)为 phar 格式进行分发和版本备份。
|
||||
- 全局配置文件新增 `module_loader` 项,用于配置外部模块加载的一些设置。
|
||||
- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数。
|
||||
- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数和 Swoole Server 的启动模式。
|
||||
- 新增 `module:list` 命令,用于查看未打包和已打包的模块列表。
|
||||
- 新增 `module:pack` 命令,用于打包现有 src 目录下的模块。
|
||||
- 新增 `module:unpack` 命令,用于解包现有的 phar 模块包。
|
||||
- 新增打包框架功能,支持将用户的整个项目连同炸毛框架打包为一个 phar 便携运行,使用命令 `build`。
|
||||
- 新增快捷脚本 `./zhamao`,效果同 `vendor/bin/start` 或 `bin/start`。
|
||||
- 新增启动参数 `--interact`:又重新支持交互终端了,但还是有点问题,不推荐使用。
|
||||
@@ -24,11 +31,24 @@
|
||||
- `DataProvider` 新增 `isRelativePath()` 方法,检查路径是否为相对路径(根据第一个字符是否是 '/' 来判断)。
|
||||
- `ZMUtil` 新增 `getClassesPsr4()` 方法,用于根据 Psr-4 标准来获取目录下的所有类文件。
|
||||
- 新增全局错误码,可以根据错误码在文档内快速定位和解决问题。
|
||||
- 中间件和注解事件支持回溯,可以快速查看调用栈(比如中间件可以知道自己是在哪个注解事件中被调用)。
|
||||
- 使用 `./zhamao build` 来构建框架的 phar 包时增加显示进度条。
|
||||
- EventDispatcher 新增方法 `getEid()` 和 `getClass()`,分别用于获取事件分发 ID 和注解事件的注解类名称。
|
||||
- 新增 EventTracer,用于追踪事件的调用栈。
|
||||
- 中间件支持传参。
|
||||
- MySQL 数据库查询器改为使用 `doctrine/dbal` 组件,更灵活和稳定。
|
||||
- 新增对 `SWOOLE_BASE` 模式的支持(支持只启动一个进程的 Server)。
|
||||
|
||||
以下是版本**修改内容**:
|
||||
|
||||
- 启动文件 `vendor/bin/start` 修改为 shell 脚本,可自动寻找 PHP 环境。
|
||||
- 全局强制依赖 `league/climate` 组件。
|
||||
- 修复框架启动时的信息显示换行问题。
|
||||
- 修复框架使用 Phar 方式启动时导致的报错。
|
||||
- 修复使用 Ctrl+C 结束时一部分用户卡住的 bug。
|
||||
- 远程和本地终端去掉 stop 命令,建议直接使用发 SIGTERM 方式结束框架。
|
||||
- 全局配置文件的 `zm_data` 根目录默认修改为 `WORKING_DIR`。
|
||||
- 命令 `systemd:generate` 修改为 `generate:systemd`。
|
||||
- 全局配置文件删除 `server_event_handler_class` 项,此项废弃。
|
||||
- 修复部分 CQ 码解析过程中没有转义的问题。
|
||||
- 将 `ZMRobot` 类转移为 `OneBotV11` 类,但提供兼容。
|
||||
@@ -42,6 +62,11 @@
|
||||
- 注解 `@OnSetup` 和 `@SwooleHandler` 可直接使用,无需设置 `server_event_handler_class` 即可。
|
||||
- 修复框架在一些非正常终端中运行时导致错误的问题。
|
||||
- 使用 `--debug-mode` 参数时,自动开启热更新。
|
||||
- 修复脚手架在使用 composer 更新后检查全局配置功能的 bug。
|
||||
- 修复重启和关闭框架时造成的非正常连接断开。
|
||||
- 改用独立进程监听文件变化和终端输入。
|
||||
- 修复有协程中断的任务时停止服务器会报 Swoole 警告的 bug。
|
||||
- 修复连接被反复断开的问题。
|
||||
|
||||
**对目录的定义解释**:
|
||||
|
||||
@@ -63,6 +88,14 @@
|
||||
|
||||
如果最后一种归档方式启动的框架是从源码模式打包而来,那么 `FrameworkRootDir` 就与 `SourceRootDir` 相同。
|
||||
|
||||
**版本部分兼容问题变化**:
|
||||
|
||||
理论上如果不使用框架内部未开放的接口方法的话,从 2.4 升级到 2.5 是非常自然的,但是也有一部分可能会造成不兼容的问题。
|
||||
|
||||
- 生成 systemd 配置文件的命令 `systemd:generate` 变成 `generate:systemd`。
|
||||
- 全局配置文件中的 `zm_data` 的父目录由 `__DIR__ . "/../"` 改为 `WORKING_DIR`。
|
||||
- 2.5 版本将 ZMRobot 类中的所有函数方法都移动到了 `OneBotV11` 类中,但原先的 ZMRobot 还可以使用。
|
||||
|
||||
## v2.4.4 (build 405)
|
||||
|
||||
> 更新时间:2021.3.29
|
||||
|
||||
0
ext/go-cqhttp/go-cqhttp-down.sh
Executable file → Normal file
0
ext/go-cqhttp/go-cqhttp-down.sh
Executable file → Normal file
0
install-runtime.sh
Executable file → Normal file
0
install-runtime.sh
Executable file → Normal file
15
mkdocs.yml
15
mkdocs.yml
@@ -20,12 +20,16 @@ extra_javascript:
|
||||
extra_css:
|
||||
- assets/css/extra.css
|
||||
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
|
||||
plugins:
|
||||
- search:
|
||||
lang: ja
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.tabbed
|
||||
- pymdownx.superfences
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.details
|
||||
- abbr
|
||||
- pymdownx.highlight:
|
||||
linenums: true
|
||||
@@ -62,6 +66,7 @@ nav:
|
||||
- 基本配置: guide/basic-config.md
|
||||
- 编写模块: guide/write-module.md
|
||||
- 注册事件响应: guide/register-event.md
|
||||
- 错误码对照表: guide/errcode.md
|
||||
- 事件和注解:
|
||||
- 事件和注解: event/index.md
|
||||
- 机器人注解事件: event/robot-annotations.md
|
||||
@@ -81,7 +86,8 @@ nav:
|
||||
- 图灵机器人 API: component/bot/turing-api.md
|
||||
- 存储:
|
||||
- LightCache 轻量缓存: component/store/light-cache.md
|
||||
- MySQL 数据库: component/store/mysql.md
|
||||
- MySQL 查询器: component/store/mysql.md
|
||||
- MySQL 查询器(废弃): component/store/mysql-db.md
|
||||
- Redis 数据库: component/store/redis.md
|
||||
- ZMAtomic 原子计数器: component/store/atomics.md
|
||||
- SpinLock 自旋锁: component/store/spin-lock.md
|
||||
@@ -89,13 +95,16 @@ nav:
|
||||
- HTTP 服务器工具类:
|
||||
- HTTP 和 WebSocket 客户端: component/zmrequest.md
|
||||
- HTTP 路由管理: component/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
|
||||
- 远程终端: component/remote-terminal.md
|
||||
- Terminal 终端: component/remote-terminal.md
|
||||
- 进阶开发:
|
||||
- 进阶开发: advanced/index.md
|
||||
- 框架剖析: advanced/framework-structure.md
|
||||
@@ -110,6 +119,7 @@ nav:
|
||||
- 编写管理员才能触发的功能: advanced/example/admin.md
|
||||
- FAQ:
|
||||
- FAQ: faq/FAQ.md
|
||||
- 框架常见问题(持续更新): faq/usual-question.md
|
||||
- 启动时报错 Address already in use: faq/address-already-in-use.md
|
||||
- 出现 deadlock 字样: faq/display-deadlock.md
|
||||
- 使用 LightCache 关闭时无法正常保存持久化: faq/light-cache-wrong.md
|
||||
@@ -117,5 +127,6 @@ nav:
|
||||
- 更新日志:
|
||||
- 更新日志(v2): update/v2.md
|
||||
- 更新日志(v1): update/v1.md
|
||||
- 更新日志(build update): update/build-update.md
|
||||
- 配置文件更新日志: update/config.md
|
||||
- <u>炸毛框架 v1</u>: https://docs-v1.zhamao.xin/
|
||||
|
||||
@@ -190,6 +190,6 @@ class Hello
|
||||
*/
|
||||
public function closeUnknownConn() {
|
||||
Console::info("Unknown connection , I will close it.");
|
||||
server()->close(ctx()->getConnection()->getFd());
|
||||
server()->disconnect(ctx()->getConnection()->getFd());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ trait CQAPI
|
||||
return $this->processHttpAPI($connection, $reply, $function);
|
||||
}
|
||||
|
||||
public function processWebsocketAPI($connection, $reply, $function = false) {
|
||||
private function processWebsocketAPI($connection, $reply, $function = false) {
|
||||
$api_id = ZMAtomic::get("wait_msg_id")->add(1);
|
||||
$reply["echo"] = $api_id;
|
||||
if (server()->push($connection->getFd(), json_encode($reply))) {
|
||||
@@ -35,7 +35,7 @@ trait CQAPI
|
||||
"self_id" => $connection->getOption("connect_id"),
|
||||
"echo" => $api_id
|
||||
];
|
||||
return CoMessage::yieldByWS($obj, ["echo"], 60);
|
||||
return CoMessage::yieldByWS($obj, ["echo"], 30);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
@@ -63,7 +63,7 @@ trait CQAPI
|
||||
* @return bool
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function processHttpAPI($connection, $reply, $function = null): bool {
|
||||
private function processHttpAPI($connection, $reply, $function = null): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,6 @@ class OneBotV11
|
||||
return new ZMRobot($r[array_rand($r)]);
|
||||
}
|
||||
|
||||
public static function getFirst() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZMRobot[]
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace ZM\Annotation;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
@@ -17,6 +18,7 @@ use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Interfaces\Level;
|
||||
use ZM\Annotation\Module\Closed;
|
||||
use ZM\Exception\AnnotationException;
|
||||
use ZM\Utils\Manager\RouteManager;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
@@ -53,7 +55,7 @@ class AnnotationParser
|
||||
*/
|
||||
public function registerMods() {
|
||||
foreach ($this->path_list as $path) {
|
||||
Console::debug("parsing annotation in " . $path[0]);
|
||||
Console::debug("parsing annotation in " . $path[0].":".$path[1]);
|
||||
$all_class = ZMUtil::getClassesPsr4($path[0], $path[1]);
|
||||
$this->reader = new AnnotationReader();
|
||||
foreach ($all_class as $v) {
|
||||
@@ -107,6 +109,7 @@ class AnnotationParser
|
||||
unset($this->annotation_map[$v]);
|
||||
continue 2;
|
||||
} elseif ($vs instanceof MiddlewareClass) {
|
||||
//注册中间件本身的类,标记到 middlewares 属性中
|
||||
Console::debug("正在注册中间件 " . $reflection_class->getName());
|
||||
$rs = $this->registerMiddleware($vs, $reflection_class);
|
||||
$this->middlewares[$rs["name"]] = $rs;
|
||||
@@ -122,7 +125,7 @@ class AnnotationParser
|
||||
if ($method_anno instanceof RequestMapping) {
|
||||
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
|
||||
} elseif ($method_anno instanceof Middleware) {
|
||||
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno->middleware;
|
||||
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,6 +196,12 @@ class AnnotationParser
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal 用于 level 排序
|
||||
* @param $events
|
||||
* @param string $class_name
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function sortByLevel(&$events, string $class_name, $prefix = "") {
|
||||
if (is_a($class_name, Level::class, true)) {
|
||||
$class_name .= $prefix;
|
||||
@@ -203,4 +212,22 @@ class AnnotationParser
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function verifyMiddlewares() {
|
||||
if ((ZMConfig::get("global", "runtime")["middleware_error_policy"] ?? 1) === 2) {
|
||||
//我承认套三层foreach很不优雅,但是这个会很快的。
|
||||
foreach($this->middleware_map as $class => $v) {
|
||||
foreach ($v as $method => $vs) {
|
||||
foreach($vs as $mid) {
|
||||
if (!isset($this->middlewares[$mid->middleware])) {
|
||||
throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$mid->middleware}\"!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,4 +22,9 @@ class Middleware extends AnnotationBase implements ErgodicAnnotation
|
||||
* @Required()
|
||||
*/
|
||||
public $middleware;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $params = [];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use League\CLImate\CLImate;
|
||||
use Phar;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
@@ -56,22 +57,22 @@ class BuildCommand extends Command
|
||||
@unlink($target_dir . $filename);
|
||||
$phar = new Phar($target_dir . $filename);
|
||||
$phar->startBuffering();
|
||||
$climate = new CLImate();
|
||||
|
||||
$allow_dir = ["bin", "config", "resources", "src", "vendor", "composer.json", "README.md", "zhamao"];
|
||||
$all = DataProvider::scanDirFiles(DataProvider::getSourceRootDir(), true, true);
|
||||
|
||||
$all = array_filter($all, function ($x) {
|
||||
$dirs = preg_match("/(^(bin|config|resources|src|vendor)\/|^(composer\.json|README\.md)$)/", $x);
|
||||
return !($dirs !== 1);
|
||||
});
|
||||
|
||||
sort($all);
|
||||
$progress = $climate->progress()->total(count($all));
|
||||
|
||||
$archive_dir = DataProvider::getSourceRootDir();
|
||||
$scan = scandir($archive_dir);
|
||||
if ($scan[0] == ".") {
|
||||
unset($scan[0], $scan[1]);
|
||||
}
|
||||
foreach ($scan as $v) {
|
||||
if (in_array($v, $allow_dir)) {
|
||||
if (is_dir($archive_dir . "/" . $v)) {
|
||||
$this->addDirectory($phar, $archive_dir . "/" . $v, $v);
|
||||
} elseif (is_file($archive_dir . "/" . $v)) {
|
||||
$phar->addFile($archive_dir . "/" . $v, $v);
|
||||
}
|
||||
}
|
||||
foreach ($all as $k => $v) {
|
||||
$phar->addFile($archive_dir . "/" . $v, $v);
|
||||
$progress->current($k + 1, "Adding " . $v);
|
||||
}
|
||||
|
||||
$phar->setStub(
|
||||
@@ -81,18 +82,4 @@ class BuildCommand extends Command
|
||||
$phar->stopBuffering();
|
||||
$this->output->writeln("Successfully built. Location: " . $target_dir . "$filename");
|
||||
}
|
||||
|
||||
private function addDirectory(Phar $phar, $dir, $local_dir) {
|
||||
$o = scandir($dir);
|
||||
if ($o[0] == ".") {
|
||||
unset($o[0], $o[1]);
|
||||
}
|
||||
foreach ($o as $v) {
|
||||
if (is_dir($dir . "/" . $v)) {
|
||||
$this->addDirectory($phar, $dir . "/" . $v, $local_dir . "/" . $v);
|
||||
} elseif (is_file($dir . "/" . $v)) {
|
||||
$phar->addFile($dir . "/" . $v, $local_dir . "/" . $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZM\Command;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Config\ZMConfig;
|
||||
|
||||
class CheckConfigCommand extends Command
|
||||
{
|
||||
@@ -37,7 +38,7 @@ class CheckConfigCommand extends Command
|
||||
$this->check($remote_cfg, "global.production.php", $output);
|
||||
}
|
||||
if ($this->need_update === true) {
|
||||
$output->writeln("<comment>有配置文件需要更新,详情见文档 `https://framework.zhamao.xin/update/config.md`</comment>");
|
||||
$output->writeln("<comment>有配置文件需要更新,详情见文档 `https://framework.zhamao.xin/update/config`</comment>");
|
||||
} else {
|
||||
$output->writeln("<info>配置文件暂无更新!</info>");
|
||||
}
|
||||
@@ -49,7 +50,10 @@ class CheckConfigCommand extends Command
|
||||
* @noinspection PhpIncludeInspection
|
||||
*/
|
||||
private function check($remote, $local, OutputInterface $out) {
|
||||
$local_file = include_once getcwd() . "/config/".$local;
|
||||
$local_file = include_once WORKING_DIR . "/config/".$local;
|
||||
if ($local_file === true) {
|
||||
$local_file = ZMConfig::get("global");
|
||||
}
|
||||
foreach($remote as $k => $v) {
|
||||
if (!isset($local_file[$k])) {
|
||||
$out->writeln("<comment>配置文件 ".$local . " 需要更新!(当前配置文件缺少 `$k` 字段配置)</comment>");
|
||||
|
||||
@@ -20,7 +20,7 @@ class DaemonStatusCommand extends DaemonCommand
|
||||
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
|
||||
$stdout = file_get_contents($this->daemon_file["stdout"]);
|
||||
$stdout = explode("\n", $stdout);
|
||||
for ($i = 10; $i > 0; --$i) {
|
||||
for ($i = 15; $i > 0; --$i) {
|
||||
if (isset($stdout[count($stdout) - $i]))
|
||||
echo $stdout[count($stdout) - $i] . PHP_EOL;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,17 @@ class DaemonStopCommand extends DaemonCommand
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
parent::execute($input, $output);
|
||||
Process::kill(intval($this->daemon_file["pid"]), SIGINT);
|
||||
unlink(DataProvider::getWorkingDir() . "/.daemon_pid");
|
||||
$output->writeln("<info>成功停止!</info>");
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
namespace ZM\Command\Generate;
|
||||
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
class SystemdCommand extends Command
|
||||
class SystemdGenerateCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'systemd:generate';
|
||||
protected static $defaultName = 'generate:systemd';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("生成框架的 systemd 配置文件");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
$path = $this->generate();
|
||||
$output->writeln("<info>成功生成 systemd 文件,位置:".$path."</info>");
|
||||
$output->writeln("<info>成功生成 systemd 文件,位置:" . $path . "</info>");
|
||||
$output->writeln("<info>有关如何使用 systemd 配置文件,请访问 `https://github.com/zhamao-robot/zhamao-framework/issues/36`</info>");
|
||||
return 0;
|
||||
}
|
||||
@@ -30,10 +33,10 @@ class SystemdCommand extends Command
|
||||
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
|
||||
$s .= "\nWorkingDirectory=" . getcwd();
|
||||
global $argv;
|
||||
$s .= "\nExecStart=".PHP_BINARY." {$argv[0]} server";
|
||||
$s .= "\nExecStart=" . PHP_BINARY . " {$argv[0]} server";
|
||||
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
|
||||
@mkdir(getcwd() . "/resources/");
|
||||
file_put_contents(getcwd() . "/resources/zhamao.service", $s);
|
||||
return getcwd() . "/resources/zhamao.service";
|
||||
file_put_contents(ZMConfig::get("global", "zm_data") . "zhamao.service", $s);
|
||||
return ZMConfig::get("global", "zm_data") . "zhamao.service";
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,20 @@ class ModuleListCommand extends Command
|
||||
protected static $defaultName = 'module:list';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
|
||||
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
|
||||
$this->setDescription("查看所有模块信息");
|
||||
$this->setHelp("此功能将会把炸毛框架的模块列举出来。");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args["env"] ?? "");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
|
||||
//定义常量
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/global_defines.php";
|
||||
|
||||
Console::init(
|
||||
@@ -38,31 +43,31 @@ class ModuleListCommand extends Command
|
||||
|
||||
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
|
||||
date_default_timezone_set($timezone);
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$list = ModuleManager::getConfiguredModules();
|
||||
|
||||
foreach ($list as $v) {
|
||||
echo "[" . Console::setColor($v["name"], "green") . "]" . PHP_EOL;
|
||||
$out_list = ["类型" => "source"];
|
||||
$out_list = ["类型" => "源码(source)"];
|
||||
if (isset($v["version"])) $out_list["版本"] = $v["version"];
|
||||
if (isset($v["description"])) $out_list["描述"] = $v["description"];
|
||||
$out_list["目录"] = str_replace(DataProvider::getSourceRootDir() . "/", "", $v["module-path"]);
|
||||
$this->printList($out_list);
|
||||
}
|
||||
if ($list === []) {
|
||||
echo Console::setColor("没有发现已编写打包配置文件(zm.json)的模块!", "yellow") . PHP_EOL;
|
||||
}
|
||||
$list = ModuleManager::getPackedModules();
|
||||
foreach ($list as $v) {
|
||||
echo "[" . Console::setColor($v["name"], "gold") . "]" . PHP_EOL;
|
||||
$out_list = ["类型" => "archive(phar)"];
|
||||
$out_list = ["类型" => "模块包(phar)"];
|
||||
if (isset($v["module-config"]["version"])) $out_list["版本"] = $v["module-config"]["version"];
|
||||
if (isset($v["module-config"]["description"])) $out_list["描述"] = $v["module-config"]["description"];
|
||||
$out_list["位置"] = str_replace(DataProvider::getSourceRootDir() . "/", "", $v["phar-path"]);
|
||||
$this->printList($out_list);
|
||||
}
|
||||
if ($list === []) {
|
||||
echo Console::setColor("没有发现已编写打包配置文件(zm.json)的模块和已打包且装载的模块!", "yellow") . PHP_EOL;
|
||||
echo Console::setColor("没有发现已打包且装载的模块!", "yellow") . PHP_EOL;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -21,13 +21,17 @@ class ModulePackCommand extends Command
|
||||
|
||||
protected function configure() {
|
||||
$this->addArgument("module-name", InputArgument::REQUIRED);
|
||||
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
|
||||
$this->setDescription("将配置好的模块构建一个phar包");
|
||||
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
|
||||
$this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args["env"] ?? "");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
|
||||
//定义常量
|
||||
@@ -42,10 +46,7 @@ class ModulePackCommand extends Command
|
||||
|
||||
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
|
||||
date_default_timezone_set($timezone);
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$list = ModuleManager::getConfiguredModules();
|
||||
if (!isset($list[$input->getArgument("module-name")])) {
|
||||
$output->writeln("<error>不存在模块 ".$input->getArgument("module-name")." !</error>");
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZM\Command\Module;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
@@ -19,20 +20,31 @@ class ModuleUnpackCommand extends Command
|
||||
protected static $defaultName = 'module:unpack';
|
||||
|
||||
protected function configure() {
|
||||
$this->addArgument("module-name", InputArgument::REQUIRED);
|
||||
$this->setDescription("Unpack a phar module into src directory");
|
||||
$this->setDefinition([
|
||||
new InputArgument("module-name", InputArgument::REQUIRED),
|
||||
new InputOption("overwrite-light-cache", null, null, "覆盖现有的LightCache项目"),
|
||||
new InputOption("overwrite-zm-data", null, null, "覆盖现有的zm_data文件"),
|
||||
new InputOption("overwrite-source", null, null, "覆盖现有的源码文件"),
|
||||
new InputOption("ignore-depends", null, null, "解包时忽略检查依赖")
|
||||
]);
|
||||
$this->setDescription("解包一个phar模块到src目录");
|
||||
$this->setHelp("此功能将phar格式的模块包解包到src目录下。");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args["env"] ?? "");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
|
||||
//定义常量
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php";
|
||||
|
||||
Console::init(
|
||||
ZMConfig::get("global", "info_level") ?? 2,
|
||||
ZMConfig::get("global", "info_level") ?? 4,
|
||||
null,
|
||||
$args["log-theme"] ?? "default",
|
||||
($o = ZMConfig::get("console_color")) === false ? [] : $o
|
||||
@@ -40,16 +52,13 @@ class ModuleUnpackCommand extends Command
|
||||
|
||||
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
|
||||
date_default_timezone_set($timezone);
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
$list = ModuleManager::getPackedModules();
|
||||
if (!isset($list[$input->getArgument("module-name")])) {
|
||||
$output->writeln("<error>不存在打包的模块 ".$input->getArgument("module-name")." !</error>");
|
||||
return 1;
|
||||
}
|
||||
$result = ModuleManager::unpackModule($list[$input->getArgument("module-name")]);
|
||||
$result = ModuleManager::unpackModule($list[$input->getArgument("module-name")], $input->getOptions());
|
||||
if ($result) Console::success("解压完成!");
|
||||
else Console::error("解压失败!");
|
||||
return 0;
|
||||
|
||||
@@ -11,6 +11,7 @@ use ZM\Command\CheckConfigCommand;
|
||||
use ZM\Command\Daemon\DaemonReloadCommand;
|
||||
use ZM\Command\Daemon\DaemonStatusCommand;
|
||||
use ZM\Command\Daemon\DaemonStopCommand;
|
||||
use ZM\Command\Generate\SystemdGenerateCommand;
|
||||
use ZM\Command\InitCommand;
|
||||
use ZM\Command\Module\ModuleListCommand;
|
||||
use ZM\Command\Module\ModulePackCommand;
|
||||
@@ -20,21 +21,34 @@ use ZM\Command\RunServerCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Command\SystemdCommand;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\InitException;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
const VERSION_ID = 408;
|
||||
const VERSION = "2.5.0-b1";
|
||||
private static $obj = null;
|
||||
|
||||
const VERSION_ID = 418;
|
||||
const VERSION = "2.5.2";
|
||||
|
||||
/**
|
||||
* @throws InitException
|
||||
*/
|
||||
public function __construct(string $name = 'UNKNOWN') {
|
||||
if (self::$obj !== null) throw new InitException(zm_internal_errcode("E00069") . "Initializing another Application is not allowed!");
|
||||
define("ZM_VERSION_ID", self::VERSION_ID);
|
||||
define("ZM_VERSION", self::VERSION);
|
||||
self::$obj = $this;
|
||||
parent::__construct($name, ZM_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $with_default_cmd
|
||||
* @return ConsoleApplication
|
||||
* @throws InitException
|
||||
*/
|
||||
public function initEnv($with_default_cmd = ""): ConsoleApplication {
|
||||
if (defined("WORKDING_DIR")) throw new InitException();
|
||||
$this->selfCheck();
|
||||
|
||||
define("WORKING_DIR", getcwd());
|
||||
@@ -77,7 +91,7 @@ class ConsoleApplication extends Application
|
||||
new DaemonStopCommand(),
|
||||
new RunServerCommand(), //运行主服务的指令控制器
|
||||
new PureHttpCommand(), //纯HTTP服务器指令
|
||||
new SystemdCommand()
|
||||
new SystemdGenerateCommand()
|
||||
]);
|
||||
if (LOAD_MODE === 1) {
|
||||
$this->add(new CheckConfigCommand());
|
||||
|
||||
@@ -6,26 +6,30 @@
|
||||
namespace ZM\DB;
|
||||
|
||||
|
||||
use Exception;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\MySQL\MySQLManager;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use Swoole\Database\PDOStatementProxy;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
/**
|
||||
* Class DB
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class DB
|
||||
{
|
||||
private static $table_list = [];
|
||||
|
||||
/**
|
||||
* @param $db_name
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function initTableList() {
|
||||
if (!extension_loaded("mysqlnd")) throw new Exception("Can not find mysqlnd PHP extension.");
|
||||
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMConfig::get("global", "sql_config")["sql_database"] . "';", []);
|
||||
public static function initTableList($db_name) {
|
||||
if (!extension_loaded("mysqlnd")) throw new DbException("Can not find mysqlnd PHP extension.");
|
||||
$result = MySQLManager::getWrapper()->fetchAllAssociative("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=?;", [$db_name]);
|
||||
foreach ($result as $v) {
|
||||
self::$table_list[] = $v['TABLE_NAME'];
|
||||
}
|
||||
@@ -64,13 +68,13 @@ class DB
|
||||
*/
|
||||
public static function unprepared($line): bool {
|
||||
try {
|
||||
$conn = SqlPoolStorage::$sql_pool->get();
|
||||
$conn = SqlPoolStorage::$sql_pool->getConnection();
|
||||
if ($conn === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
SqlPoolStorage::$sql_pool->putConnection(null);
|
||||
throw new DbException("无法连接SQL!" . $line);
|
||||
}
|
||||
$result = $conn->query($line) === false ? false : true;
|
||||
SqlPoolStorage::$sql_pool->put($conn);
|
||||
SqlPoolStorage::$sql_pool->putConnection($conn);
|
||||
return $result;
|
||||
} catch (DBException $e) {
|
||||
Console::warning($e->getMessage());
|
||||
@@ -89,20 +93,20 @@ class DB
|
||||
if (!is_array($params)) $params = [$params];
|
||||
Console::debug("MySQL: " . $line . " | " . implode(", ", $params));
|
||||
try {
|
||||
$conn = SqlPoolStorage::$sql_pool->get();
|
||||
$conn = SqlPoolStorage::$sql_pool->getConnection();
|
||||
if ($conn === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
SqlPoolStorage::$sql_pool->putConnection(null);
|
||||
throw new DbException("无法连接SQL!" . $line);
|
||||
}
|
||||
$ps = $conn->prepare($line);
|
||||
if ($ps === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
SqlPoolStorage::$sql_pool->putConnection(null);
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
throw new DbException("SQL语句查询错误," . $line . ",错误信息:" . $conn->error);
|
||||
} else {
|
||||
if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) {
|
||||
var_dump($ps);
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
SqlPoolStorage::$sql_pool->putConnection(null);
|
||||
throw new DbException("语句查询错误!返回的不是 PDOStatement" . $line);
|
||||
}
|
||||
if ($params == []) $result = $ps->execute();
|
||||
@@ -110,11 +114,11 @@ class DB
|
||||
$result = $ps->execute([$params]);
|
||||
} else $result = $ps->execute($params);
|
||||
if ($result !== true) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
SqlPoolStorage::$sql_pool->putConnection(null);
|
||||
throw new DBException("语句[$line]错误!" . $ps->errorInfo()[2]);
|
||||
//echo json_encode(debug_backtrace(), 128 | 256);
|
||||
}
|
||||
SqlPoolStorage::$sql_pool->put($conn);
|
||||
SqlPoolStorage::$sql_pool->putConnection($conn);
|
||||
return $ps->fetchAll($fetch_mode);
|
||||
}
|
||||
} catch (DbException $e) {
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace ZM\DB;
|
||||
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
/**
|
||||
* Class DeleteBody
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class DeleteBody
|
||||
{
|
||||
use WhereBody;
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace ZM\DB;
|
||||
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
/**
|
||||
* Class InsertBody
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class InsertBody
|
||||
{
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace ZM\DB;
|
||||
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
/**
|
||||
* Class SelectBody
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class SelectBody
|
||||
{
|
||||
use WhereBody;
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
|
||||
namespace ZM\DB;
|
||||
|
||||
|
||||
/**
|
||||
* Class Table
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class Table
|
||||
{
|
||||
private $table_name;
|
||||
|
||||
@@ -6,6 +6,11 @@ namespace ZM\DB;
|
||||
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
/**
|
||||
* Class UpdateBody
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
class UpdateBody
|
||||
{
|
||||
use WhereBody;
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
namespace ZM\DB;
|
||||
|
||||
|
||||
/**
|
||||
* Trait WhereBody
|
||||
* @package ZM\DB
|
||||
* @deprecated This will delete in 2.6 or future version, use \ZM\MySQL\MySQLManager::getConnection() instead
|
||||
*/
|
||||
trait WhereBody
|
||||
{
|
||||
protected $where_thing = [];
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Error;
|
||||
use Exception;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Store\LightCacheInside;
|
||||
@@ -117,7 +119,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, 拒绝执行此方法。");
|
||||
@@ -137,17 +139,23 @@ class EventDispatcher
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 ruleFunc 为真,继续执行方法本身 ...");
|
||||
if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
|
||||
$middlewares = EventManager::$middleware_map[$q_c][$q_f];
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware:" . implode(", ", $middlewares));
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware:" . implode(", ", array_map(function ($x) { return $x->middleware; }, $middlewares)));
|
||||
$before_result = true;
|
||||
$r = [];
|
||||
foreach ($middlewares as $k => $middleware) {
|
||||
if (!isset(EventManager::$middlewares[$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
|
||||
$middleware_obj = EventManager::$middlewares[$middleware];
|
||||
if (!isset(EventManager::$middlewares[$middleware->middleware])) {
|
||||
if ((ZMConfig::get("global", "runtime")["middleware_error_policy"] ?? 1) == 1)
|
||||
throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware->middleware}\"!");
|
||||
else
|
||||
continue;
|
||||
}
|
||||
$middleware_obj = EventManager::$middlewares[$middleware->middleware];
|
||||
$before = $middleware_obj["class"];
|
||||
//var_dump($middleware_obj);
|
||||
$r[$k] = new $before();
|
||||
$r[$k]->class = $q_c;
|
||||
$r[$k]->method = $q_f;
|
||||
$r[$k]->middleware = $middleware;
|
||||
if (isset($middleware_obj["before"])) {
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ...");
|
||||
$rs = $middleware_obj["before"];
|
||||
@@ -173,7 +181,7 @@ class EventDispatcher
|
||||
}
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ...");
|
||||
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware];
|
||||
if (!isset($middleware_obj["exceptions"])) continue;
|
||||
foreach ($middleware_obj["exceptions"] as $name => $method) {
|
||||
if ($e instanceof $name) {
|
||||
@@ -185,8 +193,9 @@ class EventDispatcher
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
|
||||
$cnts = count($middlewares) - 1;
|
||||
for ($i = $cnts; $i >= 0; --$i) {
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware];
|
||||
if (isset($middleware_obj["after"], $r[$i])) {
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ...");
|
||||
$r[$i]->{$middleware_obj["after"]}(...$params);
|
||||
@@ -207,4 +216,18 @@ class EventDispatcher
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getEid(): int {
|
||||
return $this->eid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClass(): string {
|
||||
return $this->class;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ class EventManager
|
||||
self::$middlewares = $parser->getMiddlewares();
|
||||
self::$middleware_map = $parser->getMiddlewareMap();
|
||||
self::$req_mapping = $parser->getReqMapping();
|
||||
$parser->verifyMiddlewares();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
45
src/ZM/Event/EventTracer.php
Normal file
45
src/ZM/Event/EventTracer.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
class EventTracer
|
||||
{
|
||||
/**
|
||||
* 获取当前注解事件的注解类,如CQCommand对象
|
||||
* @return AnnotationBase|null
|
||||
*/
|
||||
public static function getCurrentEvent() {
|
||||
$list = debug_backtrace();
|
||||
foreach ($list as $v) {
|
||||
if ((($v["object"] ?? null) instanceof EventDispatcher) && $v["function"] == "dispatchEvent") {
|
||||
return $v["args"][0];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前注解事件的中间件列表
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public static function getCurrentEventMiddlewares() {
|
||||
$current_event = self::getCurrentEvent();
|
||||
if (!isset($current_event->class, $current_event->method)) return null;
|
||||
return EventManager::$middleware_map[$current_event->class][$current_event->method] ?? [];
|
||||
}
|
||||
|
||||
public static function getEventTraceList() {
|
||||
$result = [];
|
||||
$list = debug_backtrace();
|
||||
foreach ($list as $v) {
|
||||
if ((($v["object"] ?? null) instanceof EventDispatcher) && $v["function"] == "dispatchEvent") {
|
||||
$result[] = $v["args"][0];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ 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;
|
||||
|
||||
@@ -16,11 +18,16 @@ use ZM\Event\SwooleEvent;
|
||||
*/
|
||||
class OnBeforeReload implements SwooleEvent
|
||||
{
|
||||
public function onCall() {
|
||||
public function onCall(Server $server) {
|
||||
Console::info(Console::setColor("Reloading server...", "gold"));
|
||||
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
Process::kill(zm_atomic("_#worker_".$i)->get(), SIGUSR1);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,20 @@
|
||||
namespace ZM\Event\SwooleEvent;
|
||||
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\ZMBuf;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\SignalListener;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
/**
|
||||
* Class OnManagerStart
|
||||
@@ -20,10 +28,67 @@ use ZM\Utils\SignalListener;
|
||||
*/
|
||||
class OnManagerStart implements SwooleEvent
|
||||
{
|
||||
/** @var null|Process */
|
||||
public static $process = null;
|
||||
|
||||
public function onCall(Server $server) {
|
||||
Console::debug("Calling onManagerStart event(1)");
|
||||
if (!Framework::$argv["disable-safe-exit"]) {
|
||||
SignalListener::signalManager();
|
||||
}
|
||||
self::$process = new Process(function() {
|
||||
if (Framework::$argv["watch"]) {
|
||||
if (extension_loaded('inotify')) {
|
||||
Console::info("Enabled File watcher, framework will reload automatically.");
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
Framework::$server->inotify = $fd = inotify_init();
|
||||
$this->addWatcher(DataProvider::getSourceRootDir() . "/src", $fd);
|
||||
Event::add($fd, function () use ($fd) {
|
||||
$r = inotify_read($fd);
|
||||
Console::verbose("File updated: " . $r[0]["name"]);
|
||||
ZMUtil::reload();
|
||||
});
|
||||
} else {
|
||||
Console::warning(zm_internal_errcode("E00024") . "You have not loaded \"inotify\" extension, please install first.");
|
||||
}
|
||||
}
|
||||
if (Framework::$argv["interact"]) {
|
||||
Console::info("Interact mode");
|
||||
ZMBuf::$terminal = $r = STDIN;
|
||||
Event::add($r, function () use ($r) {
|
||||
$fget = fgets($r);
|
||||
if ($fget === false) {
|
||||
Event::del($r);
|
||||
return;
|
||||
}
|
||||
$var = trim($fget);
|
||||
if ($var == "stop") Event::del($r);
|
||||
try {
|
||||
Terminal::executeCommand($var);
|
||||
} catch (Exception $e) {
|
||||
Console::error(zm_internal_errcode("E00025") . "Uncaught exception " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
} catch (Error $e) {
|
||||
Console::error(zm_internal_errcode("E00025") . "Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
self::$process->set(['enable_coroutine' => true]);
|
||||
self::$process->start();
|
||||
Console::verbose("进程 Manager 已启动");
|
||||
}
|
||||
|
||||
private function addWatcher($maindir, $fd) {
|
||||
$dir = scandir($maindir);
|
||||
if ($dir[0] == ".") {
|
||||
unset($dir[0], $dir[1]);
|
||||
}
|
||||
foreach ($dir as $subdir) {
|
||||
if (is_dir($maindir . "/" . $subdir)) {
|
||||
Console::debug("添加监听目录:" . $maindir . "/" . $subdir);
|
||||
inotify_add_watch($fd, $maindir . "/" . $subdir, IN_ATTRIB | IN_ISDIR);
|
||||
$this->addWatcher($maindir . "/" . $subdir, $fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,11 @@
|
||||
namespace ZM\Event\SwooleEvent;
|
||||
|
||||
|
||||
use Swoole\Process;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
/**
|
||||
* Class OnManagerStop
|
||||
@@ -16,6 +18,14 @@ use ZM\Event\SwooleEvent;
|
||||
class OnManagerStop implements SwooleEvent
|
||||
{
|
||||
public function onCall() {
|
||||
if (OnManagerStart::$process !== null) {
|
||||
if (Process::kill(OnManagerStart::$process->pid, 0)) {
|
||||
Process::kill(OnManagerStart::$process->pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
Console::verbose("进程 Manager 已停止!");
|
||||
if (file_exists(DataProvider::getWorkingDir()."/.daemon_pid")) {
|
||||
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,19 +4,14 @@
|
||||
namespace ZM\Event\SwooleEvent;
|
||||
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
use Swoole\Event;
|
||||
use Swoole\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\ZMBuf;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\SignalListener;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
/**
|
||||
* Class OnStart
|
||||
@@ -26,55 +21,18 @@ use ZM\Utils\ZMUtil;
|
||||
class OnStart implements SwooleEvent
|
||||
{
|
||||
public function onCall(Server $server) {
|
||||
Console::debug("Calling onStart event(1)");
|
||||
if (!Framework::$argv["disable-safe-exit"]) {
|
||||
SignalListener::signalMaster($server);
|
||||
}
|
||||
if (Framework::$argv["watch"]) {
|
||||
if (extension_loaded('inotify')) {
|
||||
Console::info("Enabled File watcher, framework will reload automatically.");
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
Framework::$server->inotify = $fd = inotify_init();
|
||||
$this->addWatcher(DataProvider::getSourceRootDir() . "/src", $fd);
|
||||
Event::add($fd, function () use ($fd) {
|
||||
$r = inotify_read($fd);
|
||||
Console::verbose("File updated: " . $r[0]["name"]);
|
||||
ZMUtil::reload();
|
||||
});
|
||||
} else {
|
||||
Console::warning(zm_internal_errcode("E00024") . "You have not loaded \"inotify\" extension, please install first.");
|
||||
}
|
||||
}
|
||||
if (Framework::$argv["interact"]) {
|
||||
ZMBuf::$terminal = $r = STDIN;
|
||||
Event::add($r, function () use ($r) {
|
||||
$fget = fgets($r);
|
||||
if ($fget === false) {
|
||||
Event::del($r);
|
||||
return;
|
||||
}
|
||||
$var = trim($fget);
|
||||
try {
|
||||
Terminal::executeCommand($var);
|
||||
} catch (Exception $e) {
|
||||
Console::error(zm_internal_errcode("E00025") . "Uncaught exception " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
} catch (Error $e) {
|
||||
Console::error(zm_internal_errcode("E00025") . "Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
}
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private function addWatcher($maindir, $fd) {
|
||||
$dir = scandir($maindir);
|
||||
if ($dir[0] == ".") {
|
||||
unset($dir[0], $dir[1]);
|
||||
}
|
||||
foreach ($dir as $subdir) {
|
||||
if (is_dir($maindir . "/" . $subdir)) {
|
||||
Console::debug("添加监听目录:" . $maindir . "/" . $subdir);
|
||||
inotify_add_watch($fd, $maindir . "/" . $subdir, IN_ATTRIB | IN_ISDIR);
|
||||
$this->addWatcher($maindir . "/" . $subdir, $fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,11 +4,13 @@
|
||||
namespace ZM\Event\SwooleEvent;
|
||||
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Server;
|
||||
use Swoole\Timer;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Store\LightCacheInside;
|
||||
|
||||
/**
|
||||
* Class OnWorkerExit
|
||||
@@ -19,6 +21,11 @@ class OnWorkerExit implements SwooleEvent
|
||||
{
|
||||
public function onCall(Server $server, $worker_id) {
|
||||
Timer::clearAll();
|
||||
foreach((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
|
||||
if (($v["worker_id"] ?? -1) == $worker_id && isset($v["coroutine"])) {
|
||||
Coroutine::resume($v["coroutine"]);
|
||||
}
|
||||
}
|
||||
Console::info("正在结束 Worker #".$worker_id.",进程内可能有事务在运行...");
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ use PDO;
|
||||
use ReflectionException;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Process;
|
||||
use Swoole\Server;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
@@ -25,13 +24,15 @@ use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Event\SwooleEvent;
|
||||
use ZM\Exception\DbException;
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
use ZM\Framework;
|
||||
use ZM\Module\QQBot;
|
||||
use ZM\MySQL\MySQLPool;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\Manager\ModuleManager;
|
||||
use ZM\Utils\SignalListener;
|
||||
|
||||
/**
|
||||
@@ -42,6 +43,7 @@ use ZM\Utils\SignalListener;
|
||||
class OnWorkerStart implements SwooleEvent
|
||||
{
|
||||
public function onCall(Server $server, $worker_id) {
|
||||
Console::debug("Calling onWorkerStart event(1)");
|
||||
if (!Framework::$argv["disable-safe-exit"]) {
|
||||
SignalListener::signalWorker($server, $worker_id);
|
||||
}
|
||||
@@ -54,9 +56,11 @@ class OnWorkerStart implements SwooleEvent
|
||||
try {
|
||||
register_shutdown_function(function () use ($server) {
|
||||
$error = error_get_last();
|
||||
if (($error["type"] ?? -1) != 0) {
|
||||
if (($error["type"] ?? 0) != 0) {
|
||||
Console::error(zm_internal_errcode("E00027") . "Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
|
||||
zm_dump($error);
|
||||
} elseif (!isset($error["type"])) {
|
||||
return;
|
||||
}
|
||||
//DataProvider::saveBuffer();
|
||||
/** @var Server $server */
|
||||
@@ -68,42 +72,9 @@ class OnWorkerStart implements SwooleEvent
|
||||
Framework::$server = $server;
|
||||
//ZMBuf::resetCache(); //清空变量缓存
|
||||
//ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行
|
||||
foreach ($server->connections as $v) {
|
||||
$server->close($v);
|
||||
}
|
||||
|
||||
//TODO: 单独抽出来MySQL和Redis连接池
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] != "") {
|
||||
if (SqlPoolStorage::$sql_pool !== null) {
|
||||
SqlPoolStorage::$sql_pool->close();
|
||||
SqlPoolStorage::$sql_pool = null;
|
||||
}
|
||||
Console::info("新建SQL连接池中");
|
||||
ob_start();
|
||||
phpinfo(); //这个phpinfo是有用的,不能删除
|
||||
$str = ob_get_clean();
|
||||
$str = explode("\n", $str);
|
||||
foreach ($str as $v) {
|
||||
$v = trim($v);
|
||||
if ($v == "") continue;
|
||||
if (mb_strpos($v, "API Extensions") === false) continue;
|
||||
if (mb_strpos($v, "pdo_mysql") === false) {
|
||||
throw new DbException(zm_internal_errcode("E00028") . "未安装 mysqlnd php-mysql扩展。");
|
||||
}
|
||||
}
|
||||
$sql = ZMConfig::get("global", "sql_config");
|
||||
SqlPoolStorage::$sql_pool = new PDOPool((new PDOConfig())
|
||||
->withHost($sql["sql_host"])
|
||||
->withPort($sql["sql_port"])
|
||||
// ->withUnixSocket('/tmp/mysql.sock')
|
||||
->withDbName($sql["sql_database"])
|
||||
->withCharset('utf8mb4')
|
||||
->withUsername($sql["sql_username"])
|
||||
->withPassword($sql["sql_password"])
|
||||
->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
|
||||
);
|
||||
DB::initTableList();
|
||||
}
|
||||
$this->initMySQLPool();
|
||||
|
||||
// 开箱即用的Redis
|
||||
$redis = ZMConfig::get("global", "redis_config");
|
||||
@@ -114,10 +85,7 @@ class OnWorkerStart implements SwooleEvent
|
||||
|
||||
$this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
|
||||
|
||||
//echo json_encode(debug_backtrace(), 128|256);
|
||||
|
||||
EventManager::registerTimerTick(); //启动计时器
|
||||
//ZMBuf::unsetCache("wait_start");
|
||||
set_coroutine_params(["server" => $server, "worker_id" => $worker_id]);
|
||||
$dispatcher = new EventDispatcher(OnStart::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
@@ -163,24 +131,9 @@ class OnWorkerStart implements SwooleEvent
|
||||
* @throws Exception
|
||||
*/
|
||||
private function loadAnnotations() {
|
||||
//加载phar包
|
||||
/*Console::debug("加载外部phar包中");
|
||||
$dir = DataProvider::getWorkingDir() . "/resources/package/";
|
||||
if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll();
|
||||
if (is_dir($dir)) {
|
||||
$list = scandir($dir);
|
||||
unset($list[0], $list[1]);
|
||||
foreach ($list as $v) {
|
||||
if (is_dir($dir . $v)) continue;
|
||||
if (pathinfo($dir . $v, 4) == "phar") {
|
||||
Console::debug("加载Phar: " . $dir . $v . " 中");
|
||||
require_once($dir . $v);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//加载各个模块的注解类,以及反射
|
||||
Console::debug("检索Module中");
|
||||
Console::debug("Mapping annotations");
|
||||
$parser = new AnnotationParser();
|
||||
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
|
||||
foreach ($composer["autoload"]["psr-4"] as $k => $v) {
|
||||
@@ -191,14 +144,12 @@ class OnWorkerStart implements SwooleEvent
|
||||
$parser->addRegisterPath(DataProvider::getSourceRootDir() . "/" . $v . "/", trim($k, "\\"));
|
||||
}
|
||||
}
|
||||
$parser->registerMods();
|
||||
EventManager::loadEventByParser($parser); //加载事件
|
||||
|
||||
//加载自定义的全局函数
|
||||
Console::debug("加载自定义上下文中...");
|
||||
Console::debug("Loading context class...");
|
||||
$context_class = ZMConfig::get("global", "context_class");
|
||||
if (!is_a($context_class, ContextInterface::class, true)) {
|
||||
throw new ZMException(zm_internal_errcode("E00032") ."Context class must implemented from ContextInterface!");
|
||||
throw new ZMKnownException("E00032", "Context class must implemented from ContextInterface!");
|
||||
}
|
||||
|
||||
//加载插件
|
||||
@@ -207,6 +158,7 @@ class OnWorkerStart implements SwooleEvent
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
|
||||
if ($obb_onebot["status"]) {
|
||||
Console::debug("OneBot support enabled, listening OneBot event(3).");
|
||||
$obj = new OnSwooleEvent();
|
||||
$obj->class = QQBot::class;
|
||||
$obj->method = 'handleByEvent';
|
||||
@@ -221,6 +173,79 @@ class OnWorkerStart implements SwooleEvent
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: 编写加载外部插件的方式
|
||||
// 检查是否允许热加载phar模块,允许的话将遍历phar内的文件
|
||||
$plugin_enable_hotload = ZMConfig::get("global", "module_loader")["enable_hotload"] ?? false;
|
||||
if ($plugin_enable_hotload) {
|
||||
$list = ModuleManager::getPackedModules();
|
||||
foreach($list as $k => $v) {
|
||||
if (\server()->worker_id === 0) Console::info("Loading packed module: ".$k);
|
||||
require_once $v["phar-path"];
|
||||
$func = "loader".$v["generated-id"];
|
||||
$func();
|
||||
$parser->addRegisterPath("phar://".$v["phar-path"]."/".$v["module-root-path"], $v["namespace"]);
|
||||
}
|
||||
}
|
||||
|
||||
$parser->registerMods();
|
||||
EventManager::loadEventByParser($parser); //加载事件
|
||||
}
|
||||
|
||||
private function initMySQLPool() {
|
||||
if (SqlPoolStorage::$sql_pool !== null) {
|
||||
SqlPoolStorage::$sql_pool->close();
|
||||
SqlPoolStorage::$sql_pool = null;
|
||||
}
|
||||
$real_conf = [];
|
||||
if (isset(ZMConfig::get("global", "sql_config")["sql_host"])) {
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] != "") {
|
||||
if (\server()->worker_id === 0) {
|
||||
Console::warning("使用 'sql_config' 配置项和 DB 数据库查询构造器进行查询数据库可能会在下一个大版本中废弃,请使用 'mysql_config' 搭配 doctrine dbal 使用!");
|
||||
Console::warning("详见: `https://framework.zhamao.xin/`");
|
||||
}
|
||||
$origin_conf = ZMConfig::get("global", "sql_config");
|
||||
$real_conf = [
|
||||
"host" => $origin_conf["sql_host"],
|
||||
"port" => $origin_conf["sql_port"],
|
||||
"username" => $origin_conf["sql_username"],
|
||||
"password" => $origin_conf["sql_password"],
|
||||
"dbname" => $origin_conf["sql_database"],
|
||||
"options" => $origin_conf["sql_options"],
|
||||
'unix_socket' => null,
|
||||
'charset' => 'utf8mb4',
|
||||
'pool_size' => 64
|
||||
];
|
||||
}
|
||||
}
|
||||
if (isset(ZMConfig::get("global", "mysql_config")["host"])) {
|
||||
if (ZMConfig::get("global", "mysql_config")["host"] != "") {
|
||||
$real_conf = ZMConfig::get("global", "mysql_config");
|
||||
}
|
||||
}
|
||||
if (!empty($real_conf)) {
|
||||
Console::info("Connecting to MySQL pool");
|
||||
ob_start();
|
||||
phpinfo(); //这个phpinfo是有用的,不能删除
|
||||
$str = ob_get_clean();
|
||||
$str = explode("\n", $str);
|
||||
foreach ($str as $v) {
|
||||
$v = trim($v);
|
||||
if ($v == "") continue;
|
||||
if (mb_strpos($v, "API Extensions") === false) continue;
|
||||
if (mb_strpos($v, "pdo_mysql") === false) {
|
||||
throw new DbException(zm_internal_errcode("E00028") . "未安装 mysqlnd php-mysql扩展。");
|
||||
}
|
||||
}
|
||||
SqlPoolStorage::$sql_pool = new MySQLPool((new PDOConfig())
|
||||
->withHost($real_conf["host"])
|
||||
->withPort($real_conf["port"])
|
||||
// ->withUnixSocket('/tmp/mysql.sock')
|
||||
->withDbName($real_conf["dbname"])
|
||||
->withCharset($real_conf["charset"])
|
||||
->withUsername($real_conf["username"])
|
||||
->withPassword($real_conf["password"])
|
||||
->withOptions($real_conf["options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
|
||||
);
|
||||
DB::initTableList($real_conf["dbname"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,6 @@ class OnWorkerStop implements SwooleEvent
|
||||
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
|
||||
LightCache::savePersistence();
|
||||
}
|
||||
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止: ".$server->getWorkerStatus($worker_id));
|
||||
}
|
||||
}
|
||||
10
src/ZM/Exception/AnnotationException.php
Normal file
10
src/ZM/Exception/AnnotationException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
class AnnotationException extends \Doctrine\Common\Annotations\AnnotationException
|
||||
{
|
||||
|
||||
}
|
||||
@@ -6,9 +6,9 @@ namespace ZM\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class DbException extends ZMException
|
||||
class DbException extends ZMKnownException
|
||||
{
|
||||
public function __construct($message = "", $code = 0, Throwable $previous = null) {
|
||||
parent::__construct(zm_internal_errcode("E00043") . $message, $code, $previous);
|
||||
parent::__construct("E00043", $message, $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/ZM/Exception/InitException.php
Normal file
10
src/ZM/Exception/InitException.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
class InitException extends ZMException
|
||||
{
|
||||
|
||||
}
|
||||
14
src/ZM/Exception/LightCacheException.php
Normal file
14
src/ZM/Exception/LightCacheException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Throwable;
|
||||
|
||||
class LightCacheException extends ZMKnownException
|
||||
{
|
||||
public function __construct($err_code, $message = "", $code = 0, Throwable $previous = null) {
|
||||
parent::__construct($err_code, $message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace ZM\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ZMException extends Exception
|
||||
abstract class ZMException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
14
src/ZM/Exception/ZMKnownException.php
Normal file
14
src/ZM/Exception/ZMKnownException.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Throwable;
|
||||
|
||||
class ZMKnownException extends ZMException
|
||||
{
|
||||
public function __construct($err_code, $message = "", $code = 0, Throwable $previous = null) {
|
||||
parent::__construct(zm_internal_errcode($err_code) . $message, $code, $previous);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ namespace ZM;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Phar;
|
||||
use Swoole\Server\Port;
|
||||
use Throwable;
|
||||
use ZM\Config\ZMConfig;
|
||||
@@ -24,6 +25,33 @@ use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
use function date_default_timezone_set, define;
|
||||
use function exec;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function file_put_contents;
|
||||
use function get_class;
|
||||
use function get_included_files;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function mkdir;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function posix_getpid;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function swoole_cpu_num;
|
||||
use function trim;
|
||||
use function uuidgen;
|
||||
use function working_dir;
|
||||
use function zm_atomic;
|
||||
use function zm_internal_errcode;
|
||||
|
||||
class Framework
|
||||
{
|
||||
@@ -55,7 +83,7 @@ class Framework
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args["env"] ?? "");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nError path: " . DataProvider::getSourceRootDir() . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
|
||||
//定义常量
|
||||
@@ -107,11 +135,19 @@ class Framework
|
||||
define("ZM_WORKER_NUM", $worker);
|
||||
|
||||
ZMAtomic::init();
|
||||
$out["working_dir"] = DataProvider::getWorkingDir();
|
||||
|
||||
// 打印初始信息
|
||||
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
|
||||
if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
else $out["worker"] = $this->server_set["worker_num"];
|
||||
if (!isset($this->server_set["worker_num"])) {
|
||||
if ((ZMConfig::get("global", "runtime")["swoole_server_mode"] ?? SWOOLE_PROCESS) == SWOOLE_PROCESS) {
|
||||
$out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
} else {
|
||||
$out["single_proc_mode"] = "true";
|
||||
}
|
||||
} else {
|
||||
$out["worker"] = $this->server_set["worker_num"];
|
||||
}
|
||||
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
|
||||
$out["log_level"] = Console::getLevel();
|
||||
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
|
||||
@@ -120,13 +156,14 @@ class Framework
|
||||
if (isset($this->server_set["task_worker_num"])) {
|
||||
$out["task_worker"] = $this->server_set["task_worker_num"];
|
||||
}
|
||||
if (!isset($this->server_set["pid_file"])) {
|
||||
$this->server_set["pid_file"] = ZMConfig::get("crash_dir") . ".zm.pid";
|
||||
}
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
|
||||
$conf = ZMConfig::get("global", "sql_config");
|
||||
$out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"];
|
||||
}
|
||||
if ((ZMConfig::get("global", "mysql_config")["host"] ?? "") !== "") {
|
||||
$conf = ZMConfig::get("global", "mysql_config");
|
||||
$out["mysql"] = $conf["dbname"] . "@" . $conf["host"] . ":" . $conf["port"];
|
||||
}
|
||||
if (ZMConfig::get("global", "redis_config")["host"] !== "") {
|
||||
$conf = ZMConfig::get("global", "redis_config");
|
||||
$out["redis_pool"] = $conf["host"] . ":" . $conf["port"];
|
||||
@@ -138,19 +175,23 @@ class Framework
|
||||
$out["php_version"] = PHP_VERSION;
|
||||
$out["swoole_version"] = SWOOLE_VERSION;
|
||||
}
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal");
|
||||
$out["terminal"] = $conf["host"] . ":" . $conf["port"];
|
||||
}
|
||||
|
||||
$out["working_dir"] = DataProvider::getWorkingDir();
|
||||
self::printProps($out, $tty_width, $args["log-theme"] === null);
|
||||
if ($args["preview"]) {
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
|
||||
self::$server = new Server(
|
||||
ZMConfig::get("global", "host"),
|
||||
ZMConfig::get("global", "port"),
|
||||
ZMConfig::get("global", "runtime")["swoole_server_mode"] ?? SWOOLE_PROCESS
|
||||
);
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal") ?? [
|
||||
@@ -215,7 +256,7 @@ class Framework
|
||||
|
||||
$r = ob_get_clean();
|
||||
if (!empty($r)) $serv->send($fd, $r);
|
||||
if (!in_array(trim($data), ['r', 'reload', 'stop'])) $serv->send($fd, ">>> ");
|
||||
if (!in_array(trim($data), ['r', 'reload'])) $serv->send($fd, ">>> ");
|
||||
});
|
||||
|
||||
$port->on('close', function ($serv, $fd) {
|
||||
@@ -291,9 +332,7 @@ class Framework
|
||||
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
|
||||
}
|
||||
$motd = explode("\n", $motd);
|
||||
foreach ($motd as $k => $v) {
|
||||
$motd[$k] = substr($v, 0, $tty_width);
|
||||
}
|
||||
foreach ($motd as $k => $v) $motd[$k] = substr($v, 0, $tty_width);
|
||||
$motd = implode("\n", $motd);
|
||||
echo $motd;
|
||||
}
|
||||
@@ -309,8 +348,18 @@ class Framework
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @noinspection PhpIncludeInspection
|
||||
*/
|
||||
private function loadServerEvents() {
|
||||
$r = exec(PHP_BINARY . " " . DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php", $output, $result_code);
|
||||
if (Phar::running() !== "") {
|
||||
ob_start();
|
||||
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php";
|
||||
$r = ob_get_clean();
|
||||
$result_code = 0;
|
||||
} else {
|
||||
$r = exec(PHP_BINARY . " " . DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php", $output, $result_code);
|
||||
}
|
||||
if ($result_code !== 0) {
|
||||
Console::error("Parsing code error!");
|
||||
exit(1);
|
||||
@@ -432,7 +481,7 @@ class Framework
|
||||
}
|
||||
}
|
||||
}
|
||||
$global_hook = ZMConfig::get("global", 'runtime')['swoole_coroutine_hook_flags'] ?? SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL);
|
||||
$global_hook = ZMConfig::get("global", 'runtime')['swoole_coroutine_hook_flags'] ?? (SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL));
|
||||
if ($coroutine_mode && $global_hook === false) Runtime::enableCoroutine(true, $global_hook);
|
||||
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
|
||||
}
|
||||
@@ -440,7 +489,7 @@ class Framework
|
||||
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
|
||||
$tmp_line = $k . ": " . $v;
|
||||
//Console::info("写入[".$tmp_line."]");
|
||||
if (strlen($tmp_line) >= $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
|
||||
if (strlen($tmp_line) > $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
|
||||
$title_strlen = strlen($k . ": ");
|
||||
$content_len = $line_width[$current_line] - $title_strlen;
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ class ModulePacker
|
||||
$this->addFiles(); //添加文件
|
||||
$this->addLightCacheStore(); //保存light-cache-store指定的项
|
||||
$this->addModuleConfig(); //生成module-config.json
|
||||
$this->addZMDataFiles(); //添加需要保存的zm_data下的目录或文件
|
||||
$this->addEntry(); //生成模块的入口文件module_entry.php
|
||||
|
||||
$this->phar->stopBuffering();
|
||||
@@ -121,7 +122,13 @@ class ModulePacker
|
||||
}
|
||||
|
||||
private function generatePharAutoload(): array {
|
||||
return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, true);
|
||||
$pos = strpos($this->module['module-path'], DataProvider::getSourceRootDir().'/');
|
||||
if ($pos === 0) {
|
||||
$path_value = substr($this->module['module-path'], strlen(DataProvider::getSourceRootDir().'/'));
|
||||
} else {
|
||||
throw new ModulePackException(zm_internal_errcode("E99999")); //未定义的错误
|
||||
}
|
||||
return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, $path_value);
|
||||
}
|
||||
|
||||
private function getComposerAutoloadItems(): array {
|
||||
@@ -159,7 +166,7 @@ class ModulePacker
|
||||
foreach ($this->module['light-cache-store'] as $v) {
|
||||
$r = LightCache::get($v);
|
||||
if ($r === null) {
|
||||
Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:`$v` 不存在或值为null,无法为其保存。');
|
||||
Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:' . $v . ' 不存在或值为null,无法为其保存。');
|
||||
} else {
|
||||
$store[$v] = $r;
|
||||
Console::info('打包LightCache持久化项:' . $v);
|
||||
@@ -179,9 +186,10 @@ class ModulePacker
|
||||
'autoload-psr-4' => $this->generatePharAutoload(),
|
||||
'unpack' => [
|
||||
'composer-autoload-items' => $this->getComposerAutoloadItems(),
|
||||
'global-config-override' => !empty($this->module['global-config-override'] ?? []) ? $this->module['global-config-override'] : false
|
||||
'global-config-override' => $this->module['global-config-override'] ?? false
|
||||
],
|
||||
'allow-hotload' => empty($this->module['global-config-override'] ?? []) && !isset($this->module['depends'])
|
||||
'allow-hotload' => $this->module["allow-hotload"] ?? false,
|
||||
'pack-time' => time()
|
||||
];
|
||||
$this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
$this->module_config = $stub_values;
|
||||
@@ -203,4 +211,28 @@ class ModulePacker
|
||||
|
||||
$this->phar->setStub($this->phar->createDefaultStub('module_entry.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ModulePackException
|
||||
*/
|
||||
private function addZMDataFiles() {
|
||||
$base_dir = realpath(DataProvider::getDataFolder());
|
||||
if (is_array($this->module["zm-data-store"] ?? null)) {
|
||||
foreach ($this->module["zm-data-store"] as $v) {
|
||||
if (is_dir($base_dir . '/' . $v)) {
|
||||
$v = rtrim($v, '/');
|
||||
Console::info("Adding external zm_data dir: " . $v);
|
||||
$files = DataProvider::scanDirFiles($base_dir . '/' . $v, true, true);
|
||||
foreach ($files as $single) {
|
||||
$this->phar->addFile($base_dir . '/' . $v . '/' . $single, 'zm_data/' . $v . '/' . $single);
|
||||
}
|
||||
} elseif (is_file($base_dir . '/' . $v)) {
|
||||
Console::info("Add external zm_data file: " . $v);
|
||||
$this->phar->addFile($base_dir . '/' . $v, 'zm_data/' . $v);
|
||||
} else {
|
||||
throw new ModulePackException(zm_internal_errcode("E00066")."`zmdata-store` 指定的文件或目录不存在");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,216 @@
|
||||
namespace ZM\Module;
|
||||
|
||||
|
||||
use Jelix\Version\VersionComparator;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\ModulePackException;
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\Manager\ModuleManager;
|
||||
|
||||
class ModuleUnpacker
|
||||
{
|
||||
private $module;
|
||||
|
||||
private $module_config = null;
|
||||
|
||||
private $light_cache = null;
|
||||
|
||||
private $unpack_data_files = [];
|
||||
|
||||
public function __construct(array $module) {
|
||||
$this->module = $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包模块
|
||||
* @param bool $override_light_cache
|
||||
* @param bool $override_data_files
|
||||
* @param bool $override_source
|
||||
* @param bool $ignore_depends
|
||||
* @return array
|
||||
* @throws ModulePackException
|
||||
* @throws ZMException
|
||||
*/
|
||||
public function unpack(): array {
|
||||
// TODO: 解包模块到src
|
||||
public function unpack(bool $override_light_cache = false, bool $override_data_files = false, bool $override_source = false, $ignore_depends = false): array {
|
||||
$this->checkConfig();
|
||||
$this->checkDepends($ignore_depends);
|
||||
$this->checkLightCacheStore();
|
||||
$this->checkZMDataStore();
|
||||
|
||||
$this->mergeComposer();
|
||||
$this->copyZMDataStore($override_data_files);
|
||||
$this->copyLightCacheStore($override_light_cache);
|
||||
|
||||
$this->mergeGlobalConfig();
|
||||
$this->copySource($override_source);
|
||||
return $this->module;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块配置文件是否正确地放在phar包的位置中
|
||||
* @return void
|
||||
*/
|
||||
private function checkConfig() {
|
||||
$config = "phar://" . $this->module["phar-path"] . "/" . $this->module["module-root-path"] . "/zm.json";
|
||||
$this->module_config = json_decode(file_get_contents($config), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查模块依赖关系
|
||||
* @param bool $ignore_depends
|
||||
* @throws ModulePackException
|
||||
* @throws ZMException
|
||||
*/
|
||||
private function checkDepends($ignore_depends = false) {
|
||||
$configured = ModuleManager::getConfiguredModules();
|
||||
$depends = $this->module_config["depends"] ?? [];
|
||||
foreach ($depends as $k => $v) {
|
||||
if (!isset($configured[$k]) && !$ignore_depends) {
|
||||
throw new ModulePackException(zm_internal_errcode("E00064") . "模块 " . $this->module_config["name"] . " 依赖的模块 $k 不存在");
|
||||
}
|
||||
$current_ver = $configured[$k]["version"] ?? "1.0";
|
||||
if (!VersionComparator::compareVersionRange($current_ver, $v) && !$ignore_depends) {
|
||||
throw new ModulePackException(zm_internal_errcode("E00063") . "模块 " . $this->module_config["name"] . " 依赖的模块 $k 版本依赖不符合条件(现有版本: " . $current_ver . ", 需求版本: " . $v . ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 light-cache-store 项是否合规
|
||||
* @throws ModulePackException
|
||||
*/
|
||||
private function checkLightCacheStore() {
|
||||
if (isset($this->module_config["light-cache-store"])) {
|
||||
$file = json_decode(file_get_contents("phar://" . $this->module["phar-path"] . "/light_cache_store.json"), true);
|
||||
if ($file === null) throw new ModulePackException(zm_internal_errcode("E00065") . "模块系统检测到打包的模块文件中未含有 `light_cache_store.json` 文件");
|
||||
$this->light_cache = $file;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ModulePackException
|
||||
*/
|
||||
private function checkZMDataStore() {
|
||||
if (is_array($this->module_config["zm-data-store"] ?? null)) {
|
||||
foreach ($this->module_config["zm-data-store"] as $v) {
|
||||
if (!file_exists("phar://" . $this->module["phar-path"] . "/zm_data/" . $v)) {
|
||||
throw new ModulePackException(zm_internal_errcode("E00067") . "压缩包损坏,内部找不到待解压的 zm_data 原始数据");
|
||||
}
|
||||
$file = "phar://" . $this->module["phar-path"] . "/zm_data/" . $v;
|
||||
if (is_dir($file)) {
|
||||
$all = DataProvider::scanDirFiles($file, true, true);
|
||||
foreach ($all as $single) {
|
||||
$this->unpack_data_files[$file . "/" . $single] = DataProvider::getDataFolder() . $v . "/" . $single;
|
||||
}
|
||||
} else {
|
||||
$this->unpack_data_files[$file] = DataProvider::getDataFolder() . $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeComposer() {
|
||||
$composer_file = DataProvider::getWorkingDir() . "/composer.json";
|
||||
if (!file_exists($composer_file)) throw new ModulePackException(zm_internal_errcode("E00068"));
|
||||
$composer = json_decode(file_get_contents($composer_file), true);
|
||||
if (isset($this->module_config["composer-extend-autoload"])) {
|
||||
$autoload = $this->module_config["composer-extend-autoload"];
|
||||
if (isset($autoload["psr-4"])) {
|
||||
Console::info("Adding extended autoload psr-4 for composer");
|
||||
$composer["autoload"]["psr-4"] = isset($composer["autoload"]["psr-4"]) ? array_merge($composer["autoload"]["psr-4"], $autoload["psr-4"]) : $autoload["psr-4"];
|
||||
}
|
||||
if (isset($autoload["files"])) {
|
||||
Console::info("Adding extended autoload file for composer");
|
||||
$composer["autoload"]["files"] = isset($composer["autoload"]["files"]) ? array_merge($composer["autoload"]["files"], $autoload["files"]) : $autoload["files"];
|
||||
}
|
||||
}
|
||||
if (isset($this->module_config["composer-extend-require"])) {
|
||||
foreach ($this->module_config["composer-extend-require"] as $k => $v) {
|
||||
Console::info("Adding extended required composer library: " . $k);
|
||||
if (!isset($composer[$k])) $composer[$k] = $v;
|
||||
}
|
||||
}
|
||||
file_put_contents($composer_file, json_encode($composer, 64 | 128 | 256));
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ModulePackException
|
||||
*/
|
||||
private function copyZMDataStore($override_data) {
|
||||
foreach ($this->unpack_data_files as $k => $v) {
|
||||
$pathinfo = pathinfo($v);
|
||||
if (!is_dir($pathinfo["dirname"])) @mkdir($pathinfo["dirname"], 0755, true);
|
||||
if (is_file($v) && $override_data !== true) {
|
||||
Console::info("Skipping zm_data file (not overwriting): " . $v);
|
||||
continue;
|
||||
}
|
||||
Console::info("Copying zm_data file: " . $v);
|
||||
if (copy($k, $v) !== true) {
|
||||
throw new ModulePackException(zm_internal_errcode("E00068") . "Cannot copy file: " . $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function copyLightCacheStore($override) {
|
||||
$r = ZMConfig::get('global', 'light_cache') ?? [
|
||||
'size' => 512, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, //单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => DataProvider::getDataFolder() . '_cache.json',
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
LightCache::init($r);
|
||||
foreach (($this->light_cache ?? []) as $k => $v) {
|
||||
if (LightCache::isset($k) && $override !== true) continue;
|
||||
LightCache::addPersistence($k);
|
||||
LightCache::set($k, $v);
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeGlobalConfig() {
|
||||
if ($this->module["unpack"]["global-config-override"] !== false) {
|
||||
$prompt = !is_string($this->module["unpack"]["global-config-override"]) ? "请根据模块提供者提供的要求进行修改 global.php 中对应的配置项" : $this->module["unpack"]["global-config-override"];
|
||||
Console::warning("模块作者要求用户手动修改 global.php 配置文件中的项目:");
|
||||
Console::warning("*" . $prompt);
|
||||
echo Console::setColor("请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N] ", "gold");
|
||||
$r = strtolower(trim(fgets(STDIN)));
|
||||
switch ($r) {
|
||||
case "y":
|
||||
system("vim " . escapeshellarg(DataProvider::getWorkingDir() . "/config/global.php") . " > `tty`");
|
||||
Console::info("已使用 vim 修改!");
|
||||
break;
|
||||
case "e":
|
||||
echo Console::setColor("请修改后文件点击回车即可继续 [Enter] ", "gold");
|
||||
fgets(STDIN);
|
||||
break;
|
||||
case "n":
|
||||
Console::info("暂不修改 global.php");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function copySource(bool $override_source) {
|
||||
$origin_base = "phar://" . $this->module["phar-path"] . "/" . $this->module["module-root-path"];
|
||||
$dir = DataProvider::scanDirFiles($origin_base, true, true);
|
||||
$base = DataProvider::getSourceRootDir() . "/" . $this->module["module-root-path"];
|
||||
foreach ($dir as $v) {
|
||||
$info = pathinfo($base . "/" . $v);
|
||||
if (!is_dir($info["dirname"])) {
|
||||
@mkdir($info["dirname"], 0755, true);
|
||||
}
|
||||
if (is_file($base . "/" . $v) && $override_source !== true) {
|
||||
Console::info("Skipping source file (not overwriting): " . $v);
|
||||
continue;
|
||||
}
|
||||
Console::info("Releasing source file: " . $this->module["module-root-path"] . "/" . $v);
|
||||
|
||||
if (copy($origin_base . "/" . $v, $base . "/" . $v) !== true) {
|
||||
throw new ModulePackException(zm_internal_errcode("E00068") . "Cannot copy file: " . $v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/ZM/MySQL/MySQLConnection.php
Normal file
96
src/ZM/MySQL/MySQLConnection.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
use Doctrine\DBAL\Driver\Connection;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use Swoole\Database\PDOProxy;
|
||||
use Swoole\Database\PDOStatementProxy;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\DbException;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
|
||||
class MySQLConnection implements Connection
|
||||
{
|
||||
/** @var PDO|PDOProxy */
|
||||
private $conn;
|
||||
|
||||
public function __construct() {
|
||||
Console::debug("Constructing...");
|
||||
$this->conn = SqlPoolStorage::$sql_pool->getConnection();
|
||||
}
|
||||
|
||||
public function prepare($sql, $options = []) {
|
||||
try {
|
||||
Console::debug("Running SQL prepare: ".$sql);
|
||||
$statement = $this->conn->prepare($sql, $options);
|
||||
assert(($statement instanceof PDOStatementProxy) || ($statement instanceof PDOStatement));
|
||||
} catch (PDOException $exception) {
|
||||
throw new DbException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
return new MySQLStatement($statement);
|
||||
}
|
||||
|
||||
public function query(...$args) {
|
||||
try {
|
||||
$statement = $this->conn->query(...$args);
|
||||
assert(($statement instanceof PDOStatementProxy) || ($statement instanceof PDOStatement));
|
||||
} catch (PDOException $exception) {
|
||||
throw new DbException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
return new MySQLStatement($statement);
|
||||
}
|
||||
|
||||
public function quote($value, $type = ParameterType::STRING) {
|
||||
return $this->conn->quote($value, $type);
|
||||
}
|
||||
|
||||
public function exec($sql) {
|
||||
try {
|
||||
Console::debug("Running SQL exec: ".$sql);
|
||||
$statement = $this->conn->exec($sql);
|
||||
assert($statement !== false);
|
||||
return $statement;
|
||||
} catch (PDOException $exception) {
|
||||
throw new DbException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function lastInsertId($name = null) {
|
||||
try {
|
||||
return $name === null ? $this->conn->lastInsertId() : $this->conn->lastInsertId($name);
|
||||
} catch (PDOException $exception) {
|
||||
throw new DbException($exception->getMessage(), $exception->getCode(), $exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function beginTransaction() {
|
||||
return $this->conn->beginTransaction();
|
||||
}
|
||||
|
||||
public function commit() {
|
||||
return $this->conn->commit();
|
||||
}
|
||||
|
||||
public function rollBack() {
|
||||
return $this->conn->rollBack();
|
||||
}
|
||||
|
||||
public function errorCode() {
|
||||
return $this->conn->errorCode();
|
||||
}
|
||||
|
||||
public function errorInfo() {
|
||||
return $this->conn->errorInfo();
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
Console::debug("Destructing!!!");
|
||||
SqlPoolStorage::$sql_pool->putConnection($this->conn);
|
||||
}
|
||||
}
|
||||
40
src/ZM/MySQL/MySQLDriver.php
Normal file
40
src/ZM/MySQL/MySQLDriver.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
use Doctrine\DBAL\Driver as DoctrineDriver;
|
||||
use Doctrine\DBAL\Platforms\MySqlPlatform;
|
||||
use Doctrine\DBAL\Schema\MySqlSchemaManager;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class MySQLDriver implements DoctrineDriver
|
||||
{
|
||||
public function connect(array $params, $username = null, $password = null, array $driverOptions = []) {
|
||||
Console::debug("Requiring new connection");
|
||||
return new MySQLConnection();
|
||||
}
|
||||
|
||||
public function getDatabasePlatform(): MySqlPlatform {
|
||||
return new MySqlPlatform();
|
||||
}
|
||||
|
||||
public function getSchemaManager($conn) {
|
||||
return new MySqlSchemaManager($conn);
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return 'pdo_mysql_pool';
|
||||
}
|
||||
|
||||
public function getDatabase($conn) {
|
||||
$params = ZMConfig::get("global", "mysql_config");
|
||||
|
||||
if (isset($params['dbname'])) {
|
||||
return $params['dbname'];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
15
src/ZM/MySQL/MySQLManager.php
Normal file
15
src/ZM/MySQL/MySQLManager.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
class MySQLManager
|
||||
{
|
||||
/**
|
||||
* @return MySQLWrapper
|
||||
*/
|
||||
public static function getWrapper(): MySQLWrapper {
|
||||
return new MySQLWrapper();
|
||||
}
|
||||
}
|
||||
41
src/ZM/MySQL/MySQLPool.php
Normal file
41
src/ZM/MySQL/MySQLPool.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
/** @noinspection PhpReturnDocTypeMismatchInspection */
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
use PDO;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Database\PDOProxy;
|
||||
|
||||
class MySQLPool extends PDOPool
|
||||
{
|
||||
private $count = 0;
|
||||
|
||||
public function __construct(PDOConfig $config, int $size = self::DEFAULT_SIZE) {
|
||||
parent::__construct($config, $size);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return PDO|PDOProxy|void
|
||||
*/
|
||||
public function getConnection() {
|
||||
$this->count++;
|
||||
return parent::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param PDO|PDOProxy $connection
|
||||
*/
|
||||
public function putConnection($connection) {
|
||||
$this->count--;
|
||||
parent::put($connection);
|
||||
}
|
||||
|
||||
public function getCount() {
|
||||
return $this->count;
|
||||
}
|
||||
}
|
||||
29
src/ZM/MySQL/MySQLQueryBuilder.php
Normal file
29
src/ZM/MySQL/MySQLQueryBuilder.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
class MySQLQueryBuilder extends QueryBuilder
|
||||
{
|
||||
private $wrapper;
|
||||
|
||||
public function __construct(MySQLWrapper $wrapper) {
|
||||
parent::__construct($wrapper->getConnection());
|
||||
$this->wrapper = $wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int|MySQLStatementWrapper
|
||||
* @throws DbException
|
||||
*/
|
||||
public function execute() {
|
||||
if ($this->getType() === self::SELECT) {
|
||||
return $this->wrapper->executeQuery($this->getSQL(), $this->getParameters(), $this->getParameterTypes());
|
||||
}
|
||||
return $this->wrapper->executeStatement($this->getSQL(), $this->getParameters(), $this->getParameterTypes());
|
||||
}
|
||||
}
|
||||
85
src/ZM/MySQL/MySQLStatement.php
Normal file
85
src/ZM/MySQL/MySQLStatement.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
|
||||
use Doctrine\DBAL\Driver\Statement;
|
||||
use Doctrine\DBAL\Driver\StatementIterator;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use Swoole\Database\PDOStatementProxy;
|
||||
|
||||
class MySQLStatement implements Statement, \IteratorAggregate
|
||||
{
|
||||
/** @var PDOStatement|PDOStatementProxy */
|
||||
private $statement;
|
||||
|
||||
public function __construct($obj) {
|
||||
$this->statement = $obj;
|
||||
}
|
||||
|
||||
public function closeCursor() {
|
||||
return $this->statement->closeCursor();
|
||||
}
|
||||
|
||||
public function columnCount() {
|
||||
return $this->statement->columnCount();
|
||||
}
|
||||
|
||||
public function setFetchMode($fetchMode, $arg2 = null, $arg3 = []) {
|
||||
if ($arg2 !== null && $arg3 !== [])
|
||||
return $this->statement->setFetchMode($fetchMode, $arg2, $arg3);
|
||||
elseif ($arg2 !== null && $arg3 === [])
|
||||
return $this->statement->setFetchMode($fetchMode, $arg2);
|
||||
elseif ($arg2 === null && $arg3 !== [])
|
||||
return $this->statement->setFetchMode($fetchMode, $arg2, $arg3);
|
||||
else
|
||||
return $this->statement->setFetchMode($fetchMode);
|
||||
}
|
||||
|
||||
public function fetch($fetchMode = PDO::FETCH_ASSOC, $cursorOrientation = PDO::FETCH_ORI_NEXT, $cursorOffset = 0) {
|
||||
return $this->statement->fetch($fetchMode, $cursorOrientation, $cursorOffset);
|
||||
}
|
||||
|
||||
public function fetchAll($fetchMode = PDO::FETCH_ASSOC, $fetchArgument = null, $ctorArgs = null) {
|
||||
return $this->statement->fetchAll($fetchMode, $fetchArgument, $ctorArgs);
|
||||
}
|
||||
|
||||
public function fetchColumn($columnIndex = 0) {
|
||||
return $this->statement->fetchColumn($columnIndex);
|
||||
}
|
||||
|
||||
public function bindValue($param, $value, $type = ParameterType::STRING) {
|
||||
return $this->statement->bindValue($param, $value, $type);
|
||||
}
|
||||
|
||||
public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null) {
|
||||
return $this->statement->bindParam($param, $variable, $type, $length);
|
||||
}
|
||||
|
||||
public function errorCode() {
|
||||
return $this->statement->errorCode();
|
||||
}
|
||||
|
||||
public function errorInfo() {
|
||||
return $this->statement->errorInfo();
|
||||
}
|
||||
|
||||
public function execute($params = null) {
|
||||
return $this->statement->execute($params);
|
||||
}
|
||||
|
||||
public function rowCount() {
|
||||
return $this->statement->rowCount();
|
||||
}
|
||||
|
||||
public function getIterator() {
|
||||
return new StatementIterator($this);
|
||||
}
|
||||
|
||||
public function current() {
|
||||
return $this->statement->current();
|
||||
}
|
||||
}
|
||||
232
src/ZM/MySQL/MySQLStatementWrapper.php
Normal file
232
src/ZM/MySQL/MySQLStatementWrapper.php
Normal file
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\ResultStatement;
|
||||
use Doctrine\DBAL\ForwardCompatibility\Result;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
class MySQLStatementWrapper
|
||||
{
|
||||
public $stmt;
|
||||
|
||||
public function __construct(?Result $stmt) {
|
||||
$this->stmt = $stmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结果的迭代器
|
||||
* wrapper method
|
||||
* @return ResultStatement
|
||||
*/
|
||||
public function getIterator() {
|
||||
return $this->stmt->getIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取列数
|
||||
* wrapper method
|
||||
* @return int
|
||||
*/
|
||||
public function columnCount() {
|
||||
return $this->stmt->columnCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array|false|mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchNumeric() {
|
||||
try {
|
||||
return $this->stmt->fetchNumeric();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array|false|mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAssociative() {
|
||||
try {
|
||||
return $this->stmt->fetchAssociative();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return false|mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchOne() {
|
||||
try {
|
||||
return $this->stmt->fetchOne();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllNumeric(): array {
|
||||
try {
|
||||
return $this->stmt->fetchAllNumeric();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllAssociative(): array {
|
||||
try {
|
||||
return $this->stmt->fetchAllAssociative();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllKeyValue(): array {
|
||||
try {
|
||||
return $this->stmt->fetchAllKeyValue();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllAssociativeIndexed(): array {
|
||||
try {
|
||||
return $this->stmt->fetchAllAssociativeIndexed();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchFirstColumn(): array {
|
||||
try {
|
||||
return $this->stmt->fetchFirstColumn();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateNumeric(): Traversable {
|
||||
try {
|
||||
return $this->stmt->iterateNumeric();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateAssociative(): Traversable {
|
||||
try {
|
||||
return $this->stmt->iterateAssociative();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateKeyValue(): Traversable {
|
||||
try {
|
||||
return $this->stmt->iterateKeyValue();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateAssociativeIndexed(): Traversable {
|
||||
try {
|
||||
return $this->stmt->iterateAssociativeIndexed();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateColumn(): Traversable {
|
||||
try {
|
||||
return $this->stmt->iterateColumn();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return int
|
||||
* @throws DbException
|
||||
*/
|
||||
public function rowCount() {
|
||||
try {
|
||||
return $this->stmt->rowCount();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
*/
|
||||
public function free(): void {
|
||||
$this->stmt->free();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
578
src/ZM/MySQL/MySQLWrapper.php
Normal file
578
src/ZM/MySQL/MySQLWrapper.php
Normal file
@@ -0,0 +1,578 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
|
||||
namespace ZM\MySQL;
|
||||
|
||||
use Closure;
|
||||
use Doctrine\DBAL\Cache\QueryCacheProfile;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\ParameterType;
|
||||
use Throwable;
|
||||
use Traversable;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
class MySQLWrapper
|
||||
{
|
||||
private $connection;
|
||||
|
||||
/**
|
||||
* MySQLWrapper constructor.
|
||||
* @throws DbException
|
||||
*/
|
||||
public function __construct() {
|
||||
try {
|
||||
$this->connection = DriverManager::getConnection(["driverClass" => MySQLDriver::class]);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabase(): string {
|
||||
return $this->connection->getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoCommit(): bool {
|
||||
return $this->connection->isAutoCommit();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $autoCommit
|
||||
*/
|
||||
public function setAutoCommit($autoCommit) {
|
||||
$this->connection->setAutoCommit($autoCommit);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array|false
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAssociative(string $query, array $params = [], array $types = []) {
|
||||
try {
|
||||
return $this->connection->fetchAssociative($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array|false
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchNumeric(string $query, array $params = [], array $types = []) {
|
||||
try {
|
||||
return $this->connection->fetchNumeric($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return false|mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchOne(string $query, array $params = [], array $types = []): bool {
|
||||
try {
|
||||
return $this->connection->fetchOne($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
*/
|
||||
public function isTransactionActive(): bool {
|
||||
return $this->connection->isTransactionActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $table
|
||||
* @param array $criteria
|
||||
* @param array $types
|
||||
* @return int
|
||||
* @throws DbException
|
||||
*/
|
||||
public function delete($table, array $criteria, array $types = []): int {
|
||||
try {
|
||||
return $this->connection->delete($table, $criteria, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $level
|
||||
* @return int
|
||||
*/
|
||||
public function setTransactionIsolation($level): int {
|
||||
return $this->connection->setTransactionIsolation($level);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return int|null
|
||||
*/
|
||||
public function getTransactionIsolation(): ?int {
|
||||
return $this->connection->getTransactionIsolation();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $table
|
||||
* @param array $data
|
||||
* @param array $criteria
|
||||
* @param array $types
|
||||
* @return int
|
||||
* @throws DbException
|
||||
*/
|
||||
public function update($table, array $data, array $criteria, array $types = []): int {
|
||||
try {
|
||||
return $this->connection->update($table, $data, $criteria, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $table
|
||||
* @param array $data
|
||||
* @param array $types
|
||||
* @return int
|
||||
* @throws DbException
|
||||
*/
|
||||
public function insert($table, array $data, array $types = []): int {
|
||||
try {
|
||||
return $this->connection->insert($table, $data, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $str
|
||||
* @return string
|
||||
*/
|
||||
public function quoteIdentifier($str): string {
|
||||
return $this->connection->quoteIdentifier($str);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $value
|
||||
* @param int $type
|
||||
* @return mixed
|
||||
*/
|
||||
public function quote($value, $type = ParameterType::STRING) {
|
||||
return $this->connection->quote($value, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllNumeric(string $query, array $params = [], array $types = []): array {
|
||||
try {
|
||||
return $this->connection->fetchAllNumeric($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllAssociative(string $query, array $params = [], array $types = []): array {
|
||||
try {
|
||||
return $this->connection->fetchAllAssociative($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllKeyValue(string $query, array $params = [], array $types = []): array {
|
||||
try {
|
||||
return $this->connection->fetchAllKeyValue($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAllAssociativeIndexed(string $query, array $params = [], array $types = []): array {
|
||||
try {
|
||||
return $this->connection->fetchAllAssociativeIndexed($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return array
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchFirstColumn(string $query, array $params = [], array $types = []): array {
|
||||
try {
|
||||
return $this->connection->fetchFirstColumn($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateNumeric(string $query, array $params = [], array $types = []): Traversable {
|
||||
try {
|
||||
return $this->connection->iterateNumeric($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateAssociative(string $query, array $params = [], array $types = []): Traversable {
|
||||
try {
|
||||
return $this->connection->iterateAssociative($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateKeyValue(string $query, array $params = [], array $types = []): Traversable {
|
||||
try {
|
||||
return $this->connection->iterateKeyValue($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateAssociativeIndexed(string $query, array $params = [], array $types = []): Traversable {
|
||||
try {
|
||||
return $this->connection->iterateAssociativeIndexed($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param string $query
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return Traversable
|
||||
* @throws DbException
|
||||
*/
|
||||
public function iterateColumn(string $query, array $params = [], array $types = []): Traversable {
|
||||
try {
|
||||
return $this->connection->iterateColumn($query, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $sql
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @param QueryCacheProfile|null $qcp
|
||||
* @return MySQLStatementWrapper
|
||||
* @throws DbException
|
||||
*/
|
||||
public function executeQuery($sql, array $params = [], $types = [], ?QueryCacheProfile $qcp = null): MySQLStatementWrapper {
|
||||
try {
|
||||
$query = $this->connection->executeQuery($sql, $params, $types, $qcp);
|
||||
return new MySQLStatementWrapper($query);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $sql
|
||||
* @param $params
|
||||
* @param $types
|
||||
* @param QueryCacheProfile $qcp
|
||||
* @return MySQLStatementWrapper
|
||||
* @throws DbException
|
||||
*/
|
||||
public function executeCacheQuery($sql, $params, $types, QueryCacheProfile $qcp): MySQLStatementWrapper {
|
||||
try {
|
||||
$query = $this->connection->executeCacheQuery($sql, $params, $types, $qcp);
|
||||
return new MySQLStatementWrapper($query);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $sql
|
||||
* @param array $params
|
||||
* @param array $types
|
||||
* @return int
|
||||
* @throws DbException
|
||||
*/
|
||||
public function executeStatement($sql, array $params = [], array $types = []): int {
|
||||
try {
|
||||
return $this->connection->executeStatement($sql, $params, $types);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return int
|
||||
*/
|
||||
public function getTransactionNestingLevel(): int {
|
||||
return $this->connection->getTransactionNestingLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param null $name
|
||||
* @return string
|
||||
*/
|
||||
public function lastInsertId($name = null): string {
|
||||
return $this->connection->lastInsertId($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* overwrite method to $this->connection->transactional()
|
||||
* @param Closure $func
|
||||
* @return mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public function transactional(Closure $func) {
|
||||
$this->beginTransaction();
|
||||
try {
|
||||
$res = $func($this);
|
||||
$this->commit();
|
||||
return $res;
|
||||
} catch (Throwable $e) {
|
||||
$this->rollBack();
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $nestTransactionsWithSavepoints
|
||||
* @throws DbException
|
||||
*/
|
||||
public function setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints) {
|
||||
try {
|
||||
$this->connection->setNestTransactionsWithSavepoints($nestTransactionsWithSavepoints);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
*/
|
||||
public function getNestTransactionsWithSavepoints(): bool {
|
||||
return $this->connection->getNestTransactionsWithSavepoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
*/
|
||||
public function beginTransaction(): bool {
|
||||
return $this->connection->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
* @throws DbException
|
||||
*/
|
||||
public function commit(): bool {
|
||||
try {
|
||||
return $this->connection->commit();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
* @throws DbException
|
||||
*/
|
||||
public function rollBack(): bool {
|
||||
try {
|
||||
return $this->connection->rollBack();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $savepoint
|
||||
* @throws DbException
|
||||
*/
|
||||
public function createSavepoint($savepoint) {
|
||||
try {
|
||||
$this->connection->createSavepoint($savepoint);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $savepoint
|
||||
* @throws DbException
|
||||
*/
|
||||
public function releaseSavepoint($savepoint) {
|
||||
try {
|
||||
$this->connection->releaseSavepoint($savepoint);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @param $savepoint
|
||||
* @throws DbException
|
||||
*/
|
||||
public function rollbackSavepoint($savepoint) {
|
||||
try {
|
||||
$this->connection->rollbackSavepoint($savepoint);
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @throws DbException
|
||||
*/
|
||||
public function setRollbackOnly() {
|
||||
try {
|
||||
$this->connection->setRollbackOnly();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper method
|
||||
* @return bool
|
||||
* @throws DbException
|
||||
*/
|
||||
public function isRollbackOnly(): bool {
|
||||
try {
|
||||
return $this->connection->isRollbackOnly();
|
||||
} catch (Throwable $e) {
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* overwrite method to $this->connection->createQueryBuilder
|
||||
* @return MySQLQueryBuilder
|
||||
*/
|
||||
public function createQueryBuilder(): MySQLQueryBuilder {
|
||||
return new MySQLQueryBuilder($this);
|
||||
}
|
||||
|
||||
public function getConnection(): Connection {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
$this->connection->close();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use Swoole\Table;
|
||||
use ZM\Annotation\Swoole\OnSave;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\LightCacheException;
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Framework;
|
||||
use ZM\Utils\Manager\ProcessManager;
|
||||
@@ -86,7 +87,7 @@ class LightCache
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function get(string $key) {
|
||||
if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new LightCacheException("E00048", "not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key);
|
||||
return $r === false ? null : self::parseGet($r);
|
||||
@@ -98,7 +99,7 @@ class LightCache
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function getExpire(string $key) {
|
||||
if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new LightCacheException("E00048", "not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key, "expire");
|
||||
return $r === false ? null : $r - time();
|
||||
@@ -111,7 +112,7 @@ class LightCache
|
||||
* @since 2.4.3
|
||||
*/
|
||||
public static function getExpireTS(string $key) {
|
||||
if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new LightCacheException("E00048", "not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key, "expire");
|
||||
return $r === false ? null : $r;
|
||||
@@ -125,7 +126,7 @@ class LightCache
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function set(string $key, $value, int $expire = -1) {
|
||||
if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new LightCacheException("E00048", "not initialized LightCache");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
@@ -138,7 +139,7 @@ class LightCache
|
||||
$data_type = "bool";
|
||||
$value = json_encode($value);
|
||||
} else {
|
||||
throw new ZMException(zm_internal_errcode("E00049") . "Only can set string, array and int");
|
||||
throw new LightCacheException("E00049", "Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
return self::$kv_table->set($key, [
|
||||
@@ -158,7 +159,7 @@ class LightCache
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function update(string $key, $value) {
|
||||
if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache.");
|
||||
if (self::$kv_table === null) throw new LightCacheException("E00048", "not initialized LightCache.");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
@@ -168,7 +169,7 @@ class LightCache
|
||||
} elseif (is_int($value)) {
|
||||
$data_type = "int";
|
||||
} else {
|
||||
throw new ZMException(zm_internal_errcode("E00048") . "Only can set string, array and int");
|
||||
throw new LightCacheException("E00048", "Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
if (self::$kv_table->get($key) === false) return false;
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZM\Store;
|
||||
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
use ZM\Exception\LightCacheException;
|
||||
use ZM\Exception\ZMException;
|
||||
|
||||
class LightCacheInside
|
||||
@@ -67,6 +68,6 @@ class LightCacheInside
|
||||
self::$kv_table[$name] = new Table($size, $conflict_proportion);
|
||||
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
|
||||
$r = self::$kv_table[$name]->create();
|
||||
if ($r === false) throw new ZMException(zm_internal_errcode("E00050") . "内存不足,创建静态表失败!");
|
||||
if ($r === false) throw new LightCacheException("E00050", "内存不足,创建静态表失败!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
namespace ZM\Store\MySQL;
|
||||
|
||||
|
||||
use Swoole\Database\PDOPool;
|
||||
use ZM\MySQL\MySQLPool;
|
||||
|
||||
class SqlPoolStorage
|
||||
{
|
||||
/** @var PDOPool */
|
||||
/** @var MySQLPool */
|
||||
public static $sql_pool = null;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class ZMAtomic
|
||||
* 初始化atomic计数器
|
||||
*/
|
||||
public static function init() {
|
||||
foreach (ZMConfig::get("global", "init_atomics") as $k => $v) {
|
||||
foreach ((ZMConfig::get("global", "init_atomics") ?? []) as $k => $v) {
|
||||
self::$atomics[$k] = new Atomic($v);
|
||||
}
|
||||
self::$atomics["stop_signal"] = new Atomic(0);
|
||||
@@ -32,6 +32,7 @@ class ZMAtomic
|
||||
self::$atomics["wait_msg_id"] = new Atomic(0);
|
||||
self::$atomics["_event_id"] = new Atomic(0);
|
||||
self::$atomics["server_is_stopped"] = new Atomic(0);
|
||||
if (!defined("ZM_WORKER_NUM")) define("ZM_WORKER_NUM", 1);
|
||||
for($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
self::$atomics["_#worker_".$i] = new Atomic(0);
|
||||
}
|
||||
|
||||
0
src/ZM/Store/ZMBuf.php
Executable file → Normal file
0
src/ZM/Store/ZMBuf.php
Executable file → Normal file
@@ -4,9 +4,11 @@
|
||||
namespace ZM\Utils\Manager;
|
||||
|
||||
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\ModulePackException;
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
use ZM\Module\ModulePacker;
|
||||
use ZM\Module\ModuleUnpacker;
|
||||
use ZM\Utils\DataProvider;
|
||||
@@ -36,12 +38,12 @@ class ModuleManager
|
||||
if ($json === null) continue;
|
||||
if (!isset($json["name"])) continue;
|
||||
if ($pathinfo["dirname"] == ".") {
|
||||
throw new ZMException(zm_internal_errcode("E00052") . "在/src/目录下不可以直接标记为模块(zm.json),因为命名空间不能为根空间!");
|
||||
throw new ZMKnownException("E00052", "在/src/目录下不可以直接标记为模块(zm.json),因为命名空间不能为根空间!");
|
||||
}
|
||||
$json["module-path"] = realpath($dir . "/" . $pathinfo["dirname"]);
|
||||
$json["namespace"] = str_replace("/", "\\", $pathinfo["dirname"]);
|
||||
if (isset($modules[$json["name"]])) {
|
||||
throw new ZMException(zm_internal_errcode("E00053") . "重名模块:" . $json["name"]);
|
||||
throw new ZMKnownException("E00053", "重名模块:" . $json["name"]);
|
||||
}
|
||||
$modules[$json["name"]] = $json;
|
||||
}
|
||||
@@ -50,7 +52,7 @@ class ModuleManager
|
||||
}
|
||||
|
||||
public static function getPackedModules(): array {
|
||||
$dir = DataProvider::getDataFolder() . "modules";
|
||||
$dir = ZMConfig::get("global", "module_loader")["load_path"] ?? (ZM_DATA . "modules");
|
||||
$ls = DataProvider::scanDirFiles($dir, true, false);
|
||||
if ($ls === false) return [];
|
||||
$modules = [];
|
||||
@@ -88,7 +90,10 @@ class ModuleManager
|
||||
public static function packModule($module): bool {
|
||||
try {
|
||||
$packer = new ModulePacker($module);
|
||||
$packer->setOutputPath(DataProvider::getDataFolder() . "output");
|
||||
if (!is_dir(DataProvider::getDataFolder())) throw new ModulePackException(zm_internal_errcode("E00070") . "zm_data dir not found!");
|
||||
$path = realpath(DataProvider::getDataFolder() . "/output");
|
||||
if ($path === false) mkdir($path = DataProvider::getDataFolder() . "/output");
|
||||
$packer->setOutputPath($path);
|
||||
$packer->setOverride();
|
||||
$packer->pack();
|
||||
return true;
|
||||
@@ -99,15 +104,16 @@ class ModuleManager
|
||||
}
|
||||
|
||||
/**
|
||||
* 解包模块 TODO
|
||||
* 解包模块
|
||||
* @param $module
|
||||
* @param array $options
|
||||
* @return array|false
|
||||
*/
|
||||
public static function unpackModule($module) {
|
||||
public static function unpackModule($module, array $options = []) {
|
||||
try {
|
||||
$packer = new ModuleUnpacker($module);
|
||||
return $packer->unpack();
|
||||
} catch (ModulePackException $e) {
|
||||
return $packer->unpack((bool)$options["overwrite-light-cache"], (bool)$options["overwrite-zm-data"], (bool)$options["overwrite-source"], (bool)$options["ignore-depends"]);
|
||||
} catch (ZMException $e) {
|
||||
Console::error($e->getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class MessageUtil
|
||||
*/
|
||||
public static function downloadCQImage($msg, $path = null) {
|
||||
$path = $path ?? DataProvider::getDataFolder() . "images/";
|
||||
if (!is_dir($path)) mkdir($path);
|
||||
if (!is_dir($path)) @mkdir($path);
|
||||
$path = realpath($path);
|
||||
if ($path === false) {
|
||||
Console::warning(zm_internal_errcode("E00059") . "指定的路径错误不存在!");
|
||||
|
||||
@@ -4,14 +4,9 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Server;
|
||||
use Swoole\Timer;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\ZMBuf;
|
||||
use ZM\Utils\Manager\ProcessManager;
|
||||
|
||||
/**
|
||||
* 炸毛框架的Linux signal管理类
|
||||
@@ -26,6 +21,7 @@ class SignalListener
|
||||
* @param Server $server
|
||||
*/
|
||||
public static function signalMaster(Server $server) {
|
||||
Console::debug("Listening Master SIGINT");
|
||||
Process::signal(SIGINT, function () use ($server) {
|
||||
if (zm_atomic("_int_is_reload")->get() === 1) {
|
||||
zm_atomic("_int_is_reload")->set(0);
|
||||
@@ -33,10 +29,6 @@ class SignalListener
|
||||
} else {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted(SIGINT) on Master.");
|
||||
if ((Framework::$server->inotify ?? null) !== null)
|
||||
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
|
||||
if (ZMBuf::$terminal !== null)
|
||||
Event::del(ZMBuf::$terminal);
|
||||
Process::kill($server->master_pid, SIGTERM);
|
||||
}
|
||||
});
|
||||
@@ -47,8 +39,17 @@ class SignalListener
|
||||
*/
|
||||
public static function signalManager() {
|
||||
$func = function () {
|
||||
Console::verbose("Interrupted in manager!");
|
||||
if (\server()->master_pid == \server()->manager_pid) {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted(SIGINT) on Manager.");
|
||||
swoole_timer_after(2, function() {
|
||||
Process::kill(posix_getpid(), SIGTERM);
|
||||
});
|
||||
} else {
|
||||
Console::verbose("Interrupted in manager!");
|
||||
}
|
||||
};
|
||||
Console::debug("Listening Manager SIGINT");
|
||||
if (version_compare(SWOOLE_VERSION, "4.6.7") >= 0) {
|
||||
Process::signal(SIGINT, $func);
|
||||
} elseif (extension_loaded("pcntl")) {
|
||||
@@ -62,14 +63,17 @@ class SignalListener
|
||||
* @param $worker_id
|
||||
*/
|
||||
public static function signalWorker(Server $server, $worker_id) {
|
||||
Console::debug("Listening Worker #".$worker_id." SIGINT");
|
||||
Process::signal(SIGINT, function () use ($worker_id, $server) {
|
||||
if ($server->master_pid == $server->worker_pid) {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted(SIGINT) on Worker.");
|
||||
swoole_timer_after(2, function() {
|
||||
Process::kill(posix_getpid(), SIGTERM);
|
||||
});
|
||||
}
|
||||
//Console::verbose("Interrupted in worker");
|
||||
// do nothing
|
||||
});
|
||||
if ($server->taskworker === false) {
|
||||
Process::signal(SIGUSR1, function () use ($worker_id) {
|
||||
Timer::clearAll();
|
||||
ProcessManager::resumeAllWorkerCoroutines();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,17 @@ use ZM\Framework;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Store\ZMBuf;
|
||||
use function file_get_contents;
|
||||
use function get_included_files;
|
||||
use function is_callable;
|
||||
use function is_string;
|
||||
use function json_decode;
|
||||
use function mb_substr;
|
||||
use function md5_file;
|
||||
use function pathinfo;
|
||||
use function server;
|
||||
use function str_replace;
|
||||
use function substr;
|
||||
|
||||
class ZMUtil
|
||||
{
|
||||
@@ -22,10 +33,6 @@ class ZMUtil
|
||||
Console::warning(Console::setColor('Stopping server...', 'red'));
|
||||
if (Console::getLevel() >= 4) Console::trace();
|
||||
ZMAtomic::get('stop_signal')->set(1);
|
||||
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
if (Process::kill(zm_atomic('_#worker_' . $i)->get(), 0))
|
||||
Process::kill(zm_atomic('_#worker_' . $i)->get(), SIGUSR1);
|
||||
}
|
||||
server()->shutdown();
|
||||
}
|
||||
|
||||
@@ -49,15 +56,15 @@ class ZMUtil
|
||||
* 在工作进程中返回可以通过reload重新加载的php文件列表
|
||||
* @return string[]|string[][]
|
||||
*/
|
||||
public static function getReloadableFiles() {
|
||||
return array_map(
|
||||
function ($x) {
|
||||
return str_replace(DataProvider::getSourceRootDir() . '/', '', $x);
|
||||
}, array_diff(
|
||||
get_included_files(),
|
||||
Framework::$loaded_files
|
||||
)
|
||||
);
|
||||
public static function getReloadableFiles(): array {
|
||||
$array_map = [];
|
||||
foreach (array_diff(
|
||||
get_included_files(),
|
||||
Framework::$loaded_files
|
||||
) as $key => $x) {
|
||||
$array_map[$key] = str_replace(DataProvider::getSourceRootDir() . '/', '', $x);
|
||||
}
|
||||
return $array_map;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,10 +72,10 @@ class ZMUtil
|
||||
* @param $dir
|
||||
* @param $base_namespace
|
||||
* @param null|mixed $rule
|
||||
* @param bool $return_kv
|
||||
* @param bool $return_path_value
|
||||
* @return String[]
|
||||
*/
|
||||
public static function getClassesPsr4($dir, $base_namespace, $rule = null, $return_kv = false) {
|
||||
public static function getClassesPsr4($dir, $base_namespace, $rule = null, $return_path_value = false): array {
|
||||
// 预先读取下composer的file列表
|
||||
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true);
|
||||
$classes = [];
|
||||
@@ -76,7 +83,7 @@ class ZMUtil
|
||||
$files = DataProvider::scanDirFiles($dir, true, true);
|
||||
foreach ($files as $v) {
|
||||
$pathinfo = pathinfo($v);
|
||||
if ($pathinfo['extension'] == 'php') {
|
||||
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;
|
||||
@@ -86,7 +93,7 @@ class ZMUtil
|
||||
} elseif (is_callable($rule) && !($rule($dir, $pathinfo))) continue;
|
||||
$dirname = $pathinfo['dirname'] == '.' ? '' : (str_replace('/', '\\', $pathinfo['dirname']) . '\\');
|
||||
$class_name = $base_namespace . '\\' . $dirname . $pathinfo['filename'];
|
||||
if ($return_kv) $classes[$class_name] = $v;
|
||||
if (is_string($return_path_value)) $classes[$class_name] = $return_path_value . "/" .$v;
|
||||
else $classes[] = $class_name;
|
||||
}
|
||||
}
|
||||
|
||||
18
src/ZM/script_orm_bootstrap.php
Normal file
18
src/ZM/script_orm_bootstrap.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
// TODO 配置 ORM
|
||||
use Doctrine\ORM\Tools\Setup;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
|
||||
$paths = array();
|
||||
$isDevMode = false;
|
||||
|
||||
// the connection configuration
|
||||
$dbParams = array(
|
||||
'driver' => 'pdo_mysql',
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'dbname' => 'foo',
|
||||
);
|
||||
|
||||
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
|
||||
$entityManager = EntityManager::create($dbParams, $config);
|
||||
@@ -7,5 +7,4 @@ function loader__generated_id__() {
|
||||
}
|
||||
|
||||
}
|
||||
echo "OK!\n";
|
||||
return json_decode(file_get_contents(__DIR__.'/zmplugin.json'), true) ?? ['zm_module' => false];
|
||||
@@ -4,14 +4,18 @@ use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use ZM\Annotation\Swoole\OnSetup;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\ConsoleApplication;
|
||||
use ZM\Exception\InitException;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
require_once ((!is_dir(__DIR__ . '/../../vendor')) ? getcwd() : (__DIR__ . "/../..")) . "/vendor/autoload.php";
|
||||
|
||||
try {
|
||||
(new ConsoleApplication('zhamao'))->initEnv();
|
||||
try {
|
||||
(new ConsoleApplication('zhamao'))->initEnv();
|
||||
} catch (InitException $e) {
|
||||
|
||||
}
|
||||
$base_path = DataProvider::getSourceRootDir();
|
||||
$scan_paths = [];
|
||||
$composer = json_decode(file_get_contents($base_path . "/composer.json"), true);
|
||||
|
||||
@@ -17,7 +17,7 @@ class ModuleManagerTest extends TestCase
|
||||
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
|
||||
ZMConfig::setEnv($args["env"] ?? "");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
|
||||
//定义常量
|
||||
|
||||
63
test/ZM/Utils/MessageUtilTest.php
Normal file
63
test/ZM/Utils/MessageUtilTest.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Requests\ZMRequest;
|
||||
|
||||
class MessageUtilTest extends TestCase
|
||||
{
|
||||
public function setUp(): void {
|
||||
ZMRequest::websocket();
|
||||
$a = new Frame();
|
||||
|
||||
$a->opcode = WEBSOCKET_OPCODE_PONG;
|
||||
}
|
||||
|
||||
public function testGetImageCQFromLocal() {
|
||||
file_put_contents("/tmp/a.jpg", "fake photo");
|
||||
$this->assertEquals(
|
||||
MessageUtil::getImageCQFromLocal("/tmp/a.jpg"),
|
||||
"[CQ:image,file=base64://".base64_encode("fake photo")."]"
|
||||
);
|
||||
}
|
||||
|
||||
public function testSplitCommand() {
|
||||
$msg_sample_1 = "你好啊 233\n\nhello";
|
||||
$msg_sample_2 = "";
|
||||
$this->assertCount(3, MessageUtil::splitCommand($msg_sample_1));
|
||||
$this->assertCount(1, MessageUtil::splitCommand($msg_sample_2));
|
||||
}
|
||||
|
||||
public function testIsAtMe() {
|
||||
$this->assertTrue(MessageUtil::isAtMe("[CQ:at,qq=123]", 123));
|
||||
$this->assertFalse(MessageUtil::isAtMe("[CQ:at,qq=]", 0));
|
||||
}
|
||||
|
||||
public function testDownloadCQImage() {
|
||||
if (file_exists(WORKING_DIR."/zm_data/images/abc.jpg"))
|
||||
unlink(WORKING_DIR."/zm_data/images/abc.jpg");
|
||||
ob_start();
|
||||
$msg = "[CQ:image,file=abc.jpg,url=https://zhamao.xin/file/hello.jpg]";
|
||||
$result = MessageUtil::downloadCQImage($msg, "/home/jerry/fweewfwwef/wef");
|
||||
$this->assertFalse($result);
|
||||
$this->assertStringContainsString("E00059", ob_get_clean());
|
||||
$result = MessageUtil::downloadCQImage($msg);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertFileExists(WORKING_DIR."/zm_data/images/abc.jpg");
|
||||
$result = MessageUtil::downloadCQImage($msg.$msg);
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(2, $result);
|
||||
}
|
||||
|
||||
public function testContainsImage() {
|
||||
$msg_sample = "hello\n[CQ:imag2]";
|
||||
$this->assertFalse(MessageUtil::containsImage($msg_sample));
|
||||
$this->assertTrue(MessageUtil::containsImage($msg_sample."[CQ:image,file=123]"));
|
||||
}
|
||||
|
||||
public function testMatchCommand() {
|
||||
|
||||
}
|
||||
}
|
||||
27
test/ZM/Utils/TerminalTest.php
Normal file
27
test/ZM/Utils/TerminalTest.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\ZMAtomic;
|
||||
|
||||
class TerminalTest extends TestCase
|
||||
{
|
||||
public function setUp(): void {
|
||||
}
|
||||
|
||||
public function testExecuteCommand() {
|
||||
ob_start();
|
||||
Terminal::executeCommand("logtest");
|
||||
$this->assertStringContainsString("debug msg", ob_get_clean());
|
||||
}
|
||||
|
||||
public function testBc() {
|
||||
ob_start();
|
||||
Terminal::executeCommand("bc ".base64_encode("echo 'hello';"));
|
||||
$this->assertStringContainsString("hello", ob_get_clean());
|
||||
}
|
||||
}
|
||||
@@ -16,4 +16,9 @@ class ZMUtilTest extends TestCase
|
||||
$this->assertContains(TimerMiddleware::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/Module", "Module"));
|
||||
$this->assertContains(Framework::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/ZM", "ZM"));
|
||||
}
|
||||
|
||||
public function testGetModInstance() {
|
||||
$class = Hello::class;
|
||||
$this->assertTrue(ZMUtil::getModInstance($class) instanceof Hello);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use ReflectionException;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventTracer;
|
||||
|
||||
class AnnotationParserRegisterTest extends TestCase
|
||||
{
|
||||
@@ -21,7 +22,7 @@ class AnnotationParserRegisterTest extends TestCase
|
||||
define("WORKING_DIR", realpath(__DIR__ . "/../../../"));
|
||||
if (!defined("LOAD_MODE"))
|
||||
define("LOAD_MODE", 0);
|
||||
Console::init(2);
|
||||
Console::init(4);
|
||||
$this->parser = new AnnotationParser();
|
||||
$this->parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
|
||||
try {
|
||||
@@ -70,4 +71,8 @@ class AnnotationParserRegisterTest extends TestCase
|
||||
$mapping = $this->parser->getReqMapping();
|
||||
$this->assertEquals("index", $mapping["method"]);
|
||||
}
|
||||
|
||||
public function testTracer() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Module\Example\Hello;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ReflectionException;
|
||||
use Swoole\Atomic;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\ZMAtomic;
|
||||
|
||||
class EventDispatcherTest extends TestCase
|
||||
{
|
||||
@@ -23,7 +26,9 @@ class EventDispatcherTest extends TestCase
|
||||
define("WORKING_DIR", realpath(__DIR__ . "/../../../"));
|
||||
if (!defined("LOAD_MODE"))
|
||||
define("LOAD_MODE", 0);
|
||||
Console::init(2);
|
||||
Console::init(4);
|
||||
ZMAtomic::$atomics["_event_id"] = new Atomic(0);
|
||||
LightCacheInside::init();
|
||||
$parser = new AnnotationParser();
|
||||
$parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
|
||||
try {
|
||||
@@ -44,5 +49,13 @@ class EventDispatcherTest extends TestCase
|
||||
$r = ob_get_clean();
|
||||
echo $r;
|
||||
$this->assertStringContainsString("你好啊", $r);
|
||||
$dispatcher = new EventDispatcher(CQCommand::class);
|
||||
$dispatcher->setReturnFunction(function ($result) {
|
||||
//echo $result . PHP_EOL;
|
||||
});
|
||||
//$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; });
|
||||
$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; });
|
||||
//$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe");
|
||||
$dispatcher->dispatchEvents();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,29 @@
|
||||
* @since 2.5
|
||||
*/
|
||||
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\Terminal;
|
||||
|
||||
set_coroutine_params([]);
|
||||
|
||||
// 模拟define
|
||||
chdir(__DIR__.'/../');
|
||||
chdir(__DIR__ . '/../');
|
||||
define("WORKING_DIR", getcwd());
|
||||
define("SOURCE_ROOT_DIR", WORKING_DIR);
|
||||
define("ZM_DATA", WORKING_DIR . "/zm_data/");
|
||||
define("LOAD_MODE", 0);
|
||||
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../"));
|
||||
|
||||
ZMConfig::setDirectory(WORKING_DIR."/config/");
|
||||
ZMConfig::setEnv("");
|
||||
if (ZMConfig::get("global") === false) {
|
||||
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nError path: " . DataProvider::getSourceRootDir() . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
|
||||
}
|
||||
LightCacheInside::init();
|
||||
ZMAtomic::init();
|
||||
Terminal::init();
|
||||
Console::setLevel(4);
|
||||
31
zhamao
Executable file → Normal file
31
zhamao
Executable file → Normal file
@@ -1,22 +1,35 @@
|
||||
#!/bin/sh
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
# shellcheck disable=SC2181
|
||||
# author: crazywhalecc
|
||||
# since: 2.5.0
|
||||
|
||||
|
||||
if [ -f "$(pwd)/runtime/php" ]; then
|
||||
executable="$(pwd)/runtime/php"
|
||||
echo "* Framework started with built-in php."
|
||||
else
|
||||
which php >/dev/null 2>&1
|
||||
which php >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
executable=$(which php)
|
||||
executable=$(which php)
|
||||
else
|
||||
echo 'Cannot find any PHP runtime, please use command `./install-runtime.sh` or install PHP manually!'
|
||||
exit 1
|
||||
echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
result=$(echo "$1" | grep -E "module|build")
|
||||
|
||||
if [ "$result" != "" ]; then
|
||||
executable="$executable -d phar.readonly=off"
|
||||
fi
|
||||
|
||||
if [ -f "$(pwd)/src/entry.php" ]; then
|
||||
$executable "$(pwd)/src/entry.php" $@
|
||||
$executable "$(pwd)/src/entry.php" $@
|
||||
elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then
|
||||
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
|
||||
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
|
||||
else
|
||||
echo "Cannot find zhamao-framework entry file!"
|
||||
exit 1
|
||||
fi
|
||||
echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user