diff --git a/bin/phpunit-swoole b/bin/phpunit-swoole old mode 100755 new mode 100644 diff --git a/bin/start b/bin/start old mode 100755 new mode 100644 diff --git a/config/global.php b/config/global.php index 146c630f..f628d252 100644 --- a/config/global.php +++ b/config/global.php @@ -34,10 +34,12 @@ $config['swoole'] = [ //'task_enable_coroutine' => true ]; -/** 一些框架与Swoole运行时设置的调整 */ + +/** 一些框架与框架运行时设置的调整 */ $config['runtime'] = [ 'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL), - 'swoole_server_mode' => SWOOLE_PROCESS + 'swoole_server_mode' => SWOOLE_PROCESS, + 'middleware_error_policy' => 1 ]; /** 轻量字符串缓存,默认开启 */ @@ -73,7 +75,7 @@ $config['sql_config'] = [ /** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ $config['mysql_config'] = [ 'host' => '', - 'port' => 33306, + 'port' => 3306, 'unix_socket' => null, 'username' => 'root', 'password' => '123456', diff --git a/docs/component/bot/robot-api.md b/docs/component/bot/robot-api.md index 46f3b674..a0edf5db 100644 --- a/docs/component/bot/robot-api.md +++ b/docs/component/bot/robot-api.md @@ -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", "你好啊!你好你好!"); ``` diff --git a/docs/component/module/module-pack.md b/docs/component/module/module-pack.md index d3fecade..523880f0 100644 --- a/docs/component/module/module-pack.md +++ b/docs/component/module/module-pack.md @@ -158,4 +158,24 @@ src/ } ``` -在打包时框架会自动添加这些文件到 phar 插件包内,到解包时,会自动将这些文件释放到对应框架的 `zm_data` 目录下。 \ No newline at end of file +在打包时框架会自动添加这些文件到 phar 插件包内,到解包时,会自动将这些文件释放到对应框架的 `zm_data` 目录下。 + +## 打包命令 + +```bash +# ./zhamao 和原先的 vendor/bin/start 是完全一致的 +./zhamao module:pack +``` + +例如,打包上面的名叫 foo 的模块:`./zhamao module:pack foo`。 + +打包命令执行后,将会在 `zm_data` 下的 `output` 目录输出一个 phar 文件。如果你指定了 `version` 参数,那么文件名将会是 `${name}_${version}.phar`,如果没有指定版本,那么只会有 `${name}.phar`,同时如果文件已经存在,将覆盖写入。 + +## 查看模块信息命令 + +```bash +./zhamao module:list +``` + +通过此命令可以查看模块相关的信息,如未打包但已配置的模块信息等。 + diff --git a/docs/component/module/module-unpack.md b/docs/component/module/module-unpack.md new file mode 100644 index 00000000..ef523539 --- /dev/null +++ b/docs/component/module/module-unpack.md @@ -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 +``` + +首先将待解包的 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,则会等待你手动修改完成文件,最后按回车完成修改。默认情况直接回车的话,会跳过此步骤,如果模块要求了修改但跳过修改,安装后可能会有功能缺失等问题。 \ No newline at end of file diff --git a/docs/component/store/mysql-db.md b/docs/component/store/mysql-db.md new file mode 100644 index 00000000..f362cda6 --- /dev/null +++ b/docs/component/store/mysql-db.md @@ -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 注入的问题。 \ No newline at end of file diff --git a/docs/component/store/mysql.md b/docs/component/store/mysql.md index 3492c3cc..fce9460a 100644 --- a/docs/component/store/mysql.md +++ b/docs/component/store/mysql.md @@ -1,97 +1,10 @@ # MySQL 数据库 -## 配置 +## 简介 -炸毛框架的数据库组件支持原生 SQL、查询构造器,去掉了复杂的对象模型关联,同时默认为数据库连接池,使开发变得简单。 +炸毛框架的数据库组件对接了 MySQL 连接池,在使用过程中无需配置即可实现 MySQL 查询,同时拥有高并发。 -数据库的配置位于 `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' -``` +目前 2.5 版本后炸毛框架底层采用了 `doctrine/dbal` 组件,可以方便地构建 SQL 语句。 -## 直接执行 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 注入的问题。 \ No newline at end of file diff --git a/docs/event/middleware.md b/docs/event/middleware.md index ec9a4790..41b263d8 100644 --- a/docs/event/middleware.md +++ b/docs/event/middleware.md @@ -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,在框架启动时抛出致命异常。 + diff --git a/docs/guide/basic-config.md b/docs/guide/basic-config.md index 4e5d6aae..d7b8622d 100644 --- a/docs/guide/basic-config.md +++ b/docs/guide/basic-config.md @@ -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` | @@ -47,6 +48,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** | 配置名称 | 说明 | 默认值 | diff --git a/docs/guide/errcode.md b/docs/guide/errcode.md index e121a10c..de1f46bb 100644 --- a/docs/guide/errcode.md +++ b/docs/guide/errcode.md @@ -69,5 +69,6 @@ | E00067 | 模块解包合并 `composer.json` 时没有找到项目原文件 | 检查项目的工作目录下是否有 `composer.json` 文件存在。 | | E00068 | 模块解包时无法正常拷贝文件 | 检查文件夹是否正常可以创建和写入。 | | E00069 | 框架不能启动两个 ConsoleApplication 实例 | 不要重复使用 `new ConsoleApplication()`。 | +| E00070 | 框架找不到 `zm_data` 目录 | 检查配置中指定的 `zm_data` 目录是否存在。 | | E99999 | 未知错误 | | diff --git a/docs/index.md b/docs/index.md index e7310d72..4db24f48 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ 炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务,聊天机器人(OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。 -框架主要用途为 HTTP/WS 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 +框架主要用途为 HTTP/WebSocket 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。 在 HTTP 和 WebSocket 服务器上,PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。 diff --git a/docs/update/build-update.md b/docs/update/build-update.md new file mode 100644 index 00000000..c218ba92 --- /dev/null +++ b/docs/update/build-update.md @@ -0,0 +1,30 @@ +# 更新日志(master 分支 commit) + +此文档将显示非发布版的提交版本相关更新文档,可能与发布版的更新日志有重合,在此仅作更新记录。 + +同时此处将只使用 build 版本号进行区分。 + +## 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()` 方法在遇到空扩展名文件时的报错 diff --git a/docs/update/v2.md b/docs/update/v2.md index 141e539e..c8b1bee2 100644 --- a/docs/update/v2.md +++ b/docs/update/v2.md @@ -1,5 +1,11 @@ # 更新日志(v2 版本) +## v2.5.1 (build 416) + +> 更新时间:2021.7.9 + +- 修复:脚手架无法正常使用 `init` 命令的 bug。 + ## v2.5.0(build 415) > 更新时间:2021.7.9 diff --git a/ext/go-cqhttp/go-cqhttp-down.sh b/ext/go-cqhttp/go-cqhttp-down.sh old mode 100755 new mode 100644 diff --git a/install-runtime.sh b/install-runtime.sh old mode 100755 new mode 100644 diff --git a/mkdocs.yml b/mkdocs.yml index 429d85c2..34ea5527 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,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 @@ -96,6 +97,7 @@ nav: - 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 @@ -125,5 +127,6 @@ nav: - 更新日志: - 更新日志(v2): update/v2.md - 更新日志(v1): update/v1.md + - 更新日志(build update): update/build-update.md - 配置文件更新日志: update/config.md - 炸毛框架 v1: https://docs-v1.zhamao.xin/ diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 10b43923..d5e7cce5 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -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; } diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 51fb9436..eec5a12a 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -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; @@ -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}\"!"); + } + } + } + } + } + } } diff --git a/src/ZM/Command/Module/ModuleListCommand.php b/src/ZM/Command/Module/ModuleListCommand.php index a4430a09..0ac5f9b8 100644 --- a/src/ZM/Command/Module/ModuleListCommand.php +++ b/src/ZM/Command/Module/ModuleListCommand.php @@ -18,8 +18,8 @@ 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("此功能将会把炸毛框架的模块列举出来。"); // ... } @@ -31,6 +31,7 @@ class ModuleListCommand extends Command } //定义常量 + /** @noinspection PhpIncludeInspection */ include_once DataProvider::getFrameworkRootDir() . "/src/ZM/global_defines.php"; Console::init( @@ -47,7 +48,7 @@ class ModuleListCommand extends Command 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"]); @@ -59,7 +60,7 @@ class ModuleListCommand extends Command $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"]); diff --git a/src/ZM/Command/Module/ModulePackCommand.php b/src/ZM/Command/Module/ModulePackCommand.php index fc422249..b11ed103 100644 --- a/src/ZM/Command/Module/ModulePackCommand.php +++ b/src/ZM/Command/Module/ModulePackCommand.php @@ -21,7 +21,7 @@ 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 | 指定输出目录"); // ... diff --git a/src/ZM/Command/Module/ModuleUnpackCommand.php b/src/ZM/Command/Module/ModuleUnpackCommand.php index 60db7482..f7d55df1 100644 --- a/src/ZM/Command/Module/ModuleUnpackCommand.php +++ b/src/ZM/Command/Module/ModuleUnpackCommand.php @@ -22,11 +22,12 @@ class ModuleUnpackCommand extends Command protected function configure() { $this->setDefinition([ new InputArgument("module-name", InputArgument::REQUIRED), - new InputOption("override-light-cache", null, null, "覆盖现有的LightCache项目"), - new InputOption("override-zm-data", null, null, "覆盖现有的zm_data文件"), - new InputOption("override-source", null, null, "覆盖现有的源码文件") + 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("Unpack a phar module into src directory"); + $this->setDescription("解包一个phar模块到src目录"); $this->setHelp("此功能将phar格式的模块包解包到src目录下。"); // ... } @@ -39,6 +40,7 @@ class ModuleUnpackCommand extends Command } //定义常量 + /** @noinspection PhpIncludeInspection */ include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php"; Console::init( diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 46ddca68..74ed0d92 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -28,8 +28,8 @@ class ConsoleApplication extends Application { private static $obj = null; - const VERSION_ID = 416; - const VERSION = "2.5.1"; + const VERSION_ID = 417; + const VERSION = "2.5.2"; /** * @throws InitException @@ -43,6 +43,8 @@ class ConsoleApplication extends Application } /** + * @param string $with_default_cmd + * @return ConsoleApplication * @throws InitException */ public function initEnv($with_default_cmd = ""): ConsoleApplication { diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index 972987b4..2968860f 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -6,9 +6,8 @@ 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; @@ -25,12 +24,12 @@ 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']; } diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 5853be61..0c184d24 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -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,11 +139,16 @@ 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(", ", array_map(function($x){ return $x->middleware; }, $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->middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware->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); @@ -186,7 +193,8 @@ class EventDispatcher } throw $e; } - for ($i = count($middlewares) - 1; $i >= 0; --$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 存在后置事件,执行中 ..."); diff --git a/src/ZM/Event/EventManager.php b/src/ZM/Event/EventManager.php index aa8e71c0..1206938a 100644 --- a/src/ZM/Event/EventManager.php +++ b/src/ZM/Event/EventManager.php @@ -32,6 +32,7 @@ class EventManager self::$middlewares = $parser->getMiddlewares(); self::$middleware_map = $parser->getMiddlewareMap(); self::$req_mapping = $parser->getReqMapping(); + $parser->verifyMiddlewares(); } /** diff --git a/src/ZM/Event/SwooleEvent/OnWorkerStart.php b/src/ZM/Event/SwooleEvent/OnWorkerStart.php index 4a9ea8ca..29f04bd5 100644 --- a/src/ZM/Event/SwooleEvent/OnWorkerStart.php +++ b/src/ZM/Event/SwooleEvent/OnWorkerStart.php @@ -24,7 +24,7 @@ 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; @@ -32,6 +32,7 @@ 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; /** @@ -48,7 +49,6 @@ class OnWorkerStart implements SwooleEvent } unset(Context::$context[Coroutine::getCid()]); if ($server->taskworker === false) { - zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid); if (LightCacheInside::get("wait_api", "wait_api") !== null) { LightCacheInside::unset("wait_api", "wait_api"); @@ -144,14 +144,12 @@ class OnWorkerStart implements SwooleEvent $parser->addRegisterPath(DataProvider::getSourceRootDir() . "/" . $v . "/", trim($k, "\\")); } } - $parser->registerMods(); - EventManager::loadEventByParser($parser); //加载事件 //加载自定义的全局函数 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!"); } //加载插件 @@ -175,7 +173,21 @@ 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() { @@ -233,7 +245,7 @@ class OnWorkerStart implements SwooleEvent ->withPassword($real_conf["password"]) ->withOptions($real_conf["options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false]) ); - DB::initTableList(); + DB::initTableList($real_conf["dbname"]); } } } \ No newline at end of file diff --git a/src/ZM/Exception/AnnotationException.php b/src/ZM/Exception/AnnotationException.php new file mode 100644 index 00000000..4c1d2b55 --- /dev/null +++ b/src/ZM/Exception/AnnotationException.php @@ -0,0 +1,10 @@ + $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; } diff --git a/src/ZM/Module/ModulePacker.php b/src/ZM/Module/ModulePacker.php index 182de6f1..3482e735 100644 --- a/src/ZM/Module/ModulePacker.php +++ b/src/ZM/Module/ModulePacker.php @@ -122,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 { diff --git a/src/ZM/Module/ModuleUnpacker.php b/src/ZM/Module/ModuleUnpacker.php index 5a5530d3..e583ff15 100644 --- a/src/ZM/Module/ModuleUnpacker.php +++ b/src/ZM/Module/ModuleUnpacker.php @@ -32,13 +32,14 @@ class ModuleUnpacker * @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(bool $override_light_cache = false, bool $override_data_files = false, bool $override_source = false): array { + 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(); + $this->checkDepends($ignore_depends); $this->checkLightCacheStore(); $this->checkZMDataStore(); @@ -62,18 +63,19 @@ class ModuleUnpacker /** * 检查模块依赖关系 + * @param bool $ignore_depends * @throws ModulePackException * @throws ZMException */ - private function checkDepends() { + private function checkDepends($ignore_depends = false) { $configured = ModuleManager::getConfiguredModules(); $depends = $this->module_config["depends"] ?? []; foreach ($depends as $k => $v) { - if (!isset($configured[$k])) { + 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)) { + if (!VersionComparator::compareVersionRange($current_ver, $v) && !$ignore_depends) { throw new ModulePackException(zm_internal_errcode("E00063") . "模块 " . $this->module_config["name"] . " 依赖的模块 $k 版本依赖不符合条件(现有版本: " . $current_ver . ", 需求版本: " . $v . ")"); } } diff --git a/src/ZM/MySQL/MySQLConnection.php b/src/ZM/MySQL/MySQLConnection.php index 8ecfc185..1915b10e 100644 --- a/src/ZM/MySQL/MySQLConnection.php +++ b/src/ZM/MySQL/MySQLConnection.php @@ -21,12 +21,13 @@ class MySQLConnection implements Connection private $conn; public function __construct() { - Console::info("Constructing..."); + 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) { @@ -51,6 +52,7 @@ class MySQLConnection implements Connection public function exec($sql) { try { + Console::debug("Running SQL exec: ".$sql); $statement = $this->conn->exec($sql); assert($statement !== false); return $statement; @@ -88,7 +90,7 @@ class MySQLConnection implements Connection } public function __destruct() { - Console::info("Destructing!!!"); + Console::debug("Destructing!!!"); SqlPoolStorage::$sql_pool->putConnection($this->conn); } } \ No newline at end of file diff --git a/src/ZM/MySQL/MySQLDriver.php b/src/ZM/MySQL/MySQLDriver.php index 35dc05c7..ec9e4634 100644 --- a/src/ZM/MySQL/MySQLDriver.php +++ b/src/ZM/MySQL/MySQLDriver.php @@ -4,16 +4,16 @@ namespace ZM\MySQL; -use Doctrine\DBAL\Driver; +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 Driver +class MySQLDriver implements DoctrineDriver { public function connect(array $params, $username = null, $password = null, array $driverOptions = []) { - Console::info("Requiring new connection"); + Console::debug("Requiring new connection"); return new MySQLConnection(); } diff --git a/src/ZM/MySQL/MySQLManager.php b/src/ZM/MySQL/MySQLManager.php index 07770d1b..00dc9db7 100644 --- a/src/ZM/MySQL/MySQLManager.php +++ b/src/ZM/MySQL/MySQLManager.php @@ -4,17 +4,12 @@ namespace ZM\MySQL; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\DriverManager; -use Doctrine\DBAL\Exception; - class MySQLManager { /** - * @return Connection - * @throws Exception + * @return MySQLWrapper */ - public static function getConnection() { - return DriverManager::getConnection(["driverClass" => MySQLDriver::class]); + public static function getWrapper(): MySQLWrapper { + return new MySQLWrapper(); } } \ No newline at end of file diff --git a/src/ZM/MySQL/MySQLPool.php b/src/ZM/MySQL/MySQLPool.php index cf776838..b9e9930a 100644 --- a/src/ZM/MySQL/MySQLPool.php +++ b/src/ZM/MySQL/MySQLPool.php @@ -34,4 +34,8 @@ class MySQLPool extends PDOPool $this->count--; parent::put($connection); } + + public function getCount() { + return $this->count; + } } \ No newline at end of file diff --git a/src/ZM/MySQL/MySQLQueryBuilder.php b/src/ZM/MySQL/MySQLQueryBuilder.php new file mode 100644 index 00000000..01ab31a4 --- /dev/null +++ b/src/ZM/MySQL/MySQLQueryBuilder.php @@ -0,0 +1,29 @@ +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()); + } +} \ No newline at end of file diff --git a/src/ZM/MySQL/MySQLStatement.php b/src/ZM/MySQL/MySQLStatement.php index 8900f69e..b0838519 100644 --- a/src/ZM/MySQL/MySQLStatement.php +++ b/src/ZM/MySQL/MySQLStatement.php @@ -29,7 +29,14 @@ class MySQLStatement implements Statement, \IteratorAggregate } public function setFetchMode($fetchMode, $arg2 = null, $arg3 = []) { - return $this->statement->setFetchMode($fetchMode, $arg2, $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) { diff --git a/src/ZM/MySQL/MySQLStatementWrapper.php b/src/ZM/MySQL/MySQLStatementWrapper.php new file mode 100644 index 00000000..c1b2e265 --- /dev/null +++ b/src/ZM/MySQL/MySQLStatementWrapper.php @@ -0,0 +1,232 @@ +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(); + } + + +} \ No newline at end of file diff --git a/src/ZM/MySQL/MySQLWrapper.php b/src/ZM/MySQL/MySQLWrapper.php index a4a94c41..d40a1d4e 100644 --- a/src/ZM/MySQL/MySQLWrapper.php +++ b/src/ZM/MySQL/MySQLWrapper.php @@ -1,15 +1,575 @@ connection = MySQLManager::getConnection(); + 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() { diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 59bf5be0..6d82e158 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -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; diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php index 4febdba1..dd63be64 100644 --- a/src/ZM/Store/LightCacheInside.php +++ b/src/ZM/Store/LightCacheInside.php @@ -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", "内存不足,创建静态表失败!"); } } diff --git a/src/ZM/Store/ZMBuf.php b/src/ZM/Store/ZMBuf.php old mode 100755 new mode 100644 diff --git a/src/ZM/Utils/Manager/ModuleManager.php b/src/ZM/Utils/Manager/ModuleManager.php index 8f833026..e9d6bf8d 100644 --- a/src/ZM/Utils/Manager/ModuleManager.php +++ b/src/ZM/Utils/Manager/ModuleManager.php @@ -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; @@ -107,7 +112,7 @@ class ModuleManager public static function unpackModule($module, array $options = []) { try { $packer = new ModuleUnpacker($module); - return $packer->unpack((bool)$options["override-light-cache"], (bool)$options["override-zm-data"], (bool)$options["override-source"]); + 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; diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 471dcdc1..c7634dc6 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -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 { @@ -45,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; } /** @@ -61,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 = []; @@ -72,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; @@ -82,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; } } diff --git a/src/ZM/script_phar_stub.php b/src/ZM/script_phar_stub.php index 9b1ec8c9..3c10e4cf 100644 --- a/src/ZM/script_phar_stub.php +++ b/src/ZM/script_phar_stub.php @@ -7,5 +7,4 @@ function loader__generated_id__() { } } -echo "OK!\n"; return json_decode(file_get_contents(__DIR__.'/zmplugin.json'), true) ?? ['zm_module' => false]; \ No newline at end of file diff --git a/zhamao b/zhamao old mode 100755 new mode 100644