update to 2.5.0-b1 (build 408)

This commit is contained in:
jerry
2021-06-16 00:17:30 +08:00
parent 59d614a24e
commit 4ee16d4fc6
92 changed files with 2286 additions and 1342 deletions

1
.gitignore vendored
View File

@@ -2,7 +2,6 @@
/src/test/ /src/test/
/src/webconsole/config/ /src/webconsole/config/
/vendor/ /vendor/
zm.json
/zm_data/ /zm_data/
composer.lock composer.lock
/resources/server.phar /resources/server.phar

View File

@@ -1,18 +1,20 @@
#!/usr/bin/env php #!/usr/bin/env php
/** @noinspection ALL */<?php <?php
/** /**
* Copyright: Swlib * Copyright: Swlib
* Author: Twosee <twose@qq.com> * Author: Twosee <twose@qq.com>
* Date: 2018/4/14 下午10:58 * Date: 2018/4/14 下午10:58
*/ */
Co::set([ use Swoole\Coroutine;
'log_level' => SWOOLE_LOG_INFO,
Coroutine::set([
'log_level' => SWOOLE_LOG_INFO,
'trace_flags' => 0 'trace_flags' => 0
]); ]);
if (!ini_get('date.timezone')) { if (!ini_get('date.timezone')) {
ini_set('date.timezone', 'UTC'); ini_set('date.timezone', 'Asia/Shanghai');
} }
foreach ([ foreach ([
@@ -55,12 +57,12 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
/** @noinspection PhpIncludeInspection */ /** @noinspection PhpIncludeInspection */
require PHPUNIT_COMPOSER_INSTALL; require PHPUNIT_COMPOSER_INSTALL;
$starttime = microtime(true); $starttime = microtime(true);
go(function (){ go(function () {
try{ try {
PHPUnit\TextUI\Command::main(false); PHPUnit\TextUI\Command::main(false);
} catch(Exception $e) { } catch (Exception $e) {
echo $e->getMessage().PHP_EOL; echo $e->getMessage() . PHP_EOL;
} }
}); });
Swoole\Event::wait(); Swoole\Event::wait();
echo "Took ".round(microtime(true) - $starttime, 4). "s\n"; echo "Took " . round(microtime(true) - $starttime, 4) . "s\n";

View File

@@ -1,23 +1,34 @@
#!/bin/sh #!/bin/sh
# shellcheck disable=SC2068
# shellcheck disable=SC2181
# author: crazywhalecc
# since: 2.5.0
if [ -f "$(pwd)/runtime/php" ]; then if [ -f "$(pwd)/runtime/php" ]; then
executable="$(pwd)/runtime/php" executable="$(pwd)/runtime/php"
echo "* Framework started with built-in php." echo "* Framework started with built-in php."
else else
which php >/dev/null 2>&1 which php >/dev/null 2>&1
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
executable=$(which php) executable=$(which php)
else else
echo 'Cannot find any PHP runtime, please use command `./install-runtime.sh` or install PHP manually!' echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!'
exit 1 exit 1
fi fi
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 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 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 else
echo "Cannot find zhamao-framework entry file!" echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
exit 1 exit 1
fi fi

View File

@@ -23,17 +23,18 @@
"php": ">=7.2", "php": ">=7.2",
"ext-json": "*", "ext-json": "*",
"ext-posix": "*", "ext-posix": "*",
"ext-pcntl": "*",
"doctrine/annotations": "~1.12 || ~1.4.0", "doctrine/annotations": "~1.12 || ~1.4.0",
"psy/psysh": "@stable",
"symfony/polyfill-ctype": "^1.19", "symfony/polyfill-ctype": "^1.19",
"symfony/polyfill-mbstring": "^1.19", "symfony/polyfill-mbstring": "^1.19",
"symfony/console": "~5.0 || ~4.0 || ~3.0", "symfony/console": "~5.0 || ~4.0 || ~3.0",
"symfony/routing": "~5.0 || ~4.0 || ~3.0", "symfony/routing": "~5.0 || ~4.0 || ~3.0",
"zhamao/console": "^1.0.10", "zhamao/console": "^1.0",
"zhamao/config": "^1.0", "zhamao/config": "^1.0",
"zhamao/request": "^1.1", "zhamao/request": "^1.1",
"zhamao/connection-manager": "^1.0" "zhamao/connection-manager": "^1.0",
"jelix/version": "^2.0",
"league/climate": "^3.7",
"psy/psysh": "^0.10.8"
}, },
"suggest": { "suggest": {
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient", "ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
@@ -51,6 +52,7 @@
] ]
}, },
"require-dev": { "require-dev": {
"swoole/ide-helper": "@dev" "swoole/ide-helper": "@dev",
"phpunit/phpunit": "^8.5 || ^9.0"
} }
} }

View File

@@ -34,6 +34,11 @@ $config['swoole'] = [
//'task_enable_coroutine' => true //'task_enable_coroutine' => true
]; ];
/** 一些框架与Swoole运行时设置的调整 */
$config['runtime'] = [
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)
];
/** 轻量字符串缓存,默认开启 */ /** 轻量字符串缓存,默认开启 */
$config['light_cache'] = [ $config['light_cache'] = [
'size' => 512, //最多允许储存的条数需要2的倍数 'size' => 512, //最多允许储存的条数需要2的倍数
@@ -122,4 +127,10 @@ $config['remote_terminal'] = [
'token' => '' 'token' => ''
]; ];
/** 模块(插件)加载器的相关设置 */
$config['module_loader'] = [
'enable_hotload' => true,
'load_path' => $config['zm_data'] . 'modules'
];
return $config; return $config;

View File

@@ -0,0 +1,3 @@
# 手动部署环境教程
TODO: 还没写

View File

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

View File

@@ -16,6 +16,16 @@ DataProvider 是框架内提供的一个简易的文件管理类。
获取配置项 `zm_data` 指定的目录。 获取配置项 `zm_data` 指定的目录。
如果指定参数 `$second`,则返回二级目录地址,如果二级目录不存在则自动创建。
```php
DataProvider::getDataFolder("TestModule"); // 例如返回 /root/zhamao-framework/zm_data/TestModule/
```
## DataProvider::getFrameworkRootDir()
返回框架本体的根目录。
## DataProvider::saveToJson() ## DataProvider::saveToJson()
将变量内容保存为 json 格式的文件,存储在 `zm_data/config/` 目录下或子目录下。 将变量内容保存为 json 格式的文件,存储在 `zm_data/config/` 目录下或子目录下。

View File

@@ -2,111 +2,49 @@
> 这篇为炸毛框架以及环境的部署教程。 > 这篇为炸毛框架以及环境的部署教程。
框架部署分为环境部署和框架部署。框架部署非常简单,只需要通用的指令,下方主要说环境部署 框架部署分为两部分,一部分是安装 PHP 环境,另一部分是通过 Composer 或 GitHub 拉取框架的脚手架
## Docker 部署 PHP 环境 ## 一键下载静态 PHP 环境和框架脚手架
如果你不想干扰主机的环境,可以使用 Docker 进行拉取框架适用的 PHP7 with Swoole Extension Docker Container。本框架安装教程中使用的 DockerHub 及 Dockerfile 构建文件所构建的容器均为独立的容器,和框架无关,此 Docker 也可以用作运行**其他基于 php-cli 模式的项目**。
方法一、直接拉取远程容器(推荐 从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架,只需运行下面的脚本即可。(开发环境推荐此方法
```bash
docker pull zmbot/swoole
```
方法二、从 Dockerfile 构建容器
```bash
git clone https://github.com/zhamao-robot/zhamao-swoole-docker.git
cd zhamao-swoole-docker/
docker build -t zm .
```
!!! note "从 Dockerfile 构建容器的提示"
使用 Dockerfile 构建后,需要将下方所有的 `zmbot/swoole` 全部更换成 `zm`,或者你上方指令中的 `-t` 参数后方的名称,具体可以详情查阅 Docker 的文档。
## 主机部署 PHP 环境
### Debian 系列Ubuntu、Kali
需要的系统内软件包为:`php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql wget composer`
下面是一个一键安装的命令行(最小安装,需 root 权限):
```bash ```bash
apt-get update && apt-get install -y software-properties-common && add-apt-repository ppa:ondrej/php && apt-get update && apt-get install php php-dev php-mbstring gcc make openssl php-mbstring php-json php-curl php-mysql -y && apt-get install wget composer -y && wget https://github.com/swoole/swoole-src/archive/v4.5.7.tar.gz && tar -zxvf v4.5.7.tar.gz && cd swoole-src-4.5.7/ && phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && (echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')) # 将会把 PHP、框架都安装在此目录下
``` mkdir zhamao-app/ # 这里可以取自己的项目名字
cd zhamao-app/
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
### macOS (with Homebrew) # 安装完成后的启动框架命令2.5.0 版本后可省略掉 runtime/php 前缀)
macOS 系统下的部署相对简单很多,只需要使用 Homebrew 安装以下包和执行安装命令即可
!!! note "给 macOS 开发者的提示"
因为苹果新的 Apple Sillicon 对 Homebrew 的支持目前仅限于 Rosetta2 转译版,
所以在使用 M1-based Mac 时出现问题暂时无解。
使用以下指令可能会遇到报错等问题,如有疑问可直接使用 Docker 或咨询我(炸毛框架开发者)。
```bash
brew install php composer
pecl install swoole
```
### 其他 Linux 发行版
其他 Linux 发行版,如 CentOSFedoraArch 等暂时还没有经过严格的测试需要哪些依赖,大体和 Ubuntu、Debian 系需要的依赖包差不多,可根据安装过程中报错提示依次安装,或者直接使用 Docker 环境。
## 安装框架
恭喜你,前方通过 Docker 或主机安装环境后可以开始构建框架的开发脚手架了!
如果你是通过**主机安装 PHP 部署的环境**,下方是通过脚手架来构建项目的命令行。
```bash
composer create-project zhamao/framework-starter zhamao-app
cd zhamao-app/ # 这个是你可以自己定义的名称
vendor/bin/start server # 启动框架
```
如果是通过 **Docker 部署的环境**,则需要在先克隆脚手架后在文件夹内使用 Docker 命令下的 `composer update`。(如果主机环境有 composer 也可以使用 `composer create-project` 的方式拉取脚手架。)
```bash
git clone https://github.com/zhamao-robot/zhamao-framework-starter.git
cd zhamao-framework-starter/
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update
```
或者在 Docker 环境下,你可以直接使用如下方法拉取和快速启动一个最标准的框架。
```bash
git clone https://github.com/zhamao-robot/zhamao-framework-starter.git
cd zhamao-framework-starter
./run-docker.sh # 在正式版炸毛框架 v2 发布后可用,测试版暂不放出
```
!!! tip "提示"
如果国内 Composer 下载过慢,可以使用阿里云的 Composer 镜像加速。
```bash
# 仅对当前的项目使用阿里云加速
composer config repo.packagist composer https://mirrors.aliyun.com/composer/
# 对全局的 Composer 使用阿里云加速
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
```
## 启动框架
本地环境启动方式:
```bash
cd zhamao-framework-starter
vendor/bin/start server vendor/bin/start server
# 扩展用法:使用静态 PHP 版本的 Composer update
runtime/composer update
# 扩展用法:使用静态 PHP 运行别的 CLI 脚本
runtime/php path/to/your/script.php
``` ```
使用 Docker 启动: > 有关静态 PHP 的多种用法(如 Composer见 [进阶 - PHP 环境高级](/advanced/php-env)
## 使用 Docker 部署 PHP 和框架
你也可以使用 Docker 进行拉取 PHP 环境。
```bash ```bash
cd zhamao-framework-starter # 拉取 Docker 镜像
docker pull zmbot/swoole
# 再通过 GitHub 或其他方式拉取框架脚手架
git clone --depth=1 https://github.com/zhamao-robot/zhamao-framework-starter.git
cd zhamao-framework-starter/
# Docker 内使用 Composer 更新依赖
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole composer update
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start init
# 使用 Docker 启动框架
docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole vendor/bin/start server
``` ```
启动后你会看到和下方类似的初始化内容,表明启动成功了 启动后你会看到和下方类似的初始化内容,表明启动成功了
```verilog ```verilog
@@ -114,17 +52,13 @@ $ vendor/bin/start server
host: 0.0.0.0 | port: 20001 host: 0.0.0.0 | port: 20001
log_level: 2 | version: 2.0.0 log_level: 2 | version: 2.0.0
config: global.php | worker_num: 4 config: global.php | worker_num: 4
working_dir: /Users/jerry/project/git-project/zhamao-framework working_dir: /app/zhamao-framework
______ ______
|__ / |__ __ _ _ __ ___ __ _ ___ |__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \ / /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) | / /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/ /____|_| |_|\__,_|_| |_| |_|\__,_|\___/
[14:27:31] [I] [#0] Worker #0 启动中
[14:27:31] [I] [#2] Worker #2 启动中
[14:27:31] [I] [#1] Worker #1 启动中
[14:27:31] [I] [#3] Worker #3 启动中
[14:27:31] [S] [#3] Worker #3 [14:27:31] [S] [#3] Worker #3
[14:27:31] [S] [#0] Worker #0 [14:27:31] [S] [#0] Worker #0
[14:27:31] [S] [#2] Worker #2 [14:27:31] [S] [#2] Worker #2
@@ -133,9 +67,11 @@ working_dir: /Users/jerry/project/git-project/zhamao-framework
单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。 单纯运行 炸毛框架 后,如果不部署或安装启动任何机器人客户端的话,仅仅相当于启动了一个 监听 20001 端口的WebSoket + HTTP 服务器。你可以通过浏览器访问http://127.0.0.1:20001 ,或者你部署到了服务器后需要输入服务器地址。
!!! note "安装和部署总结" ## 命令总结
根据上方描述,此文档中剩余提到的所有 Bash 命令,如果使用 Docker 部署环境,则需要加上 Docker 环境的指令:`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`,如执行其他 Linux 指令(以查看 PHP 版本为例):`docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole php -v` 1. 对于框架的启动,必须 cd 到项目的跟目录,比如 `cd zhamao-app/` 进入到项目根目录
2. 无论何种方式启动,启动框架的命令格式都为这个格式:`{php二进制路径} vendor/bin/start server {--如果需要参数的话这样跟}`
3. 第二条的 `php 二进制路径` 指的是,比如使用第一种静态 PHP 环境,这里写 `runtime/php` 就好了,如果是安装到系统的 PHP 的话,这里为空,如果是 Docker 部署的环境,则这里填 `docker run -it --rm -v $(pwd):/app/ -p 20001:20001 zmbot/swoole`
## 使用 IDE 等工具开发代码 ## 使用 IDE 等工具开发代码

View File

@@ -1,5 +1,68 @@
# 更新日志v2 版本) # 更新日志v2 版本)
## v2.5.0(未发布,测试中)
> 更新时间:未知
以下是版本**新增内容**
- 新增全新的模块系统可打包模块src 目录下的子目录用户逻辑代码)为 phar 格式进行分发和版本备份。
- 全局配置文件新增 `module_loader` 项,用于配置外部模块加载的一些设置。
- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数。
- 新增 `module:list` 命令,用于查看未打包和已打包的模块列表。
- 新增 `module:pack` 命令,用于打包现有 src 目录下的模块。
- 新增打包框架功能,支持将用户的整个项目连同炸毛框架打包为一个 phar 便携运行,使用命令 `build`
- 新增快捷脚本 `./zhamao`,效果同 `vendor/bin/start``bin/start`
- 新增启动参数 `--interact`:又重新支持交互终端了,但还是有点问题,不推荐使用。
- 新增启动参数 `--disable-safe-exit`:如果你的项目在 Ctrl+C 时总是卡住且项目内没有什么使用 LightCache 等缓存在内存的数据可开启防止关不掉框架。
- 新增启动参数 `--preview`:只显示参数,不启动炸毛框架的服务器。
- 新增启动参数 `--force-load-module`:强制打包状态下加载的模块(使用英文逗号分隔多个模块名称)。
- `CoroutinePool` 协程池新增 `getRunningCoroutineCount` 方法,用于查看协程池中的协程数量。
- `DataProvider` 新增 `getFrameworkRootDir()``getSourceRootDir()`,分别代表获取框架的根目录和用户源码根目录。(详见下方对目录的定义解释)
- `DataProvider``getDataFolder` 新增参数 `$second = ''`,如果给定,则自动创建子目录 `$second` 并返回。
- `DataProvider` 新增 `scanDirFiles()` 方法,用于扫描目录,可选择是否递归、是否返回相对路径,也支持扫描 Phar 文件内的路径,非常好用。
- `DataProvider` 新增 `isRelativePath()` 方法,检查路径是否为相对路径(根据第一个字符是否是 '/' 来判断)。
- `ZMUtil` 新增 `getClassesPsr4()` 方法,用于根据 Psr-4 标准来获取目录下的所有类文件。
- 新增全局错误码,可以根据错误码在文档内快速定位和解决问题。
以下是版本**修改内容**
- 启动文件 `vendor/bin/start` 修改为 shell 脚本,可自动寻找 PHP 环境。
- 全局配置文件的 `zm_data` 根目录默认修改为 `WORKING_DIR`
- 全局配置文件删除 `server_event_handler_class` 项,此项废弃。
- 修复部分 CQ 码解析过程中没有转义的问题。
-`ZMRobot` 类转移为 `OneBotV11` 类,但提供兼容。
- 修复在守护进程模式下使用 `daemon:reload``daemon:stop` 命令可能失效的问题。
- 修复 systemd 生成时脚本目录错误的 bug。
- 修复 PipeMessage 等事件未捕获错误导致崩溃的问题。
- `ZM\Http\RouteManager` 移动到 `ZM\Utils\Manager\RouteManager`,但原地址兼容。
- 修复 `Terminal` 类使用的一些问题。
-`pcntl` 扩展改为可选依赖,当 Swoole 版本大于等于 4.6.7 时不需要安装 `pcntl` 扩展。
- 修正启动时框架对缺省配置项的一些默认参数。
- 注解 `@OnSetup``@SwooleHandler` 可直接使用,无需设置 `server_event_handler_class` 即可。
- 修复框架在一些非正常终端中运行时导致错误的问题。
- 使用 `--debug-mode` 参数时,自动开启热更新。
**对目录的定义解释**
在 2.4.4 版本之前,使用炸毛框架中,只含有两种目录,`getWorkingDir``getDataFolder`,分别代表获取工作目录和数据目录。在 2.5 版本中,又新增了 `getFrameworkRootDir` 代表获取框架的根目录,`getSourceRootDir` 代表获取源码的根目录。
以 Composer 运行模式举例,如果你使用 `composer create-project zhamao/framework-starter` 命令新建的框架,那么假设我们从 `/app` 目录下运行此命令,然后使用 `cd framework-starter/` 进入项目目录,此时我们使用 `vendor/bin/start server` 命令运行服务器,对应的目录为:
- `WorkingDir``/app/framework-starter/`
- `SourceRootDir``/app/framework-starter/`
- `FrameworkRootDir``/app/framework-starter/vendor/zhamao/framework/`
如果以源码模式(直接克隆 `zhamao-framework.git` 仓库),启动框架,那么使用命令 `bin/start server` 启动框架后,以上三个返回的目录则完全相同。
如果以 2.5 版本新的项目归档模式build启动框架假设我们的项目代码打包为 `server.phar`,在 `/app/` 目录,我们使用命令 `php server.phar server` 启动炸毛框架,那么它对应的目录为:
- `WorkingDir``/app/`
- `SourceRootDir``phar:///app/server.phar/`
- `FrameworkRootDir``phar:///app/server.phar/vendor/zhamao/framework/`
如果最后一种归档方式启动的框架是从源码模式打包而来,那么 `FrameworkRootDir` 就与 `SourceRootDir` 相同。
## v2.4.4 (build 405) ## v2.4.4 (build 405)
> 更新时间2021.3.29 > 更新时间2021.3.29

View File

@@ -29,6 +29,6 @@ if [ "$ver" != "" ]; then
tar -zxvf temp.tar.gz go-cqhttp tar -zxvf temp.tar.gz go-cqhttp
rm temp.tar.gz rm temp.tar.gz
echo "下载完成,启动命令:./go-cqhttp" echo "下载完成,启动命令:./go-cqhttp"
echo "首次启动后先编辑config.hjson文件!" echo "首次启动后先编辑config文件"
fi fi
fi fi

View File

@@ -1,4 +1,4 @@
site_name: 炸毛框架 v2 site_name: 炸毛框架文档
repo_name: '炸毛框架' repo_name: '炸毛框架'
repo_url: 'https://github.com/zhamao-robot/zhamao-framework' repo_url: 'https://github.com/zhamao-robot/zhamao-framework'
@@ -32,7 +32,7 @@ markdown_extensions:
linenums_style: pymdownx.inline linenums_style: pymdownx.inline
extra: extra:
version: version:
method: mike provider: mike
copyright: 'Copyright &copy; 2019 - 2021 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tx-switch"> copyright: 'Copyright &copy; 2019 - 2021 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="tx-switch">
<button data-md-color-scheme="default"><code>默认模式</code></button> <button data-md-color-scheme="default"><code>默认模式</code></button>
@@ -50,7 +50,7 @@ copyright: 'Copyright &copy; 2019 - 2021 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&n
name.textContent = attr; name.textContent = attr;
}) })
}) })
</script><br><a href="http://beian.miit.gov.cn">ICP备18000198号-1</a>' </script><br><a href="http://beian.miit.gov.cn">ICP备2021010446号-1</a>'
nav: nav:
- 指南: - 指南:
@@ -74,18 +74,18 @@ nav:
- 框架组件: component/index.md - 框架组件: component/index.md
- 上下文: component/context.md - 上下文: component/context.md
- 聊天机器人组件: - 聊天机器人组件:
- 机器人 API: component/robot-api.md - 机器人 API: component/bot/robot-api.md
- CQ 码(多媒体消息): component/cqcode.md - CQ 码(多媒体消息): component/bot/cqcode.md
- 机器人消息处理: component/message-util.md - 机器人消息处理: component/bot/message-util.md
- Token 验证: component/access-token.md - Token 验证: component/bot/access-token.md
- 图灵机器人 API: component/turing-api.md - 图灵机器人 API: component/bot/turing-api.md
- 存储: - 存储:
- LightCache 轻量缓存: component/light-cache.md - LightCache 轻量缓存: component/store/light-cache.md
- MySQL 数据库: component/mysql.md - MySQL 数据库: component/store/mysql.md
- Redis 数据库: component/redis.md - Redis 数据库: component/store/redis.md
- ZMAtomic 原子计数器: component/atomics.md - ZMAtomic 原子计数器: component/store/atomics.md
- SpinLock 自旋锁: component/spin-lock.md - SpinLock 自旋锁: component/store/spin-lock.md
- 文件管理: component/data-provider.md - 文件管理: component/store/data-provider.md
- HTTP 服务器工具类: - HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/zmrequest.md - HTTP 和 WebSocket 客户端: component/zmrequest.md
- HTTP 路由管理: component/route-manager.md - HTTP 路由管理: component/route-manager.md
@@ -100,6 +100,7 @@ nav:
- 进阶开发: advanced/index.md - 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md - 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md - 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 从 v1 升级: advanced/to-v2.md - 从 v1 升级: advanced/to-v2.md
- 内部类文件手册: advanced/inside-class.md - 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md - 接入 WebSocket 客户端: advanced/connect-ws-client.md

View File

@@ -92,6 +92,7 @@ class Hello
return TuringAPI::getTuringMsg($msg, $user_id, $api); return TuringAPI::getTuringMsg($msg, $user_id, $api);
} else { } else {
QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1); QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1);
//执行嵌套消息,递归层级+1
return true; return true;
} }
} }

View File

@@ -20,7 +20,7 @@ class CQ
if (is_numeric($qq) || $qq === "all") { if (is_numeric($qq) || $qq === "all") {
return "[CQ:at,qq=" . $qq . "]"; return "[CQ:at,qq=" . $qq . "]";
} }
Console::warning("传入的QQ号码($qq)错误!"); Console::warning(zm_internal_errcode("E00035") . "传入的QQ号码($qq)错误!");
return " "; return " ";
} }
@@ -33,7 +33,7 @@ class CQ
if (is_numeric($id)) { if (is_numeric($id)) {
return "[CQ:face,id=" . $id . "]"; return "[CQ:face,id=" . $id . "]";
} }
Console::warning("传入的face id($id)错误!"); Console::warning(zm_internal_errcode("E00035") . "传入的face id($id)错误!");
return " "; return " ";
} }
@@ -46,13 +46,13 @@ class CQ
* @param int $timeout * @param int $timeout
* @return string * @return string
*/ */
public static function image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1) { public static function image($file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1) {
return return
"[CQ:image,file=" . self::encode($file, true) . "[CQ:image,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") . (!$cache ? ",cache=0" : "") .
($flash ? ",type=flash" : "") . ($flash ? ",type=flash" : "") .
(!$proxy ? ",proxy=false" : "") . (!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . intval($timeout)) : "") . ($timeout != -1 ? (",timeout=" . $timeout) : "") .
"]"; "]";
} }
@@ -65,13 +65,13 @@ class CQ
* @param int $timeout * @param int $timeout
* @return string * @return string
*/ */
public static function record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1) { public static function record($file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1) {
return return
"[CQ:record,file=" . self::encode($file, true) . "[CQ:record,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") . (!$cache ? ",cache=0" : "") .
($magic ? ",magic=1" : "") . ($magic ? ",magic=1" : "") .
(!$proxy ? ",proxy=false" : "") . (!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . intval($timeout)) : "") . ($timeout != -1 ? (",timeout=" . $timeout) : "") .
"]"; "]";
} }
@@ -83,7 +83,7 @@ class CQ
* @param int $timeout * @param int $timeout
* @return string * @return string
*/ */
public static function video($file, $cache = true, $proxy = true, $timeout = -1) { public static function video($file, bool $cache = true, bool $proxy = true, int $timeout = -1) {
return return
"[CQ:video,file=" . self::encode($file, true) . "[CQ:video,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") . (!$cache ? ",cache=0" : "") .
@@ -123,8 +123,8 @@ class CQ
* @param string $name * @param string $name
* @return string * @return string
*/ */
public static function poke($type, $id, $name = "") { public static function poke($type, $id, string $name = "") {
return "[CQ:poke,type=$type,id=$id" . ($name != "" ? (",name=".self::encode($name, true)) : "") . "]"; return "[CQ:poke,type=$type,id=$id" . ($name != "" ? (",name=" . self::encode($name, true)) : "") . "]";
} }
/** /**
@@ -132,7 +132,7 @@ class CQ
* @param int $ignore * @param int $ignore
* @return string * @return string
*/ */
public static function anonymous($ignore = 1) { public static function anonymous(int $ignore = 1) {
return "[CQ:anonymous" . ($ignore != 1 ? ",ignore=0" : "") . "]"; return "[CQ:anonymous" . ($ignore != 1 ? ",ignore=0" : "") . "]";
} }
@@ -170,12 +170,12 @@ class CQ
* @param string $content * @param string $content
* @return string * @return string
*/ */
public static function location($lat, $lon, $title = "", $content = "") { public static function location($lat, $lon, string $title = "", string $content = "") {
return "[CQ:location" . return "[CQ:location" .
",lat=".self::encode($lat, true) . ",lat=" . self::encode($lat, true) .
",lon=".self::encode($lon, true). ",lon=" . self::encode($lon, true) .
($title != "" ? (",title=".self::encode($title, true)) : "") . ($title != "" ? (",title=" . self::encode($title, true)) : "") .
($content != "" ? (",content=".self::encode($content, true)) : "") . ($content != "" ? (",content=" . self::encode($content, true)) : "") .
"]"; "]";
} }
@@ -205,7 +205,7 @@ class CQ
return "[CQ:music,type=$type,id=$id_or_url]"; return "[CQ:music,type=$type,id=$id_or_url]";
case "custom": case "custom":
if ($title === null || $audio === null) { if ($title === null || $audio === null) {
Console::warning("传入CQ码实例的标题和音频链接不能为空"); Console::warning(zm_internal_errcode("E00035") . "传入CQ码实例的标题和音频链接不能为空");
return " "; return " ";
} }
if ($content === null) $c = ""; if ($content === null) $c = "";
@@ -217,17 +217,17 @@ class CQ
",audio=" . self::encode($audio, true) . ",title=" . self::encode($title, true) . $c . $i . ",audio=" . self::encode($audio, true) . ",title=" . self::encode($title, true) . $c . $i .
"]"; "]";
default: default:
Console::warning("传入的music type($type)错误!"); Console::warning(zm_internal_errcode("E00035") . "传入的music type($type)错误!");
return " "; return " ";
} }
} }
public static function forward($id) { public static function forward($id) {
return "[CQ:forward,id=$id]"; return "[CQ:forward,id=".self::encode($id)."]";
} }
public static function node($user_id, $nickname, $content) { public static function node($user_id, $nickname, $content) {
return "[CQ:node,user_id=$user_id,nickname=".self::encode($nickname, true).",content=" . self::encode($content, true) . "]"; return "[CQ:node,user_id=$user_id,nickname=" . self::encode($nickname, true) . ",content=" . self::encode($content, true) . "]";
} }
public static function xml($data) { public static function xml($data) {
@@ -297,7 +297,7 @@ class CQ
public static function removeCQ($msg) { public static function removeCQ($msg) {
$final = ""; $final = "";
$last_end = 0; $last_end = 0;
foreach(self::getAllCQ($msg) as $k => $v) { foreach (self::getAllCQ($msg) as $v) {
$final .= mb_substr($msg, $last_end, $v["start"] - $last_end); $final .= mb_substr($msg, $last_end, $v["start"] - $last_end);
$last_end = $v["end"] + 1; $last_end = $v["end"] + 1;
} }
@@ -319,7 +319,7 @@ class CQ
$content = mb_substr($msg, $head + 4, $close + $head - mb_strlen($msg)); $content = mb_substr($msg, $head + 4, $close + $head - mb_strlen($msg));
$exp = explode(",", $content); $exp = explode(",", $content);
$cq["type"] = array_shift($exp); $cq["type"] = array_shift($exp);
foreach ($exp as $k => $v) { foreach ($exp as $v) {
$ss = explode("=", $v); $ss = explode("=", $v);
$sk = array_shift($ss); $sk = array_shift($ss);
$cq["params"][$sk] = self::decode(implode("=", $ss), true); $cq["params"][$sk] = self::decode(implode("=", $ss), true);
@@ -349,7 +349,7 @@ class CQ
$exp = explode(",", $content); $exp = explode(",", $content);
$cq = []; $cq = [];
$cq["type"] = array_shift($exp); $cq["type"] = array_shift($exp);
foreach ($exp as $k => $v) { foreach ($exp as $v) {
$ss = explode("=", $v); $ss = explode("=", $v);
$sk = array_shift($ss); $sk = array_shift($ss);
$cq["params"][$sk] = self::decode(implode("=", $ss), true); $cq["params"][$sk] = self::decode(implode("=", $ss), true);

View File

@@ -22,8 +22,6 @@ trait CQAPI
return $this->processWebsocketAPI($connection, $reply, $function); return $this->processWebsocketAPI($connection, $reply, $function);
else else
return $this->processHttpAPI($connection, $reply, $function); return $this->processHttpAPI($connection, $reply, $function);
} }
public function processWebsocketAPI($connection, $reply, $function = false) { public function processWebsocketAPI($connection, $reply, $function = false) {
@@ -41,7 +39,7 @@ trait CQAPI
} }
return true; return true;
} else { } else {
Console::warning("CQAPI send failed, websocket push error."); Console::warning(zm_internal_errcode("E00036") . "CQAPI send failed, websocket push error.");
$response = [ $response = [
"status" => "failed", "status" => "failed",
"retcode" => -1000, "retcode" => -1000,
@@ -69,7 +67,6 @@ trait CQAPI
return false; return false;
} }
/** @noinspection PhpMissingReturnTypeInspection */
public function __call($name, $arguments) { public function __call($name, $arguments) {
return false; return false;
} }

689
src/ZM/API/OneBotV11.php Normal file
View File

@@ -0,0 +1,689 @@
<?php
namespace ZM\API;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Exception\RobotNotFoundException;
/**
* OneBot V11 的 API 实现类
* Class OneBotV11
* @package ZM\API
* @since 2.5
*/
class OneBotV11
{
use CQAPI;
const API_ASYNC = 1;
const API_NORMAL = 0;
const API_RATE_LIMITED = 2;
/** @var ConnectionObject|null */
private $connection;
private $callback = true;
private $prefix = 0;
/**
* @param $robot_id
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function get($robot_id) {
$r = ManagerGM::getAllByName('qq');
foreach ($r as $v) {
if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v);
}
throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!");
}
/**
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function getRandom() {
$r = ManagerGM::getAllByName('qq');
if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
return new ZMRobot($r[array_rand($r)]);
}
public static function getFirst() {
}
/**
* @return ZMRobot[]
*/
public static function getAllRobot() {
$r = ManagerGM::getAllByName('qq');
$obj = [];
foreach ($r as $v) {
$obj[] = new ZMRobot($v);
}
return $obj;
}
public function __construct(ConnectionObject $connection) {
$this->connection = $connection;
}
public function setCallback($callback = true) {
$this->callback = $callback;
return $this;
}
public function setPrefix($prefix = self::API_NORMAL) {
$this->prefix = $prefix;
return $this;
}
public function getSelfId() {
return $this->connection->getOption('connect_id');
}
/* 下面是 OneBot 标准的 V11 公开 API */
/**
* 发送私聊消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
* @param $user_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendPrivateMsg($user_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 发送群消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
* @param $group_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendGroupMsg($group_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 发送消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF
* @param $message_type
* @param $target_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_type' => $message_type,
($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 撤回消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF
* @param $message_id
* @return array|bool|null
*/
public function deleteMsg($message_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_id' => $message_id
]
], $this->callback);
}
/**
* 获取消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF
* @param $message_id
* @return array|bool|null
*/
public function getMsg($message_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_id' => $message_id
]
], $this->callback);
}
/**
* 获取合并转发消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF
* @param $id
* @return array|bool|null
*/
public function getForwardMsg($id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'id' => $id
]
], $this->callback);
}
/**
* 发送好友赞
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E
* @param $user_id
* @param int $times
* @return array|bool|null
*/
public function sendLike($user_id, $times = 1) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'times' => $times
]
], $this->callback);
}
/**
* 群组踢人
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA
* @param $group_id
* @param $user_id
* @param bool $reject_add_request
* @return array|bool|null
*/
public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'reject_add_request' => $reject_add_request
]
], $this->callback);
}
/**
* 群组单人禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
* @param $group_id
* @param $user_id
* @param $duration
* @return array|bool|null
*/
public function setGroupBan($group_id, $user_id, $duration = 1800) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'duration' => $duration
]
], $this->callback);
}
/**
* 群组匿名用户禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80
* @param $group_id
* @param $anonymous_or_flag
* @param int $duration
* @return array|bool|null
*/
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
(is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag,
'duration' => $duration
]
], $this->callback);
}
/**
* 群组全员禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80
* @param $group_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupWholeBan($group_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 群组设置管理员
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98
* @param $group_id
* @param $user_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAdmin($group_id, $user_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 群组匿名
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D
* @param $group_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAnonymous($group_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 设置群名片(群备注)
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8
* @param $group_id
* @param $user_id
* @param string $card
* @return array|bool|null
*/
public function setGroupCard($group_id, $user_id, $card = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'card' => $card
]
], $this->callback);
}
/**
* 设置群名
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D
* @param $group_id
* @param $group_name
* @return array|bool|null
*/
public function setGroupName($group_id, $group_name) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'group_name' => $group_name
]
], $this->callback);
}
/**
* 退出群组
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84
* @param $group_id
* @param bool $is_dismiss
* @return array|bool|null
*/
public function setGroupLeave($group_id, $is_dismiss = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'is_dismiss' => $is_dismiss
]
], $this->callback);
}
/**
* 设置群组专属头衔
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94
* @param $group_id
* @param $user_id
* @param string $special_title
* @param int $duration
* @return array|bool|null
*/
public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'special_title' => $special_title,
'duration' => $duration
]
], $this->callback);
}
/**
* 处理加好友请求
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
* @param $flag
* @param bool $approve
* @param string $remark
* @return array|bool|null
*/
public function setFriendAddRequest($flag, $approve = true, $remark = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'approve' => $approve,
'remark' => $remark
]
], $this->callback);
}
/**
* 处理加群请求/邀请
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
* @param $flag
* @param $sub_type
* @param bool $approve
* @param string $reason
* @return array|bool|null
*/
public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'sub_type' => $sub_type,
'approve' => $approve,
'reason' => $reason
]
], $this->callback);
}
/**
* 获取登录号信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getLoginInfo() {
return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
}
/**
* 获取陌生人信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF
* @param $user_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getStrangerInfo($user_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取好友列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getFriendList() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取群信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF
* @param $group_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupInfo($group_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取群列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getGroupList() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取群成员信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF
* @param $group_id
* @param $user_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取群成员列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
* @param $group_id
* @return array|bool|null
*/
public function getGroupMemberList($group_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id
]
], $this->callback);
}
/**
* 获取群荣誉信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
* @param $group_id
* @param $type
* @return array|bool|null
*/
public function getGroupHonorInfo($group_id, $type) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'type' => $type
]
], $this->callback);
}
/**
* 获取 CSRF Token
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token
* @return array|bool|null
*/
public function getCsrfToken() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取 QQ 相关接口凭证
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81
* @param string $domain
* @return array|bool|null
*/
public function getCredentials($domain = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'domain' => $domain
]
], $this->callback);
}
/**
* 获取语音
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3
* @param $file
* @param $out_format
* @return array|bool|null
*/
public function getRecord($file, $out_format) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file,
'out_format' => $out_format
]
], $this->callback);
}
/**
* 获取图片
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87
* @param $file
* @return array|bool|null
*/
public function getImage($file) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file
]
], $this->callback);
}
/**
* 检查是否可以发送图片
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
* @return array|bool|null
*/
public function canSendImage() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 检查是否可以发送语音
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
* @return array|bool|null
*/
public function canSendRecord() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取运行状态
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81
* @return array|bool|null
*/
public function getStatus() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取版本信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getVersionInfo() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 重启 OneBot 实现
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0
* @param int $delay
* @return array|bool|null
*/
public function setRestart($delay = 0) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'delay' => $delay
]
], $this->callback);
}
/**
* 清理缓存
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98
* @return array|bool|null
*/
public function cleanCache() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function callExtendedAPI($action, $params = []) {
return $this->processAPI($this->connection, [
'action' => $action,
'params' => $params
], $this->callback);
}
private function getActionName(string $method) {
$prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $func_name . $prefix;
}
}

View File

@@ -54,14 +54,14 @@ class TuringAPI
if ($api_return["intent"]["code"] == 4003) { if ($api_return["intent"]["code"] == 4003) {
return "哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗"; return "哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗";
} }
Console::error("图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status); Console::error(zm_internal_errcode("E00038") . "图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status);
//echo json_encode($r, 128|256); //echo json_encode($r, 128|256);
return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?"; return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?";
} }
$result = $api_return["results"]; $result = $api_return["results"];
//Console::info(Console::setColor(json_encode($result, 128 | 256), "green")); //Console::info(Console::setColor(json_encode($result, 128 | 256), "green"));
$final = ""; $final = "";
foreach ($result as $k => $v) { foreach ($result as $v) {
switch ($v["resultType"]) { switch ($v["resultType"]) {
case "url": case "url":
$final .= "\n" . $v["values"]["url"]; $final .= "\n" . $v["values"]["url"];

View File

@@ -5,687 +5,13 @@
namespace ZM\API; namespace ZM\API;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Exception\RobotNotFoundException;
/** /**
* Class ZMRobot * Class ZMRobot
* @package ZM\Utils * @package ZM\Utils
* @since 1.2 * @since 1.2
* @version V11 * @version V11
*/ */
class ZMRobot class ZMRobot extends OneBotV11
{ {
use CQAPI;
const API_ASYNC = 1;
const API_NORMAL = 0;
const API_RATE_LIMITED = 2;
/** @var ConnectionObject|null */
private $connection;
private $callback = true;
private $prefix = 0;
/**
* @param $robot_id
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function get($robot_id) {
$r = ManagerGM::getAllByName('qq');
foreach ($r as $v) {
if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v);
}
throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!");
}
/**
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function getRandom() {
$r = ManagerGM::getAllByName('qq');
if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
return new ZMRobot($r[array_rand($r)]);
}
public static function getFirst() {
}
/**
* @return ZMRobot[]
*/
public static function getAllRobot() {
$r = ManagerGM::getAllByName('qq');
$obj = [];
foreach ($r as $v) {
$obj[] = new ZMRobot($v);
}
return $obj;
}
public function __construct(ConnectionObject $connection) {
$this->connection = $connection;
}
public function setCallback($callback = true) {
$this->callback = $callback;
return $this;
}
public function setPrefix($prefix = self::API_NORMAL) {
$this->prefix = $prefix;
return $this;
}
public function getSelfId() {
return $this->connection->getOption('connect_id');
}
/* 下面是 OneBot 标准的 V11 公开 API */
/**
* 发送私聊消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
* @param $user_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendPrivateMsg($user_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 发送群消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
* @param $group_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendGroupMsg($group_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 发送消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF
* @param $message_type
* @param $target_id
* @param $message
* @param bool $auto_escape
* @return array|bool|null
*/
public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_type' => $message_type,
($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
/**
* 撤回消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF
* @param $message_id
* @return array|bool|null
*/
public function deleteMsg($message_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_id' => $message_id
]
], $this->callback);
}
/**
* 获取消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF
* @param $message_id
* @return array|bool|null
*/
public function getMsg($message_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_id' => $message_id
]
], $this->callback);
}
/**
* 获取合并转发消息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF
* @param $id
* @return array|bool|null
*/
public function getForwardMsg($id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'id' => $id
]
], $this->callback);
}
/**
* 发送好友赞
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E
* @param $user_id
* @param int $times
* @return array|bool|null
*/
public function sendLike($user_id, $times = 1) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'times' => $times
]
], $this->callback);
}
/**
* 群组踢人
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA
* @param $group_id
* @param $user_id
* @param bool $reject_add_request
* @return array|bool|null
*/
public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'reject_add_request' => $reject_add_request
]
], $this->callback);
}
/**
* 群组单人禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
* @param $group_id
* @param $user_id
* @param $duration
* @return array|bool|null
*/
public function setGroupBan($group_id, $user_id, $duration = 1800) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'duration' => $duration
]
], $this->callback);
}
/**
* 群组匿名用户禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80
* @param $group_id
* @param $anonymous_or_flag
* @param int $duration
* @return array|bool|null
*/
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
(is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag,
'duration' => $duration
]
], $this->callback);
}
/**
* 群组全员禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80
* @param $group_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupWholeBan($group_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 群组设置管理员
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98
* @param $group_id
* @param $user_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAdmin($group_id, $user_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 群组匿名
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D
* @param $group_id
* @param bool $enable
* @return array|bool|null
*/
public function setGroupAnonymous($group_id, $enable = true) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
/**
* 设置群名片(群备注)
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8
* @param $group_id
* @param $user_id
* @param string $card
* @return array|bool|null
*/
public function setGroupCard($group_id, $user_id, $card = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'card' => $card
]
], $this->callback);
}
/**
* 设置群名
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D
* @param $group_id
* @param $group_name
* @return array|bool|null
*/
public function setGroupName($group_id, $group_name) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'group_name' => $group_name
]
], $this->callback);
}
/**
* 退出群组
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84
* @param $group_id
* @param bool $is_dismiss
* @return array|bool|null
*/
public function setGroupLeave($group_id, $is_dismiss = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'is_dismiss' => $is_dismiss
]
], $this->callback);
}
/**
* 设置群组专属头衔
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94
* @param $group_id
* @param $user_id
* @param string $special_title
* @param int $duration
* @return array|bool|null
*/
public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'special_title' => $special_title,
'duration' => $duration
]
], $this->callback);
}
/**
* 处理加好友请求
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
* @param $flag
* @param bool $approve
* @param string $remark
* @return array|bool|null
*/
public function setFriendAddRequest($flag, $approve = true, $remark = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'approve' => $approve,
'remark' => $remark
]
], $this->callback);
}
/**
* 处理加群请求/邀请
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
* @param $flag
* @param $sub_type
* @param bool $approve
* @param string $reason
* @return array|bool|null
*/
public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'sub_type' => $sub_type,
'approve' => $approve,
'reason' => $reason
]
], $this->callback);
}
/**
* 获取登录号信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getLoginInfo() {
return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
}
/**
* 获取陌生人信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF
* @param $user_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getStrangerInfo($user_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取好友列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getFriendList() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取群信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF
* @param $group_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupInfo($group_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取群列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
* @return array|bool|null
*/
public function getGroupList() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取群成员信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF
* @param $group_id
* @param $user_id
* @param bool $no_cache
* @return array|bool|null
*/
public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
/**
* 获取群成员列表
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
* @param $group_id
* @return array|bool|null
*/
public function getGroupMemberList($group_id) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id
]
], $this->callback);
}
/**
* 获取群荣誉信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
* @param $group_id
* @param $type
* @return array|bool|null
*/
public function getGroupHonorInfo($group_id, $type) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'type' => $type
]
], $this->callback);
}
/**
* 获取 CSRF Token
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token
* @return array|bool|null
*/
public function getCsrfToken() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取 QQ 相关接口凭证
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81
* @param string $domain
* @return array|bool|null
*/
public function getCredentials($domain = "") {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'domain' => $domain
]
], $this->callback);
}
/**
* 获取语音
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3
* @param $file
* @param $out_format
* @return array|bool|null
*/
public function getRecord($file, $out_format) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file,
'out_format' => $out_format
]
], $this->callback);
}
/**
* 获取图片
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87
* @param $file
* @return array|bool|null
*/
public function getImage($file) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file
]
], $this->callback);
}
/**
* 检查是否可以发送图片
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
* @return array|bool|null
*/
public function canSendImage() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 检查是否可以发送语音
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
* @return array|bool|null
*/
public function canSendRecord() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取运行状态
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81
* @return array|bool|null
*/
public function getStatus() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 获取版本信息
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
* @return array|bool|null
*/
public function getVersionInfo() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
/**
* 重启 OneBot 实现
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0
* @param int $delay
* @return array|bool|null
*/
public function setRestart($delay = 0) {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'delay' => $delay
]
], $this->callback);
}
/**
* 清理缓存
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98
* @return array|bool|null
*/
public function cleanCache() {
return $this->processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function callExtendedAPI($action, $params = []) {
return $this->processAPI($this->connection, [
'action' => $action,
'params' => $params
], $this->callback);
}
private function getActionName(string $method) {
$prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $func_name . $prefix;
}
} }

View File

@@ -17,7 +17,8 @@ use ZM\Annotation\Http\MiddlewareClass;
use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed; use ZM\Annotation\Module\Closed;
use ZM\Http\RouteManager; use ZM\Utils\Manager\RouteManager;
use ZM\Utils\ZMUtil;
class AnnotationParser class AnnotationParser
{ {
@@ -53,7 +54,7 @@ class AnnotationParser
public function registerMods() { public function registerMods() {
foreach ($this->path_list as $path) { foreach ($this->path_list as $path) {
Console::debug("parsing annotation in " . $path[0]); Console::debug("parsing annotation in " . $path[0]);
$all_class = getAllClasses($path[0], $path[1]); $all_class = ZMUtil::getClassesPsr4($path[0], $path[1]);
$this->reader = new AnnotationReader(); $this->reader = new AnnotationReader();
foreach ($all_class as $v) { foreach ($all_class as $v) {
Console::debug("正在检索 " . $v); Console::debug("正在检索 " . $v);
@@ -88,7 +89,7 @@ class AnnotationParser
} }
foreach ($this->annotation_map[$v]["class_annotations"] as $ks => $vs) { foreach ($this->annotation_map[$v]["class_annotations"] as $vs) {
$vs->class = $v; $vs->class = $v;
//预处理1将适用于每一个函数的注解到类注解重新注解到每个函数下面 //预处理1将适用于每一个函数的注解到类注解重新注解到每个函数下面
@@ -132,12 +133,12 @@ class AnnotationParser
public function generateAnnotationEvents() { public function generateAnnotationEvents() {
$o = []; $o = [];
foreach ($this->annotation_map as $module => $obj) { foreach ($this->annotation_map as $obj) {
foreach (($obj["class_annotations"] ?? []) as $class_annotation) { foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
if ($class_annotation instanceof ErgodicAnnotation) continue; if ($class_annotation instanceof ErgodicAnnotation) continue;
else $o[get_class($class_annotation)][] = $class_annotation; else $o[get_class($class_annotation)][] = $class_annotation;
} }
foreach (($obj["methods_annotations"] ?? []) as $method_name => $methods_annotations) { foreach (($obj["methods_annotations"] ?? []) as $methods_annotations) {
foreach ($methods_annotations as $annotation) { foreach ($methods_annotations as $annotation) {
$o[get_class($annotation)][] = $annotation; $o[get_class($annotation)][] = $annotation;
} }

View File

@@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use ZM\Console\TermColor; use ZM\Console\TermColor;
use ZM\Utils\DataProvider;
class BuildCommand extends Command class BuildCommand extends Command
{ {
@@ -21,28 +22,28 @@ class BuildCommand extends Command
protected function configure() { protected function configure() {
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包"); $this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。"); $this->setHelp("此功能将会把整个项目打包为phar");
$this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录"); $this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
// ... // ...
} }
protected function execute(InputInterface $input, OutputInterface $output): int { protected function execute(InputInterface $input, OutputInterface $output): int {
$this->output = $output; $this->output = $output;
$target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/'); $target_dir = $input->getOption("target") ?? (WORKING_DIR);
if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir); if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir);
if ($target_dir === false) { if ($target_dir === false) {
$output->writeln(TermColor::color8(31) . "Error: No such file or directory (" . __DIR__ . '/../../../resources/' . ")" . TermColor::RESET); $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory (" . $target_dir . ")" . TermColor::RESET);
return 1; return 1;
} }
$output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"])); $output->writeln("Target: " . $target_dir);
if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/"; if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/";
if (ini_get('phar.readonly') == 1) { if (ini_get('phar.readonly') == 1) {
$output->writeln(TermColor::color8(31) . "You need to set \"phar.readonly\" to \"Off\"!"); $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00040") . "You need to set \"phar.readonly\" to \"Off\"!");
$output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing"); $output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing");
return 1; return 1;
} }
if (!is_dir($target_dir)) { if (!is_dir($target_dir)) {
$output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET); $output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory ($target_dir)" . TermColor::RESET);
return 1; return 1;
} }
$filename = "server.phar"; $filename = "server.phar";
@@ -55,19 +56,43 @@ class BuildCommand extends Command
@unlink($target_dir . $filename); @unlink($target_dir . $filename);
$phar = new Phar($target_dir . $filename); $phar = new Phar($target_dir . $filename);
$phar->startBuffering(); $phar->startBuffering();
$src = realpath(__DIR__ . '/../../zhamao-framework/');
$hello = file_get_contents($src . '/src/Module/Example/Hello.php'); $allow_dir = ["bin", "config", "resources", "src", "vendor", "composer.json", "README.md", "zhamao"];
$middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php');
unlink($src . '/src/Module/Example/Hello.php'); $archive_dir = DataProvider::getSourceRootDir();
unlink($src . '/src/Module/Middleware/TimerMiddleware.php'); $scan = scandir($archive_dir);
$phar->buildFromDirectory($src); if ($scan[0] == ".") {
$phar->addFromString('tmp/Hello.php.bak', $hello); unset($scan[0], $scan[1]);
$phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware); }
//$phar->compressFiles(Phar::GZ); foreach ($scan as $v) {
$phar->setStub($phar->createDefaultStub('phar-starter.php')); 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);
}
}
}
$phar->setStub(
"#!/usr/bin/env php\n" .
$phar->createDefaultStub(LOAD_MODE == 0 ? "src/entry.php" : "vendor/zhamao/framework/src/entry.php")
);
$phar->stopBuffering(); $phar->stopBuffering();
file_put_contents($src . '/src/Module/Example/Hello.php', $hello);
file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware);
$this->output->writeln("Successfully built. Location: " . $target_dir . "$filename"); $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);
}
}
}
} }

View File

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

View File

@@ -1,7 +1,7 @@
<?php <?php
namespace ZM\Command; namespace ZM\Command\Daemon;
use Swoole\Process; use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;

View File

@@ -1,7 +1,7 @@
<?php <?php
namespace ZM\Command; namespace ZM\Command\Daemon;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;

View File

@@ -1,7 +1,7 @@
<?php <?php
namespace ZM\Command; namespace ZM\Command\Daemon;
use Swoole\Process; use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;

View File

@@ -45,13 +45,13 @@ class InitCommand extends Command
$info = pathinfo($file); $info = pathinfo($file);
@mkdir($base_path . $info["dirname"], 0777, true); @mkdir($base_path . $info["dirname"], 0777, true);
echo "Copying " . $file . PHP_EOL; echo "Copying " . $file . PHP_EOL;
$package_name = ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["name"]); $package_name = (json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["name"]);
copy($base_path . "/vendor/" . $package_name . $file, $base_path . $file); copy($base_path . "/vendor/" . $package_name . $file, $base_path . $file);
} else { } else {
echo "Skipping " . $file . " , file exists." . PHP_EOL; echo "Skipping " . $file . " , file exists." . PHP_EOL;
} }
} }
chmod($base_path."/zhamao", 0755); chmod($base_path . "/zhamao", 0755);
$autoload = [ $autoload = [
"psr-4" => [ "psr-4" => [
"Module\\" => "src/Module", "Module\\" => "src/Module",
@@ -69,7 +69,7 @@ class InitCommand extends Command
foreach ($autoload["psr-4"] as $k => $v) { foreach ($autoload["psr-4"] as $k => $v) {
if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v; if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v;
} }
foreach ($autoload["files"] as $k => $v) { foreach ($autoload["files"] as $v) {
if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v; if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v;
} }
} }
@@ -78,7 +78,7 @@ class InitCommand extends Command
exec("composer dump-autoload"); exec("composer dump-autoload");
echo PHP_EOL; echo PHP_EOL;
} else { } else {
echo("Error occurred. Please check your updates.\n"); echo(zm_internal_errcode("E00041") . "Error occurred. Please check your updates.\n");
return 1; return 1;
} }
$output->writeln("<info>Done!</info>"); $output->writeln("<info>Done!</info>");
@@ -99,7 +99,7 @@ class InitCommand extends Command
} }
} }
} }
$output->writeln("initialization must be started with composer-project mode!"); $output->writeln(zm_internal_errcode("E00042") . "initialization must be started with composer-project mode!");
return 1; return 1;
} }

View File

@@ -0,0 +1,75 @@
<?php
namespace ZM\Command\Module;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ModuleManager;
class ModuleListCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'module:list';
protected function configure() {
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
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");
}
//定义常量
include_once DataProvider::getFrameworkRootDir() . "/src/ZM/global_defines.php";
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
null,
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
$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"];
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);
}
$list = ModuleManager::getPackedModules();
foreach ($list as $v) {
echo "[" . Console::setColor($v["name"], "gold") . "]" . PHP_EOL;
$out_list = ["类型" => "archive(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;
}
return 0;
}
private function printList($list) {
foreach ($list as $k => $v) {
echo "\t" . $k . ": " . Console::setColor($v, "yellow") . PHP_EOL;
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
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;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ModuleManager;
class ModulePackCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'module:pack';
protected function configure() {
$this->addArgument("module-name", InputArgument::REQUIRED);
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
$this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
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");
}
//定义常量
include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php";
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
null,
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
$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>");
return 1;
}
$result = ModuleManager::packModule($list[$input->getArgument("module-name")]);
if ($result) Console::success("打包完成!");
else Console::error("打包失败!");
return 0;
}
}

View File

@@ -0,0 +1,57 @@
<?php
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\Output\OutputInterface;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ModuleManager;
class ModuleUnpackCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'module:unpack';
protected function configure() {
$this->addArgument("module-name", InputArgument::REQUIRED);
$this->setDescription("Unpack a phar module into src directory");
$this->setHelp("此功能将phar格式的模块包解包到src目录下。");
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");
}
//定义常量
include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php";
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
null,
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
$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")]);
if ($result) Console::success("解压完成!");
else Console::error("解压失败!");
return 0;
}
}

View File

@@ -41,7 +41,7 @@ class PureHttpCommand extends Command
$output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>"); $output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>");
return self::FAILURE; return self::FAILURE;
} }
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
$global = ZMConfig::get("global"); $global = ZMConfig::get("global");
$host = $input->getOption("host") ?? $global["host"]; $host = $input->getOption("host") ?? $global["host"];
$port = $input->getOption("port") ?? $global["port"]; $port = $input->getOption("port") ?? $global["port"];

View File

@@ -23,6 +23,7 @@ class RunServerCommand extends Command
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"), new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"), new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"), new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
new InputOption("interact", null, null, "打开终端输入"),
new InputOption("remote-terminal", null, null, "启用远程终端配置使用global.php中的"), new InputOption("remote-terminal", null, null, "启用远程终端配置使用global.php中的"),
new InputOption("disable-coroutine", null, null, "关闭协程Hook"), new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
new InputOption("daemon", null, null, "以守护进程的方式运行框架"), new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
@@ -32,7 +33,8 @@ class RunServerCommand extends Command
new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"), new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"),
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"), new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
new InputOption("disable-safe-exit", null, null, "关闭安全退出关闭后按CtrlC时直接杀死进程"), new InputOption("disable-safe-exit", null, null, "关闭安全退出关闭后按CtrlC时直接杀死进程"),
new InputOption("preview", null, null, "只显示参数,不启动服务器") new InputOption("preview", null, null, "只显示参数,不启动服务器"),
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)")
]); ]);
$this->setDescription("Run zhamao-framework | 启动框架"); $this->setDescription("Run zhamao-framework | 启动框架");
$this->setHelp("直接运行可以启动"); $this->setHelp("直接运行可以启动");

View File

@@ -29,11 +29,8 @@ class SystemdCommand extends Command
$s .= "\nUser=" . exec("whoami"); $s .= "\nUser=" . exec("whoami");
$s .= "\nGroup=" . exec("groups | awk '{print $1}'"); $s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd(); $s .= "\nWorkingDirectory=" . getcwd();
if (LOAD_MODE == 1) { global $argv;
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server"; $s .= "\nExecStart=".PHP_BINARY." {$argv[0]} server";
} else {
$s .= "\nExecStart=" . getcwd() . "/bin/start server";
}
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"; $s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/"); @mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s); file_put_contents(getcwd() . "/resources/zhamao.service", $s);

View File

@@ -5,22 +5,28 @@ namespace ZM;
use Exception; use Exception;
use Phar;
use ZM\Command\BuildCommand;
use ZM\Command\CheckConfigCommand; use ZM\Command\CheckConfigCommand;
use ZM\Command\DaemonReloadCommand; use ZM\Command\Daemon\DaemonReloadCommand;
use ZM\Command\DaemonStatusCommand; use ZM\Command\Daemon\DaemonStatusCommand;
use ZM\Command\DaemonStopCommand; use ZM\Command\Daemon\DaemonStopCommand;
use ZM\Command\InitCommand; use ZM\Command\InitCommand;
use ZM\Command\Module\ModuleListCommand;
use ZM\Command\Module\ModulePackCommand;
use ZM\Command\Module\ModuleUnpackCommand;
use ZM\Command\PureHttpCommand; use ZM\Command\PureHttpCommand;
use ZM\Command\RunServerCommand; use ZM\Command\RunServerCommand;
use Symfony\Component\Console\Application; use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\SystemdCommand; use ZM\Command\SystemdCommand;
use ZM\Console\Console;
class ConsoleApplication extends Application class ConsoleApplication extends Application
{ {
const VERSION_ID = 407; const VERSION_ID = 408;
const VERSION = "2.5.0"; const VERSION = "2.5.0-b1";
public function __construct(string $name = 'UNKNOWN') { public function __construct(string $name = 'UNKNOWN') {
define("ZM_VERSION_ID", self::VERSION_ID); define("ZM_VERSION_ID", self::VERSION_ID);
@@ -32,10 +38,18 @@ class ConsoleApplication extends Application
$this->selfCheck(); $this->selfCheck();
define("WORKING_DIR", getcwd()); define("WORKING_DIR", getcwd());
define("LOAD_MODE", is_dir(WORKING_DIR . "/src/ZM") ? 0 : 1); if (Phar::running() !== "") {
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../../")); echo "* Running in phar mode.\n";
define("SOURCE_ROOT_DIR", Phar::running());
define("LOAD_MODE", is_dir(SOURCE_ROOT_DIR . "/src/ZM") ? 0 : 1);
define("FRAMEWORK_ROOT_DIR", LOAD_MODE == 1 ? (SOURCE_ROOT_DIR . "/vendor/zhamao/framework") : SOURCE_ROOT_DIR);
} else {
define("SOURCE_ROOT_DIR", WORKING_DIR);
define("LOAD_MODE", is_dir(SOURCE_ROOT_DIR . "/src/ZM") ? 0 : 1);
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../../"));
}
if (LOAD_MODE == 0) { if (LOAD_MODE == 0) {
$composer = json_decode(file_get_contents(WORKING_DIR . "/composer.json"), true); $composer = json_decode(file_get_contents(SOURCE_ROOT_DIR . "/composer.json"), true);
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) { if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] "; echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] ";
$r = strtolower(trim(fgets(STDIN))); $r = strtolower(trim(fgets(STDIN)));
@@ -47,7 +61,7 @@ class ConsoleApplication extends Application
echo "成功添加!请运行 composer dump-autoload \n"; echo "成功添加!请运行 composer dump-autoload \n";
exit(0); exit(0);
} else { } else {
echo "添加失败!请按任意键继续!"; echo zm_internal_errcode("E00006") . "添加失败!请按任意键继续!";
fgets(STDIN); fgets(STDIN);
exit(1); exit(1);
} }
@@ -62,13 +76,19 @@ class ConsoleApplication extends Application
new DaemonReloadCommand(), new DaemonReloadCommand(),
new DaemonStopCommand(), new DaemonStopCommand(),
new RunServerCommand(), //运行主服务的指令控制器 new RunServerCommand(), //运行主服务的指令控制器
new InitCommand(), //初始化用的用于项目初始化和phar初始化
new PureHttpCommand(), //纯HTTP服务器指令 new PureHttpCommand(), //纯HTTP服务器指令
new SystemdCommand() new SystemdCommand()
]); ]);
if (LOAD_MODE === 1) { if (LOAD_MODE === 1) {
$this->add(new CheckConfigCommand()); $this->add(new CheckConfigCommand());
} }
if (Phar::running() === "") {
$this->add(new BuildCommand());
$this->add(new InitCommand());
$this->add(new ModulePackCommand());
$this->add(new ModuleListCommand());
$this->add(new ModuleUnpackCommand());
}
if (!empty($with_default_cmd)) { if (!empty($with_default_cmd)) {
$this->setDefaultCommand($with_default_cmd); $this->setDefaultCommand($with_default_cmd);
} }
@@ -84,14 +104,20 @@ class ConsoleApplication extends Application
try { try {
return parent::run($input, $output); return parent::run($input, $output);
} catch (Exception $e) { } catch (Exception $e) {
die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); die(zm_internal_errcode("E00005") . "{$e->getMessage()} at {$e->getFile()}({$e->getLine()})");
} }
} }
private function selfCheck(): bool { /**
if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19\n"); * 启动炸毛前要做的环境检查项目
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die("You must install swoole version >= 4.5.0 !"); */
if (version_compare(PHP_VERSION, "7.2") == -1) die("PHP >= 7.2 required."); private function selfCheck(): void {
return true; if (!extension_loaded("swoole")) die(zm_internal_errcode("E00001") . "Can not find swoole extension.\n");
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die(zm_internal_errcode("E00002") . "You must install swoole version >= 4.5.0 !");
if (version_compare(PHP_VERSION, "7.2") == -1) die(zm_internal_errcode("E00003") . "PHP >= 7.2 required.");
if (version_compare(SWOOLE_VERSION, "4.6.7") < 0 && !extension_loaded("pcntl")) {
Console::error(zm_internal_errcode("E00004") . "Swoole 版本必须不低于 4.6.7 或 PHP 安装加载了 pcntl 扩展!");
die();
}
} }
} }

View File

@@ -4,8 +4,8 @@
namespace ZM\Context; namespace ZM\Context;
use Co;
use Exception; use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request; use Swoole\Http\Request;
use Swoole\WebSocket\Frame; use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server; use Swoole\WebSocket\Server;
@@ -104,8 +104,7 @@ class Context implements ContextInterface
* only can used by cq->message event function * only can used by cq->message event function
* @param $msg * @param $msg
* @param bool $yield * @param bool $yield
* @return mixed * @return array|bool
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function reply($msg, $yield = false) { public function reply($msg, $yield = false) {
$data = $this->getData(); $data = $this->getData();
@@ -136,7 +135,7 @@ class Context implements ContextInterface
/** /**
* @param $msg * @param $msg
* @param false $yield * @param false $yield
* @return mixed|void * @return void
* @throws InterruptException * @throws InterruptException
*/ */
public function finalReply($msg, $yield = false) { public function finalReply($msg, $yield = false) {
@@ -170,42 +169,6 @@ class Context implements ContextInterface
throw new WaitTimeoutException($this, $timeout_prompt); throw new WaitTimeoutException($this, $timeout_prompt);
} }
return $r["message"]; return $r["message"];
/*
$cid = Co::getuid();
$api_id = ZMAtomic::get("wait_msg_id")->add(1);
$hang = [
"coroutine" => $cid,
"user_id" => $this->getData()["user_id"],
"message" => $this->getData()["message"],
"self_id" => $this->getData()["self_id"],
"message_type" => $this->getData()["message_type"],
"result" => null
];
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
$hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"];
}
SpinLock::lock("wait_api");
$hw = LightCacheInside::get("wait_api", "wait_api") ?? [];
$hw[$api_id] = $hang;
LightCacheInside::set("wait_api", "wait_api", $hw);
SpinLock::unlock("wait_api");
$id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) {
$r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null;
if (is_array($r)) {
Co::resume($r["coroutine"]);
}
});
Co::suspend();
SpinLock::lock("wait_api");
$hw = LightCacheInside::get("wait_api", "wait_api") ?? [];
$sess = $hw[$api_id];
unset($hw[$api_id]);
LightCacheInside::set("wait_api", "wait_api", $hw);
$result = $sess["result"];
if (isset($id)) swoole_timer_clear($id);
if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
return $result;*/
} }
/** /**
@@ -269,7 +232,7 @@ class Context implements ContextInterface
/** @noinspection PhpMissingReturnTypeInspection */ /** @noinspection PhpMissingReturnTypeInspection */
public function cloneFromParent() { public function cloneFromParent() {
set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); set_coroutine_params(self::$context[Coroutine::getPcid()] ?? self::$context[$this->cid]);
return context(); return context();
} }

View File

@@ -54,9 +54,9 @@ class EventManager
try { try {
$dispatcher->dispatchEvent($vss, null); $dispatcher->dispatchEvent($vss, null);
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})"); Console::error(zm_internal_errcode("E00034") . "Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
} catch (Error $e) { } catch (Error $e) {
Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage()); Console::error(zm_internal_errcode("E00034") . "Uncaught fatal error from TimerTick: " . $e->getMessage());
echo Console::setColor($e->getTraceAsString(), "gray"); echo Console::setColor($e->getTraceAsString(), "gray");
Console::error("Please check your code!"); Console::error("Please check your code!");
} }

View File

@@ -4,9 +4,9 @@
namespace ZM\Event\SwooleEvent; namespace ZM\Event\SwooleEvent;
use Co;
use Error; use Error;
use Exception; use Exception;
use Swoole\Coroutine;
use ZM\Annotation\Swoole\OnCloseEvent; use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Annotation\Swoole\SwooleHandler; use ZM\Annotation\Swoole\SwooleHandler;
@@ -27,7 +27,7 @@ class OnClose implements SwooleEvent
{ {
/** @noinspection PhpUnreachableStatementInspection */ /** @noinspection PhpUnreachableStatementInspection */
public function onCall($server, $fd) { public function onCall($server, $fd) {
unset(Context::$context[Co::getCid()]); unset(Context::$context[Coroutine::getCid()]);
Console::debug("Calling Swoole \"close\" event from fd=" . $fd); Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
$conn = ManagerGM::get($fd); $conn = ManagerGM::get($fd);
if ($conn === null) return; if ($conn === null) return;
@@ -61,11 +61,11 @@ class OnClose implements SwooleEvent
$dispatcher->dispatchEvents($conn); $dispatcher->dispatchEvents($conn);
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg); Console::error(zm_internal_errcode("E00016") . "Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg); Console::error(zm_internal_errcode("E00016") . "Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg);
Console::trace(); Console::trace();
} }
ManagerGM::popConnect($fd); ManagerGM::popConnect($fd);

View File

@@ -11,6 +11,7 @@ use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Event\SwooleEvent; use ZM\Event\SwooleEvent;
use ZM\Framework; use ZM\Framework;
use ZM\Utils\SignalListener;
/** /**
* Class OnManagerStart * Class OnManagerStart
@@ -21,9 +22,7 @@ class OnManagerStart implements SwooleEvent
{ {
public function onCall(Server $server) { public function onCall(Server $server) {
if (!Framework::$argv["disable-safe-exit"]) { if (!Framework::$argv["disable-safe-exit"]) {
pcntl_signal(SIGINT, function () { SignalListener::signalManager();
Console::verbose("Interrupted in manager!");
});
} }
Console::verbose("进程 Manager 已启动"); Console::verbose("进程 Manager 已启动");
} }

View File

@@ -56,11 +56,11 @@ class OnMessage implements SwooleEvent
//Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); //Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!");
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); Console::error(zm_internal_errcode("E00017") . "Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg); Console::error(zm_internal_errcode("E00017") . "Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace(); Console::trace();
} }

View File

@@ -5,9 +5,9 @@ namespace ZM\Event\SwooleEvent;
use Closure; use Closure;
use Co;
use Error; use Error;
use Exception; use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request; use Swoole\Http\Request;
use ZM\Annotation\Swoole\OnOpenEvent; use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\OnSwooleEvent;
@@ -30,20 +30,20 @@ class OnOpen implements SwooleEvent
/** @noinspection PhpUnreachableStatementInspection */ /** @noinspection PhpUnreachableStatementInspection */
public function onCall($server, Request $request) { public function onCall($server, Request $request) {
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
unset(Context::$context[Co::getCid()]); unset(Context::$context[Coroutine::getCid()]);
$type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? ""); $type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? "");
$access_token = explode(" ", $request->header["authorization"] ?? "")[1] ?? $request->get["token"] ?? ""; $access_token = explode(" ", $request->header["authorization"] ?? "")[1] ?? $request->get["token"] ?? "";
$token = ZMConfig::get("global", "access_token"); $token = ZMConfig::get("global", "access_token");
if ($token instanceof Closure) { if ($token instanceof Closure) {
if (!$token($access_token)) { if (!$token($access_token)) {
$server->close($request->fd); $server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token); Console::warning(zm_internal_errcode("E00018") . "Unauthorized access_token: " . $access_token);
return; return;
} }
} elseif (is_string($token)) { } elseif (is_string($token)) {
if ($access_token !== $token && $token !== "") { if ($access_token !== $token && $token !== "") {
$server->close($request->fd); $server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token); Console::warning(zm_internal_errcode("E00019") . "Unauthorized access_token: " . $access_token);
return; return;
} }
} }
@@ -81,11 +81,11 @@ class OnOpen implements SwooleEvent
$dispatcher->dispatchEvents($conn); $dispatcher->dispatchEvents($conn);
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); Console::error(zm_internal_errcode("E00020") . "Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); Console::error(zm_internal_errcode("E00020") . "Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace(); Console::trace();
} }
} }

View File

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

View File

@@ -4,9 +4,9 @@
namespace ZM\Event\SwooleEvent; namespace ZM\Event\SwooleEvent;
use Co;
use Error; use Error;
use Exception; use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request; use Swoole\Http\Request;
use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnRequestEvent; use ZM\Annotation\Swoole\OnRequestEvent;
@@ -33,7 +33,7 @@ class OnRequest implements SwooleEvent
foreach (ZMConfig::get("global")["http_header"] as $k => $v) { foreach (ZMConfig::get("global")["http_header"] as $k => $v) {
$response->setHeader($k, $v); $response->setHeader($k, $v);
} }
unset(Context::$context[Co::getCid()]); unset(Context::$context[Coroutine::getCid()]);
Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd); Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
set_coroutine_params(["request" => $request, "response" => $response]); set_coroutine_params(["request" => $request, "response" => $response]);
@@ -86,11 +86,11 @@ class OnRequest implements SwooleEvent
); );
if (!$response->isEnd()) { if (!$response->isEnd()) {
if (ZMConfig::get("global", "debug_mode")) if (ZMConfig::get("global", "debug_mode"))
$response->end("Internal server exception: " . $e->getMessage()); $response->end(zm_internal_errcode("E00023") . "Internal server exception: " . $e->getMessage());
else else
$response->end("Internal server error."); $response->end(zm_internal_errcode("E00023") . "Internal server error.");
} }
Console::error("Internal server exception (500), caused by " . get_class($e) . ": " . $e->getMessage()); Console::error(zm_internal_errcode("E00023") . "Internal server exception (500), caused by " . get_class($e) . ": " . $e->getMessage());
Console::log($e->getTraceAsString(), "gray"); Console::log($e->getTraceAsString(), "gray");
} catch (Error $e) { } catch (Error $e) {
$response->status(500); $response->status(500);
@@ -100,11 +100,11 @@ class OnRequest implements SwooleEvent
if (!$response->isEnd()) { if (!$response->isEnd()) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
if (ZMConfig::get("global", "debug_mode")) if (ZMConfig::get("global", "debug_mode"))
$response->end("Internal server error: " . $error_msg); $response->end(zm_internal_errcode("E00023") . "Internal server error: " . $error_msg);
else else
$response->end("Internal server error."); $response->end(zm_internal_errcode("E00023") . "Internal server error.");
} }
Console::error("Internal server error (500), caused by " . get_class($e) . ": " . $e->getMessage()); Console::error(zm_internal_errcode("E00023") . "Internal server error (500), caused by " . get_class($e) . ": " . $e->getMessage());
Console::log($e->getTraceAsString(), "gray"); Console::log($e->getTraceAsString(), "gray");
} }
} }

View File

@@ -4,15 +4,18 @@
namespace ZM\Event\SwooleEvent; namespace ZM\Event\SwooleEvent;
use Error;
use Exception;
use Swoole\Event; use Swoole\Event;
use Swoole\Process;
use Swoole\Server; use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler; use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Config\ZMConfig;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Event\SwooleEvent; use ZM\Event\SwooleEvent;
use ZM\Framework; use ZM\Framework;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider; use ZM\Utils\DataProvider;
use ZM\Utils\SignalListener;
use ZM\Utils\Terminal;
use ZM\Utils\ZMUtil; use ZM\Utils\ZMUtil;
/** /**
@@ -23,48 +26,49 @@ use ZM\Utils\ZMUtil;
class OnStart implements SwooleEvent class OnStart implements SwooleEvent
{ {
public function onCall(Server $server) { public function onCall(Server $server) {
$r = null;
if (!Framework::$argv["disable-safe-exit"]) { if (!Framework::$argv["disable-safe-exit"]) {
Process::signal(SIGINT, function () use ($r, $server) { SignalListener::signalMaster($server);
if (zm_atomic("_int_is_reload")->get() === 1) {
zm_atomic("_int_is_reload")->set(0);
$server->reload();
} else {
echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
if ((Framework::$server->inotify ?? null) !== null)
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
Process::kill($server->master_pid, SIGTERM);
}
});
}
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);
} }
if (Framework::$argv["watch"]) { if (Framework::$argv["watch"]) {
if (extension_loaded('inotify')) { if (extension_loaded('inotify')) {
Console::info("Enabled File watcher, framework will reload automatically."); Console::info("Enabled File watcher, framework will reload automatically.");
/** @noinspection PhpUndefinedFieldInspection */ /** @noinspection PhpUndefinedFieldInspection */
Framework::$server->inotify = $fd = inotify_init(); Framework::$server->inotify = $fd = inotify_init();
$this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd); $this->addWatcher(DataProvider::getSourceRootDir() . "/src", $fd);
Event::add($fd, function () use ($fd) { Event::add($fd, function () use ($fd) {
$r = inotify_read($fd); $r = inotify_read($fd);
Console::verbose("File updated: ".$r[0]["name"]); Console::verbose("File updated: " . $r[0]["name"]);
ZMUtil::reload(); ZMUtil::reload();
}); });
} else { } else {
Console::warning("You have not loaded \"inotify\" extension, please install first."); 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() . ")");
}
});
}
} }
private function addWatcher($maindir, $fd) { private function addWatcher($maindir, $fd) {
$dir = scandir($maindir); $dir = scandir($maindir);
unset($dir[0], $dir[1]); if ($dir[0] == ".") {
unset($dir[0], $dir[1]);
}
foreach ($dir as $subdir) { foreach ($dir as $subdir) {
if (is_dir($maindir . "/" . $subdir)) { if (is_dir($maindir . "/" . $subdir)) {
Console::debug("添加监听目录:" . $maindir . "/" . $subdir); Console::debug("添加监听目录:" . $maindir . "/" . $subdir);

View File

@@ -65,11 +65,11 @@ class OnTask implements SwooleEvent
$dispatcher->dispatchEvents(); $dispatcher->dispatchEvents();
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"task\": " . $error_msg); Console::error(zm_internal_errcode("E00026") . "Uncaught exception " . get_class($e) . " when calling \"task\": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"task\": " . $error_msg); Console::error(zm_internal_errcode("E00026") . "Uncaught " . get_class($e) . " when calling \"task\": " . $error_msg);
Console::trace(); Console::trace();
} }
} }

View File

@@ -12,7 +12,6 @@ use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool; use Swoole\Database\PDOPool;
use Swoole\Process; use Swoole\Process;
use Swoole\Server; use Swoole\Server;
use Swoole\Timer;
use ZM\Annotation\AnnotationParser; use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnStart; use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\OnSwooleEvent;
@@ -26,13 +25,14 @@ use ZM\Event\EventDispatcher;
use ZM\Event\EventManager; use ZM\Event\EventManager;
use ZM\Event\SwooleEvent; use ZM\Event\SwooleEvent;
use ZM\Exception\DbException; use ZM\Exception\DbException;
use ZM\Exception\ZMException;
use ZM\Framework; use ZM\Framework;
use ZM\Module\QQBot; use ZM\Module\QQBot;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool; use ZM\Store\Redis\ZMRedisPool;
use ZM\Utils\DataProvider; use ZM\Utils\DataProvider;
use ZM\Utils\ProcessManager; use ZM\Utils\SignalListener;
/** /**
* Class OnWorkerStart * Class OnWorkerStart
@@ -43,18 +43,10 @@ class OnWorkerStart implements SwooleEvent
{ {
public function onCall(Server $server, $worker_id) { public function onCall(Server $server, $worker_id) {
if (!Framework::$argv["disable-safe-exit"]) { if (!Framework::$argv["disable-safe-exit"]) {
Process::signal(SIGINT, function () use ($worker_id, $server) { SignalListener::signalWorker($server, $worker_id);
});
} }
unset(Context::$context[Coroutine::getCid()]); unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) { if ($server->taskworker === false) {
if (!Framework::$argv["disable-safe-exit"]) {
Process::signal(SIGUSR1, function () use ($worker_id) {
Timer::clearAll();
ProcessManager::resumeAllWorkerCoroutines();
});
}
zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid); zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid);
if (LightCacheInside::get("wait_api", "wait_api") !== null) { if (LightCacheInside::get("wait_api", "wait_api") !== null) {
LightCacheInside::unset("wait_api", "wait_api"); LightCacheInside::unset("wait_api", "wait_api");
@@ -63,7 +55,8 @@ class OnWorkerStart implements SwooleEvent
register_shutdown_function(function () use ($server) { register_shutdown_function(function () use ($server) {
$error = error_get_last(); $error = error_get_last();
if (($error["type"] ?? -1) != 0) { if (($error["type"] ?? -1) != 0) {
Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); Console::error(zm_internal_errcode("E00027") . "Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
zm_dump($error);
} }
//DataProvider::saveBuffer(); //DataProvider::saveBuffer();
/** @var Server $server */ /** @var Server $server */
@@ -71,7 +64,7 @@ class OnWorkerStart implements SwooleEvent
else server()->shutdown(); else server()->shutdown();
}); });
Console::verbose("Worker #{$server->worker_id} 启动中"); Console::verbose("Worker #{$server->worker_id} starting");
Framework::$server = $server; Framework::$server = $server;
//ZMBuf::resetCache(); //清空变量缓存 //ZMBuf::resetCache(); //清空变量缓存
//ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行 //ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
@@ -90,12 +83,12 @@ class OnWorkerStart implements SwooleEvent
phpinfo(); //这个phpinfo是有用的不能删除 phpinfo(); //这个phpinfo是有用的不能删除
$str = ob_get_clean(); $str = ob_get_clean();
$str = explode("\n", $str); $str = explode("\n", $str);
foreach ($str as $k => $v) { foreach ($str as $v) {
$v = trim($v); $v = trim($v);
if ($v == "") continue; if ($v == "") continue;
if (mb_strpos($v, "API Extensions") === false) continue; if (mb_strpos($v, "API Extensions") === false) continue;
if (mb_strpos($v, "pdo_mysql") === false) { if (mb_strpos($v, "pdo_mysql") === false) {
throw new DbException("未安装 mysqlnd php-mysql扩展。"); throw new DbException(zm_internal_errcode("E00028") . "未安装 mysqlnd php-mysql扩展。");
} }
} }
$sql = ZMConfig::get("global", "sql_config"); $sql = ZMConfig::get("global", "sql_config");
@@ -115,7 +108,7 @@ class OnWorkerStart implements SwooleEvent
// 开箱即用的Redis // 开箱即用的Redis
$redis = ZMConfig::get("global", "redis_config"); $redis = ZMConfig::get("global", "redis_config");
if ($redis !== null && $redis["host"] != "") { if ($redis !== null && $redis["host"] != "") {
if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n"); if (!extension_loaded("redis")) Console::error(zm_internal_errcode("E00029") . "Can not find redis extension.\n");
else ZMRedisPool::init($redis); else ZMRedisPool::init($redis);
} }
@@ -136,11 +129,11 @@ class OnWorkerStart implements SwooleEvent
Console::success("Worker #" . $worker_id . " started"); Console::success("Worker #" . $worker_id . " started");
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Worker加载出错停止服务"); Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); Console::error(zm_internal_errcode("E00030") . $e->getMessage() . "\n" . $e->getTraceAsString());
Process::kill($server->master_pid, SIGTERM); Process::kill($server->master_pid, SIGTERM);
return; return;
} catch (Error $e) { } catch (Error $e) {
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); Console::error(zm_internal_errcode("E00030") . "PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory."); Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray'); Console::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM); Process::kill($server->master_pid, SIGTERM);
@@ -153,11 +146,11 @@ class OnWorkerStart implements SwooleEvent
Console::success("TaskWorker #" . $server->worker_id . " started"); Console::success("TaskWorker #" . $server->worker_id . " started");
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Worker加载出错停止服务"); Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); Console::error(zm_internal_errcode("E00030") . $e->getMessage() . "\n" . $e->getTraceAsString());
Process::kill($server->master_pid, SIGTERM); Process::kill($server->master_pid, SIGTERM);
return; return;
} catch (Error $e) { } catch (Error $e) {
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine()); Console::error(zm_internal_errcode("E00030") . "PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory."); Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray'); Console::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM); Process::kill($server->master_pid, SIGTERM);
@@ -189,15 +182,13 @@ class OnWorkerStart implements SwooleEvent
//加载各个模块的注解类,以及反射 //加载各个模块的注解类,以及反射
Console::debug("检索Module中"); Console::debug("检索Module中");
$parser = new AnnotationParser(); $parser = new AnnotationParser();
$path = DataProvider::getWorkingDir() . "/src/"; $composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
$dir = scandir($path); foreach ($composer["autoload"]["psr-4"] as $k => $v) {
unset($dir[0], $dir[1]); if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) {
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue;
foreach ($dir as $v) { if (trim($k, "\\") == "ZM") continue;
if (is_dir($path . "/" . $v) && isset($composer["autoload"]["psr-4"][$v . "\\"]) && !in_array($composer["autoload"]["psr-4"][$v . "\\"], $composer["extra"]["exclude_annotate"] ?? [])) { if (\server()->worker_id == 0) Console::verbose("Add " . $v . ":$k to register path");
if (\server()->worker_id == 0) $parser->addRegisterPath(DataProvider::getSourceRootDir() . "/" . $v . "/", trim($k, "\\"));
Console::verbose("Add " . $v . " to register path");
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v);
} }
} }
$parser->registerMods(); $parser->registerMods();
@@ -207,7 +198,7 @@ class OnWorkerStart implements SwooleEvent
Console::debug("加载自定义上下文中..."); Console::debug("加载自定义上下文中...");
$context_class = ZMConfig::get("global", "context_class"); $context_class = ZMConfig::get("global", "context_class");
if (!is_a($context_class, ContextInterface::class, true)) { if (!is_a($context_class, ContextInterface::class, true)) {
throw new Exception("Context class must implemented from ContextInterface!"); throw new ZMException(zm_internal_errcode("E00032") ."Context class must implemented from ContextInterface!");
} }
//加载插件 //加载插件

View File

@@ -4,7 +4,11 @@
namespace ZM\Exception; namespace ZM\Exception;
use Throwable;
class DbException extends ZMException class DbException extends ZMException
{ {
public function __construct($message = "", $code = 0, Throwable $previous = null) {
parent::__construct(zm_internal_errcode("E00043") . $message, $code, $previous);
}
} }

View File

@@ -0,0 +1,15 @@
<?php
namespace ZM\Exception;
/**
* 这里放所有框架内部的异常处理方法
* Class ExceptionHandler
* @package ZM\Exception
* @since 2.5
*/
class ExceptionHandler
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace ZM\Exception;
use Throwable;
class ModulePackException extends ZMException
{
public function __construct($message = "", $code = 0, Throwable $previous = null) {
parent::__construct(zm_internal_errcode("E00044") . $message, $code, $previous);
}
}

View File

@@ -4,7 +4,11 @@
namespace ZM\Exception; namespace ZM\Exception;
class NotInitializedException extends ZMException use Throwable;
{
class NotInitializedException extends RedisException
{
public function __construct($message = "", $code = 0, Throwable $previous = null) {
parent::__construct(zm_internal_errcode("E00046") . $message, $code, $previous);
}
} }

View File

@@ -0,0 +1,9 @@
<?php
namespace ZM\Exception;
class RedisException extends ZMException
{
}

View File

@@ -14,6 +14,6 @@ use Throwable;
class RobotNotFoundException extends ZMException class RobotNotFoundException extends ZMException
{ {
public function __construct($message = "", $code = 0, Throwable $previous = null) { public function __construct($message = "", $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous); parent::__construct(zm_internal_errcode("E00037") . $message, $code, $previous);
} }
} }

View File

@@ -3,14 +3,11 @@
namespace ZM; namespace ZM;
use Doctrine\Common\Annotations\AnnotationReader; use Doctrine\Common\Annotations\AnnotationReader;
use Error; use Error;
use Exception; use Exception;
use Swoole\Event;
use Swoole\Process;
use Swoole\Server\Port; use Swoole\Server\Port;
use ZM\Annotation\Swoole\OnSetup; use Throwable;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor; use ZM\Console\TermColor;
@@ -21,7 +18,6 @@ use ZM\Store\ZMAtomic;
use ZM\Utils\DataProvider; use ZM\Utils\DataProvider;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;
use ReflectionMethod;
use Swoole\Runtime; use Swoole\Runtime;
use Swoole\WebSocket\Server; use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleHandler; use ZM\Annotation\Swoole\SwooleHandler;
@@ -47,17 +43,19 @@ class Framework
* @var array|bool|mixed|null * @var array|bool|mixed|null
*/ */
private $server_set; private $server_set;
/**
* @var array
*/
private $setup_events = [];
/** @noinspection PhpUnusedParameterInspection */
public function __construct($args = []) { public function __construct($args = []) {
$tty_width = $this->getTtyWidth(); $tty_width = $this->getTtyWidth();
self::$argv = $args; self::$argv = $args;
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
ZMConfig::setEnv($args["env"] ?? ""); ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) { 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");
} }
//定义常量 //定义常量
@@ -80,9 +78,10 @@ class Framework
] ]
]); ]);
} catch (ConnectionManager\TableException $e) { } catch (ConnectionManager\TableException $e) {
die($e->getMessage()); die(zm_internal_errcode("E00008") . $e->getMessage());
} }
try { try {
Console::init( Console::init(
ZMConfig::get("global", "info_level") ?? 2, ZMConfig::get("global", "info_level") ?? 2,
self::$server, self::$server,
@@ -97,13 +96,18 @@ class Framework
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG; $this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false; $add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
$this->loadServerEvents();
$this->parseCliArgs(self::$argv, $add_port); $this->parseCliArgs(self::$argv, $add_port);
if (!isset($this->server_set["max_wait_time"])) { if (!isset($this->server_set["max_wait_time"])) {
$this->server_set["max_wait_time"] = 5; $this->server_set["max_wait_time"] = 5;
} }
$worker = $this->server_set["worker_num"] ?? swoole_cpu_num(); $worker = $this->server_set["worker_num"] ?? swoole_cpu_num();
define("ZM_WORKER_NUM", $worker); define("ZM_WORKER_NUM", $worker);
ZMAtomic::init(); ZMAtomic::init();
// 打印初始信息 // 打印初始信息
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port"); $out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)"; if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
@@ -116,6 +120,9 @@ class Framework
if (isset($this->server_set["task_worker_num"])) { if (isset($this->server_set["task_worker_num"])) {
$out["task_worker"] = $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"] !== "") { if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
$conf = ZMConfig::get("global", "sql_config"); $conf = ZMConfig::get("global", "sql_config");
$out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"]; $out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"];
@@ -141,6 +148,8 @@ class Framework
if ($args["preview"]) { if ($args["preview"]) {
exit(); 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"));
if ($add_port) { if ($add_port) {
@@ -196,11 +205,11 @@ class Framework
Terminal::executeCommand(trim($data)); Terminal::executeCommand(trim($data));
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg); Console::error(zm_internal_errcode("E00009") . "Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg); Console::error(zm_internal_errcode("E00009") . "Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace(); Console::trace();
} }
@@ -224,11 +233,11 @@ class Framework
// 注册 Swoole Server 的事件 // 注册 Swoole Server 的事件
$this->registerServerEvents(); $this->registerServerEvents();
$r = ZMConfig::get("global", "light_cache") ?? [ $r = ZMConfig::get("global", "light_cache") ?? [
"size" => 1024, 'size' => 512, //最多允许储存的条数需要2的倍数
"max_strlen" => 8192, 'max_strlen' => 32768, //单行字符串最大长度需要2的倍数
"hash_conflict_proportion" => 0.6, 'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多
"persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"), 'persistence_path' => DataProvider::getDataFolder() . '_cache.json',
"auto_save_interval" => 900 'auto_save_interval' => 900
]; ];
LightCache::init($r); LightCache::init($r);
LightCacheInside::init(); LightCacheInside::init();
@@ -269,15 +278,15 @@ class Framework
}, E_ALL | E_STRICT); }, E_ALL | E_STRICT);
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Framework初始化出现错误请检查"); Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage()); Console::error(zm_internal_errcode("E00010") . $e->getMessage());
Console::debug($e); Console::debug($e);
die; die;
} }
} }
private static function printMotd($tty_width) { private static function printMotd($tty_width) {
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { if (file_exists(DataProvider::getSourceRootDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); $motd = file_get_contents(DataProvider::getSourceRootDir() . "/config/motd.txt");
} else { } else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt"); $motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
} }
@@ -290,10 +299,27 @@ class Framework
} }
public function start() { public function start() {
self::$loaded_files = get_included_files(); try {
self::$server->start(); self::$loaded_files = get_included_files();
zm_atomic("server_is_stopped")->set(1); self::$server->start();
Console::log("zhamao-framework is stopped."); zm_atomic("server_is_stopped")->set(1);
Console::log("zhamao-framework is stopped.");
} catch (Throwable $e) {
die(zm_internal_errcode("E00011") . "Framework has an uncaught " . get_class($e) . ": " . $e->getMessage() . PHP_EOL);
}
}
private function loadServerEvents() {
$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);
}
$json = json_decode($r, true);
if (!is_array($json)) {
Console::warning(zm_internal_errcode("E00012") . "Parsing @SwooleHandler and @OnSetup error!");
}
$this->setup_events = $json;
} }
/** /**
@@ -303,18 +329,13 @@ class Framework
*/ */
private function registerServerEvents() { private function registerServerEvents() {
$reader = new AnnotationReader(); $reader = new AnnotationReader();
global $master_events; $all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
$master_events = [
"setup" => [],
"event" => []
];
$all = getAllClasses(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
foreach ($all as $v) { foreach ($all as $v) {
$class = new $v(); $class = new $v();
$reflection_class = new ReflectionClass($class); $reflection_class = new ReflectionClass($class);
$anno_class = $reader->getClassAnnotation($reflection_class, SwooleHandler::class); $anno_class = $reader->getClassAnnotation($reflection_class, SwooleHandler::class);
if ($anno_class !== null) { // 类名形式的注解 if ($anno_class !== null) { // 类名形式的注解
$master_events["event"][]=[ $this->setup_events["event"][] = [
"class" => $v, "class" => $v,
"method" => "onCall", "method" => "onCall",
"event" => $anno_class->event "event" => $anno_class->event
@@ -322,66 +343,14 @@ class Framework
} }
} }
$base_path = DataProvider::getWorkingDir(); foreach (($this->setup_events["setup"] ?? []) as $v) {
$scan_paths = [];
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach (($composer["autoload"]["psr-4"] ?? []) as $k => $v) {
if (is_dir($base_path."/".$v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) {
$scan_paths[trim($k, "\\")]=realpath($base_path."/".$v);
}
}
$all_event_class = [];
foreach($scan_paths as $namespace => $autoload_path) {
$all_event_class = array_merge($all_event_class, getAllClasses($autoload_path."/", $namespace));
}
$process = new Process(function(Process $process) use ($all_event_class) {
$reader = new AnnotationReader();
$event_list = [];
$setup_list = [];
foreach ($all_event_class as $v) {
$reflection_class = new ReflectionClass($v);
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $vs) {
$method_annotations = $reader->getMethodAnnotations($vs);
if ($method_annotations != []) {
$annotation = $method_annotations[0];
if ($annotation instanceof SwooleHandler) {
$event_list[]=[
"class" => $v,
"method" => $vs->getName(),
"event" => $annotation->event
];
} elseif ($annotation instanceof OnSetup) {
$setup_list[]=[
"class" => $v,
"method" => $vs->getName()
];
}
}
}
}
$sock = $process->exportSocket();
$sock->send(json_encode(["setup" => $setup_list, "event" => $event_list]));
});
$process->start();
go(function() use ($process) {
$socket = $process->exportSocket();
global $master_events;
$obj = json_decode($socket->recv(), true);
if ($obj["setup"] != []) $master_events["setup"] = array_merge($master_events["setup"], $obj["setup"]);
if ($obj["event"] != []) $master_events["event"] = array_merge($master_events["event"], $obj["event"]);
});
Process::wait(true);
Event::wait();
foreach($master_events["setup"] as $k => $v) {
$c = ZMUtil::getModInstance($v["class"]); $c = ZMUtil::getModInstance($v["class"]);
$method = $v["method"]; $method = $v["method"];
$c->$method(); $c->$method();
} }
foreach ($master_events["event"] as $k => $v) { foreach ($this->setup_events["event"] as $v) {
self::$server->on($v["event"], function (...$param) use ($k, $v) { self::$server->on($v["event"], function (...$param) use ($v) {
ZMUtil::getModInstance($v["class"])->{$v["method"]}(...$param); ZMUtil::getModInstance($v["class"])->{$v["method"]}(...$param);
}); });
} }
@@ -403,7 +372,7 @@ class Framework
if (intval($y) >= 1 && intval($y) <= 1024) { if (intval($y) >= 1 && intval($y) <= 1024) {
$this->server_set["worker_num"] = intval($y); $this->server_set["worker_num"] = intval($y);
} else { } else {
Console::warning("Invalid worker num! Turn to default value (" . ($this->server_set["worker_num"] ?? swoole_cpu_num()) . ")"); Console::warning(zm_internal_errcode("E00013") . "Invalid worker num! Turn to default value (" . ($this->server_set["worker_num"] ?? swoole_cpu_num()) . ")");
} }
break; break;
case 'task-worker-num': case 'task-worker-num':
@@ -411,7 +380,7 @@ class Framework
$this->server_set["task_worker_num"] = intval($y); $this->server_set["task_worker_num"] = intval($y);
$this->server_set["task_enable_coroutine"] = true; $this->server_set["task_enable_coroutine"] = true;
} else { } else {
Console::warning("Invalid worker num! Turn to default value (0)"); Console::warning(zm_internal_errcode("E00013") . "Invalid worker num! Turn to default value (0)");
} }
break; break;
case 'disable-coroutine': case 'disable-coroutine':
@@ -421,7 +390,7 @@ class Framework
$coroutine_mode = false; $coroutine_mode = false;
$terminal_id = null; $terminal_id = null;
self::$argv["watch"] = true; self::$argv["watch"] = true;
echo ("* You are in debug mode, do not use in production!\n"); echo("* You are in debug mode, do not use in production!\n");
break; break;
case 'daemon': case 'daemon':
$this->server_set["daemonize"] = 1; $this->server_set["daemonize"] = 1;
@@ -463,7 +432,8 @@ class Framework
} }
} }
} }
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); $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); else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
} }
@@ -554,7 +524,9 @@ class Framework
} }
public static function getTtyWidth(): string { public static function getTtyWidth(): string {
return explode(" ", trim(exec("stty size")))[1]; $size = exec("stty size");
if (empty($size)) return 65;
return explode(" ", trim($size))[1];
} }
public static function loadFrameworkState() { public static function loadFrameworkState() {

View File

@@ -3,58 +3,12 @@
namespace ZM\Http; namespace ZM\Http;
/**
use Symfony\Component\Routing\Route; * 为了兼容豁出去了2.5),此兼容类
use Symfony\Component\Routing\RouteCollection; * Class RouteManager
use ZM\Annotation\Http\Controller; * @package ZM\Http
use ZM\Annotation\Http\RequestMapping; * @deprecated
use ZM\Console\Console; */
use ZM\Store\LightCacheInside; class RouteManager extends \ZM\Utils\Manager\RouteManager
class RouteManager
{ {
/** @var null|RouteCollection */
public static $routes = null;
public static function importRouteByAnnotation(RequestMapping $vss, $method, $class, $methods_annotations) {
if (self::$routes === null) self::$routes = new RouteCollection();
// 拿到所属方法的类上面有没有控制器的注解
$prefix = '';
foreach ($methods_annotations as $annotation) {
if ($annotation instanceof Controller) {
$prefix = $annotation->prefix;
break;
}
}
$tail = trim($vss->route, "/");
$route_name = $prefix . ($tail === "" ? "" : "/") . $tail;
Console::debug("添加路由:" . $route_name);
$route = new Route($route_name, ['_class' => $class, '_method' => $method]);
$route->setMethods($vss->request_method);
self::$routes->add(md5($route_name), $route);
}
public static function addStaticFileRoute($route, $path) {
$tail = trim($route, "/");
$route_name = ($tail === "" ? "" : "/") . $tail . "/{filename}";
Console::debug("添加静态文件路由:" . $route_name);
$route = new Route($route_name, ['_class' => RouteManager::class, '_method' => "onStaticRoute"]);
//echo $path.PHP_EOL;
LightCacheInside::set("static_route", $route->getPath(), $path);
self::$routes->add(md5($route_name), $route);
}
public function onStaticRoute($params) {
$route_path = self::$routes->get($params["_route"])->getPath();
if(($path = LightCacheInside::get("static_route", $route_path)) === null) {
ctx()->getResponse()->endWithStatus(404);
return false;
}
unset($params["_route"]);
$obj = array_shift($params);
return new StaticFileHandler($obj, $path);
}
} }

View File

@@ -0,0 +1,206 @@
<?php
namespace ZM\Module;
use Exception;
use Phar;
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\ZMUtil;
/**
* 模块构造器
* Class ModulePacker
* @package ZM\Module
* @since 2.5
*/
class ModulePacker
{
const ZM_MODULE_PACKER_VERSION = '1.0';
/** @var array $module */
private $module = [];
/** @var bool $override */
private $override = false;
/** @var string $output_path */
private $output_path = '';
/** @var string $filename */
private $filename = '';
/** @var Phar $phar */
private $phar;
/** @var null|array $module_config */
private $module_config = null;
/**
* @throws ModulePackException
*/
public function __construct(array $module) {
if (ini_get('phar.readonly') == '1') {
throw new ModulePackException('请先在 php.ini 中设置 `phar.readonly = Off` 后再打包模块!');
}
if (!isset($module['name'], $module['module-path'], $module['namespace'])) {
throw new ModulePackException('模块打包需要至少传入name、module-path、namespace三个参数');
}
$this->module = $module;
}
/**
* 设置输出文件夹
* @param $path
*/
public function setOutputPath($path) {
$this->output_path = $path;
}
/**
* 设置是否覆盖
* @param bool $override
*/
public function setOverride(bool $override = true) {
$this->override = $override;
}
/**
* 获取模块名字
* @return mixed
*/
public function getName() {
return $this->module['name'];
}
/**
* 获取打包的文件名绝对路径
* @return string
*/
public function getFileName(): string {
return $this->filename;
}
/**
* 打包模块
* @throws ZMException
*/
public function pack() {
$this->filename = $this->output_path . '/' . $this->module['name'];
if (isset($this->module['version'])) {
$this->filename .= '_' . $this->module['version'];
}
$this->filename .= '.phar';
if ($this->override) {
if (file_exists($this->filename)) {
Console::info('Overwriting ' . $this->filename);
unlink($this->filename);
}
}
$this->phar = new Phar($this->filename);
$this->phar->startBuffering();
Console::info('模块输出文件:' . $this->filename);
$this->addFiles(); //添加文件
$this->addLightCacheStore(); //保存light-cache-store指定的项
$this->addModuleConfig(); //生成module-config.json
$this->addEntry(); //生成模块的入口文件module_entry.php
$this->phar->stopBuffering();
}
private function addFiles() {
$file_list = DataProvider::scanDirFiles($this->module['module-path'], true, false);
foreach ($file_list as $v) {
$this->phar->addFile($v, $this->getRelativePath($v));
}
}
private function getRelativePath($path) {
return str_replace(realpath(DataProvider::getSourceRootDir()) . '/', '', realpath($path));
}
private function generatePharAutoload(): array {
return ZMUtil::getClassesPsr4($this->module['module-path'], $this->module['namespace'], null, true);
}
private function getComposerAutoloadItems(): array {
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true);
$path = self::getRelativePath($this->module['module-path']);
$item = [];
foreach (($composer['autoload']['psr-4'] ?? []) as $k => $v) {
if (strpos($path, $v) === 0) {
$item['psr-4'][$k] = $v;
}
}
foreach (($composer['autoload']['files'] ?? []) as $v) {
if (strcmp($path, $v) === 0) {
$item['files'][] = $v;
}
}
return $item;
}
/**
* @throws ZMException
* @throws Exception
*/
private function addLightCacheStore() {
if (isset($this->module['light-cache-store'])) {
$store = [];
$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->module['light-cache-store'] as $v) {
$r = LightCache::get($v);
if ($r === null) {
Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:`$v` 不存在或值为null无法为其保存。');
} else {
$store[$v] = $r;
Console::info('打包LightCache持久化项' . $v);
}
}
$this->phar->addFromString('light_cache_store.json', json_encode($store, 128 | 256));
}
}
private function addModuleConfig() {
$stub_values = [
'zm-module' => true,
'generated-id' => sha1(strval(microtime(true))),
'module-packer-version' => self::ZM_MODULE_PACKER_VERSION,
'module-root-path' => $this->getRelativePath($this->module['module-path']),
'namespace' => $this->module['namespace'],
'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
],
'allow-hotload' => empty($this->module['global-config-override'] ?? []) && !isset($this->module['depends'])
];
$this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->module_config = $stub_values;
}
private function addEntry() {
$stub_replace = [
'generated_id' => $this->module_config['generated-id']
];
$stub_template = str_replace(
array_map(function ($x) {
return '__' . $x . '__';
}, array_keys($stub_replace)),
array_values($stub_replace),
file_get_contents(DataProvider::getFrameworkRootDir() . '/src/ZM/script_phar_stub.php')
);
$this->phar->addFromString('module_entry.php', $stub_template);
$this->phar->setStub($this->phar->createDefaultStub('module_entry.php'));
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace ZM\Module;
class ModuleUnpacker
{
private $module;
public function __construct(array $module) {
$this->module = $module;
}
/**
* 解包模块
* @return array
*/
public function unpack(): array {
// TODO: 解包模块到src
return $this->module;
}
}

View File

@@ -11,7 +11,7 @@ use ZM\Console\Console;
use ZM\Event\EventDispatcher; use ZM\Event\EventDispatcher;
use ZM\Exception\ZMException; use ZM\Exception\ZMException;
use ZM\Framework; use ZM\Framework;
use ZM\Utils\ProcessManager; use ZM\Utils\Manager\ProcessManager;
class LightCache class LightCache
{ {
@@ -26,7 +26,6 @@ class LightCache
* @param $config * @param $config
* @return bool|mixed * @return bool|mixed
* @throws Exception * @throws Exception
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function init($config) { public static function init($config) {
self::$config = $config; self::$config = $config;
@@ -44,17 +43,17 @@ class LightCache
$write = self::set($k, $v); $write = self::set($k, $v);
Console::verbose("Writing LightCache: " . $k); Console::verbose("Writing LightCache: " . $k);
if ($write === false) { if ($write === false) {
self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; self::$last_error = zm_internal_errcode("E00051") . '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
return false; return false;
} }
} }
} }
} }
if ($result === false) { if ($result === false) {
self::$last_error = '系统内存不足,申请失败'; self::$last_error = zm_internal_errcode("E00050") . '系统内存不足,申请失败';
} else { } else {
$obj = Framework::loadFrameworkState(); $obj = Framework::loadFrameworkState();
foreach(($obj["expiring_light_cache"] ?? []) as $k => $v) { foreach (($obj["expiring_light_cache"] ?? []) as $k => $v) {
$value = $v["value"]; $value = $v["value"];
if (is_array($value)) { if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE); $value = json_encode($value, JSON_UNESCAPED_UNICODE);
@@ -87,7 +86,7 @@ class LightCache
* @throws ZMException * @throws ZMException
*/ */
public static function get(string $key) { public static function get(string $key) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
self::checkExpire($key); self::checkExpire($key);
$r = self::$kv_table->get($key); $r = self::$kv_table->get($key);
return $r === false ? null : self::parseGet($r); return $r === false ? null : self::parseGet($r);
@@ -99,7 +98,7 @@ class LightCache
* @throws ZMException * @throws ZMException
*/ */
public static function getExpire(string $key) { public static function getExpire(string $key) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
self::checkExpire($key); self::checkExpire($key);
$r = self::$kv_table->get($key, "expire"); $r = self::$kv_table->get($key, "expire");
return $r === false ? null : $r - time(); return $r === false ? null : $r - time();
@@ -112,7 +111,7 @@ class LightCache
* @since 2.4.3 * @since 2.4.3
*/ */
public static function getExpireTS(string $key) { public static function getExpireTS(string $key) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
self::checkExpire($key); self::checkExpire($key);
$r = self::$kv_table->get($key, "expire"); $r = self::$kv_table->get($key, "expire");
return $r === false ? null : $r; return $r === false ? null : $r;
@@ -124,10 +123,9 @@ class LightCache
* @param int $expire * @param int $expire
* @return mixed * @return mixed
* @throws ZMException * @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function set(string $key, $value, int $expire = -1) { public static function set(string $key, $value, int $expire = -1) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache");
if (is_array($value)) { if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE); $value = json_encode($value, JSON_UNESCAPED_UNICODE);
if (strlen($value) >= self::$config["max_strlen"]) return false; if (strlen($value) >= self::$config["max_strlen"]) return false;
@@ -140,7 +138,7 @@ class LightCache
$data_type = "bool"; $data_type = "bool";
$value = json_encode($value); $value = json_encode($value);
} else { } else {
throw new ZMException("Only can set string, array and int"); throw new ZMException(zm_internal_errcode("E00049") . "Only can set string, array and int");
} }
try { try {
return self::$kv_table->set($key, [ return self::$kv_table->set($key, [
@@ -158,10 +156,9 @@ class LightCache
* @param $value * @param $value
* @return bool|mixed * @return bool|mixed
* @throws ZMException * @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function update(string $key, $value) { public static function update(string $key, $value) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache."); if (self::$kv_table === null) throw new ZMException(zm_internal_errcode("E00048") . "not initialized LightCache.");
if (is_array($value)) { if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE); $value = json_encode($value, JSON_UNESCAPED_UNICODE);
if (strlen($value) >= self::$config["max_strlen"]) return false; if (strlen($value) >= self::$config["max_strlen"]) return false;
@@ -171,7 +168,7 @@ class LightCache
} elseif (is_int($value)) { } elseif (is_int($value)) {
$data_type = "int"; $data_type = "int";
} else { } else {
throw new ZMException("Only can set string, array and int"); throw new ZMException(zm_internal_errcode("E00048") . "Only can set string, array and int");
} }
try { try {
if (self::$kv_table->get($key) === false) return false; if (self::$kv_table->get($key) === false) return false;

View File

@@ -67,6 +67,6 @@ class LightCacheInside
self::$kv_table[$name] = new Table($size, $conflict_proportion); self::$kv_table[$name] = new Table($size, $conflict_proportion);
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size); self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
$r = self::$kv_table[$name]->create(); $r = self::$kv_table[$name]->create();
if ($r === false) throw new ZMException("内存不足,创建静态表失败!"); if ($r === false) throw new ZMException(zm_internal_errcode("E00050") . "内存不足,创建静态表失败!");
} }
} }

View File

@@ -30,7 +30,7 @@ class ZMRedisPool
var_dump($r); var_dump($r);
} }
} catch (RedisException $e) { } catch (RedisException $e) {
Console::error("Redis init failed! " . $e->getMessage()); Console::error(zm_internal_errcode("E00047") . "Redis init failed! " . $e->getMessage());
self::$pool = null; self::$pool = null;
} }
} }

View File

@@ -38,6 +38,7 @@ class ZMAtomic
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
self::$atomics["_tmp_" . $i] = new Atomic(0); self::$atomics["_tmp_" . $i] = new Atomic(0);
} }
self::$atomics["ss"] = new Atomic(1);
} }

View File

@@ -4,10 +4,11 @@
namespace ZM\Utils; namespace ZM\Utils;
use Co; use Swoole\Coroutine;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock; use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic; use ZM\Store\ZMAtomic;
use ZM\Utils\Manager\ProcessManager;
class CoMessage class CoMessage
{ {
@@ -16,10 +17,9 @@ class CoMessage
* @param array $compare * @param array $compare
* @param int $timeout * @param int $timeout
* @return mixed * @return mixed
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function yieldByWS(array $hang, array $compare, $timeout = 600) { public static function yieldByWS(array $hang, array $compare, $timeout = 600) {
$cid = Co::getuid(); $cid = Coroutine::getuid();
$api_id = ZMAtomic::get("wait_msg_id")->add(1); $api_id = ZMAtomic::get("wait_msg_id")->add(1);
$hang["compare"] = $compare; $hang["compare"] = $compare;
$hang["coroutine"] = $cid; $hang["coroutine"] = $cid;
@@ -33,10 +33,10 @@ class CoMessage
$id = swoole_timer_after($timeout * 1000, function () use ($api_id) { $id = swoole_timer_after($timeout * 1000, function () use ($api_id) {
$r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null; $r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null;
if (is_array($r)) { if (is_array($r)) {
Co::resume($r["coroutine"]); Coroutine::resume($r["coroutine"]);
} }
}); });
Co::suspend(); Coroutine::suspend();
SpinLock::lock("wait_api"); SpinLock::lock("wait_api");
$sess = LightCacheInside::get("wait_api", "wait_api"); $sess = LightCacheInside::get("wait_api", "wait_api");
$result = $sess[$api_id]["result"] ?? null; $result = $sess[$api_id]["result"] ?? null;
@@ -70,7 +70,7 @@ class CoMessage
if ($all[$last]["worker_id"] != server()->worker_id) { if ($all[$last]["worker_id"] != server()->worker_id) {
ProcessManager::sendActionToWorker($all[$last]["worker_id"], "resume_ws_message", $all[$last]); ProcessManager::sendActionToWorker($all[$last]["worker_id"], "resume_ws_message", $all[$last]);
} else { } else {
Co::resume($all[$last]["coroutine"]); Coroutine::resume($all[$last]["coroutine"]);
} }
return true; return true;
} else { } else {

View File

@@ -38,6 +38,10 @@ class CoroutinePool
self::$sizes[$name] = $size; self::$sizes[$name] = $size;
} }
public static function getRunningCoroutineCount($name = "default") {
return count(self::$cids[$name]);
}
private static function checkCids($name) { private static function checkCids($name) {
if (in_array(Coroutine::getCid(), self::$cids[$name])) { if (in_array(Coroutine::getCid(), self::$cids[$name])) {
$a = array_search(Coroutine::getCid(), self::$cids[$name]); $a = array_search(Coroutine::getCid(), self::$cids[$name]);

View File

@@ -11,44 +11,139 @@ class DataProvider
{ {
public static $buffer_list = []; public static $buffer_list = [];
/**
* 返回资源目录
* @return string
*/
public static function getResourceFolder(): string { public static function getResourceFolder(): string {
return self::getWorkingDir() . '/resources/'; return self::getWorkingDir() . '/resources/';
} }
/**
* 返回工作目录,不带最右边文件夹的斜杠(/
* @return false|string
*/
public static function getWorkingDir() { public static function getWorkingDir() {
return WORKING_DIR; return WORKING_DIR;
} }
/**
* 获取框架所在根目录
* @return false|string
*/
public static function getFrameworkRootDir() {
return FRAMEWORK_ROOT_DIR;
}
/**
* 获取源码根目录除Phar模式外均与工作目录相同
* @return false|string
*/
public static function getSourceRootDir() {
return defined("SOURCE_ROOT_DIR") ? SOURCE_ROOT_DIR : WORKING_DIR;
}
/**
* 获取框架反代链接
* @return array|false|mixed|null
*/
public static function getFrameworkLink() { public static function getFrameworkLink() {
return ZMConfig::get("global", "http_reverse_link"); return ZMConfig::get("global", "http_reverse_link");
} }
public static function getDataFolder() { /**
* 获取zm_data数据目录如果二级目录不为空则自动创建目录并返回
* @param string $second
* @return array|false|mixed|string|null
*/
public static function getDataFolder(string $second = '') {
if ($second !== '') {
@mkdir(ZM_DATA . $second);
if (!is_dir(ZM_DATA . $second)) return false;
return realpath(ZM_DATA . $second) . "/";
}
return ZM_DATA; return ZM_DATA;
} }
/**
* 将变量保存在zm_data下的数据目录传入数组
* @param $filename
* @param $file_array
* @return false|int
*/
public static function saveToJson($filename, $file_array) { public static function saveToJson($filename, $file_array) {
$path = ZMConfig::get("global", "config_dir"); $path = ZMConfig::get("global", "config_dir");
$r = explode("/", $filename); $r = explode("/", $filename);
if(count($r) == 2) { if (count($r) == 2) {
$path = $path . $r[0]."/"; $path = $path . $r[0] . "/";
if(!is_dir($path)) mkdir($path); if (!is_dir($path)) mkdir($path);
$name = $r[1]; $name = $r[1];
} elseif (count($r) != 1) { } elseif (count($r) != 1) {
Console::warning("存储失败,文件名只能有一级目录"); Console::warning(zm_internal_errcode("E00057") . "存储失败,文件名只能有一级目录");
return false; return false;
} else { } else {
$name = $r[0]; $name = $r[0];
} }
return file_put_contents($path.$name.".json", json_encode($file_array, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); return file_put_contents($path . $name . ".json", json_encode($file_array, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
} }
/**
* 从json加载变量到内存
* @param $filename
* @return mixed|null
*/
public static function loadFromJson($filename) { public static function loadFromJson($filename) {
$path = ZMConfig::get("global", "config_dir"); $path = ZMConfig::get("global", "config_dir");
if(file_exists($path.$filename.".json")) { if (file_exists($path . $filename . ".json")) {
return json_decode(file_get_contents($path.$filename.".json"), true); return json_decode(file_get_contents($path . $filename . ".json"), true);
} else { } else {
return null; return null;
} }
} }
/**
* 递归或非递归扫描目录,可返回相对目录的文件列表或绝对目录的文件列表
* @param $dir
* @param bool $recursive
* @param bool|string $relative
* @return array|false
* @since 2.5
*/
public static function scanDirFiles($dir, bool $recursive = true, $relative = false) {
$dir = rtrim($dir, "/");
if (!is_dir($dir)) return false;
$r = scandir($dir);
if ($r === false) return false;
$list = [];
if ($relative === true) {
$relative = $dir;
}
foreach ($r as $v) {
if ($v == "." || $v == "..") continue;
$sub_file = $dir . "/" . $v;
if (is_dir($sub_file) && $recursive) {
$list = array_merge($list, self::scanDirFiles($sub_file, $recursive, $relative));
} elseif (is_file($sub_file)) {
if (is_string($relative) && mb_strpos($sub_file, $relative) === 0) {
$list [] = ltrim(mb_substr($sub_file, mb_strlen($relative)), "/");
} elseif ($relative === false) {
$list[] = $sub_file;
} else {
Console::warning(zm_internal_errcode("E00058") . "Relative path is not generated: wrong base directory ($relative)");
return false;
}
}
}
return $list;
}
/**
* 检查路径是否为相对路径(根据第一个字符是否为"/"来判断)
* @param $path
* @return bool
* @since 2.5
*/
public static function isRelativePath($path) {
return strlen($path) > 0 && $path[0] === '/';
}
} }

View File

@@ -4,7 +4,7 @@
namespace ZM\Utils; namespace ZM\Utils;
use Co; use Swoole\Coroutine;
use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcher; use Symfony\Component\Routing\Matcher\UrlMatcher;
@@ -13,7 +13,7 @@ use Symfony\Component\Routing\RouteCollection;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Http\Response; use ZM\Http\Response;
use ZM\Http\RouteManager; use ZM\Utils\Manager\RouteManager;
class HttpUtil class HttpUtil
{ {
@@ -51,7 +51,7 @@ class HttpUtil
public static function getHttpCodePage(int $http_code) { public static function getHttpCodePage(int $http_code) {
if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) { if (isset(ZMConfig::get("global", "http_default_code_page")[$http_code])) {
return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]); return Coroutine::readFile(DataProvider::getResourceFolder() . "html/" . ZMConfig::get("global", "http_default_code_page")[$http_code]);
} else return null; } else return null;
} }

View File

@@ -0,0 +1,115 @@
<?php
namespace ZM\Utils\Manager;
use ZM\Console\Console;
use ZM\Exception\ModulePackException;
use ZM\Exception\ZMException;
use ZM\Module\ModulePacker;
use ZM\Module\ModuleUnpacker;
use ZM\Utils\DataProvider;
/**
* 模块管理器,负责打包解包模块
* Class ModuleManager
* @package ZM\Utils\Manager
* @since 2.5
*/
class ModuleManager
{
/**
* 扫描src目录下的所有已经被标注的模块
* @return array
* @throws ZMException
*/
public static function getConfiguredModules(): array {
$dir = DataProvider::getSourceRootDir() . "/src/";
$ls = DataProvider::scanDirFiles($dir, true, true);
$modules = [];
foreach ($ls as $v) {
$pathinfo = pathinfo($v);
if ($pathinfo["basename"] == "zm.json") {
$json = json_decode(file_get_contents(realpath($dir . "/" . $v)), true);
if ($json === null) continue;
if (!isset($json["name"])) continue;
if ($pathinfo["dirname"] == ".") {
throw new ZMException(zm_internal_errcode("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"]);
}
$modules[$json["name"]] = $json;
}
}
return $modules;
}
public static function getPackedModules(): array {
$dir = DataProvider::getDataFolder() . "modules";
$ls = DataProvider::scanDirFiles($dir, true, false);
if ($ls === false) return [];
$modules = [];
foreach ($ls as $v) {
$pathinfo = pathinfo($v);
if (($pathinfo["extension"] ?? "") != "phar") continue;
$file = "phar://" . $v;
if (!is_file($file . "/module_entry.php") || !is_file($file . "/zmplugin.json")) continue;
$module_config = json_decode(file_get_contents($file . "/zmplugin.json"), true);
if ($module_config === null) continue;
if (!is_file($file . "/" . $module_config["module-root-path"] . "/zm.json")) {
Console::warning(zm_internal_errcode("E00054") . "模块(插件)文件 " . $pathinfo["basename"] . " 无法找到模块配置文件zm.json");
continue;
}
$module_file = json_decode(file_get_contents($file . "/" . $module_config["module-root-path"] . "/zm.json"), true);
if ($module_file === null) {
Console::warning(zm_internal_errcode("E000555") . "模块(插件)文件 " . $pathinfo["basename"] . " 无法正常读取模块配置文件zm.json");
continue;
}
$module_config["phar-path"] = $v;
$module_config["name"] = $module_file["name"] ?? null;
if ($module_config["name"] === null) continue;
$module_config["module-config"] = $module_file;
$modules[$module_config["name"]] = $module_config;
}
return $modules;
}
/**
* 打包模块
* @param $module
* @return bool
* @throws ZMException
*/
public static function packModule($module): bool {
try {
$packer = new ModulePacker($module);
$packer->setOutputPath(DataProvider::getDataFolder() . "output");
$packer->setOverride();
$packer->pack();
return true;
} catch (ModulePackException $e) {
Console::error($e->getMessage());
return false;
}
}
/**
* 解包模块 TODO
* @param $module
* @return array|false
*/
public static function unpackModule($module) {
try {
$packer = new ModuleUnpacker($module);
return $packer->unpack();
} catch (ModulePackException $e) {
Console::error($e->getMessage());
return false;
}
}
}

View File

@@ -1,10 +1,10 @@
<?php /** @noinspection PhpUnused */ <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils\Manager;
use Co; use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent; use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console; use ZM\Console\Console;
@@ -38,7 +38,7 @@ class ProcessManager
break; break;
case "resume_ws_message": case "resume_ws_message":
$obj = $data["data"]; $obj = $data["data"];
Co::resume($obj["coroutine"]); Coroutine::resume($obj["coroutine"]);
break; break;
case "getWorkerCache": case "getWorkerCache":
$r = WorkerCache::get($data["key"]); $r = WorkerCache::get($data["key"]);
@@ -99,7 +99,7 @@ class ProcessManager
public static function sendActionToWorker($worker_id, $action, $data) { public static function sendActionToWorker($worker_id, $action, $data) {
$obj = ["action" => $action, "data" => $data]; $obj = ["action" => $action, "data" => $data];
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) { if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
Console::warning("Cannot send worker action from master or manager process!"); Console::warning(zm_internal_errcode("E00022") . "Cannot send worker action from master or manager process!");
return; return;
} }
if (server()->worker_id == $worker_id) { if (server()->worker_id == $worker_id) {
@@ -114,9 +114,9 @@ class ProcessManager
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!"); Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
return; return;
} }
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) { foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $v) {
if (isset($v["coroutine"], $v["worker_id"])) { if (isset($v["coroutine"], $v["worker_id"])) {
if (server()->worker_id == $v["worker_id"]) Co::resume($v["coroutine"]); if (server()->worker_id == $v["worker_id"]) Coroutine::resume($v["coroutine"]);
} }
} }
} }

View File

@@ -0,0 +1,67 @@
<?php
namespace ZM\Utils\Manager;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
use ZM\Console\Console;
use ZM\Http\StaticFileHandler;
use ZM\Store\LightCacheInside;
/**
* 路由管理器2.5版本更改了命名空间
* Class RouteManager
* @package ZM\Utils\Manager
* @since 2.3.0
*/
class RouteManager
{
/** @var null|RouteCollection */
public static $routes = null;
public static function importRouteByAnnotation(RequestMapping $vss, $method, $class, $methods_annotations) {
if (self::$routes === null) self::$routes = new RouteCollection();
// 拿到所属方法的类上面有没有控制器的注解
$prefix = '';
foreach ($methods_annotations as $annotation) {
if ($annotation instanceof Controller) {
$prefix = $annotation->prefix;
break;
}
}
$tail = trim($vss->route, "/");
$route_name = $prefix . ($tail === "" ? "" : "/") . $tail;
Console::debug("添加路由:" . $route_name);
$route = new Route($route_name, ['_class' => $class, '_method' => $method]);
$route->setMethods($vss->request_method);
self::$routes->add(md5($route_name), $route);
}
public static function addStaticFileRoute($route, $path) {
$tail = trim($route, "/");
$route_name = ($tail === "" ? "" : "/") . $tail . "/{filename}";
Console::debug("添加静态文件路由:" . $route_name);
$route = new Route($route_name, ['_class' => RouteManager::class, '_method' => "onStaticRoute"]);
//echo $path.PHP_EOL;
LightCacheInside::set("static_route", $route->getPath(), $path);
self::$routes->add(md5($route_name), $route);
}
public function onStaticRoute($params) {
$route_path = self::$routes->get($params["_route"])->getPath();
if(($path = LightCacheInside::get("static_route", $route_path)) === null) {
ctx()->getResponse()->endWithStatus(404);
return false;
}
unset($params["_route"]);
$obj = array_shift($params);
return new StaticFileHandler($obj, $path);
}
}

View File

@@ -1,7 +1,7 @@
<?php /** @noinspection PhpUnused */ <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils\Manager;
use ZM\Console\Console; use ZM\Console\Console;
@@ -9,15 +9,14 @@ use ZM\Console\Console;
class TaskManager class TaskManager
{ {
/** /**
* @noinspection PhpMissingReturnTypeInspection
* @param $task_name * @param $task_name
* @param int $timeout * @param int $timeout
* @param mixed ...$params * @param mixed ...$params
* @return false|mixed * @return false|mixed
*/ */
public static function runTask($task_name, $timeout = -1, ...$params) { public static function runTask($task_name, int $timeout = -1, ...$params) {
if (!isset(server()->setting["task_worker_num"])) { if (!isset(server()->setting["task_worker_num"])) {
Console::warning("未开启 TaskWorker 进程,请先修改 global 配置文件启用!"); Console::warning(zm_internal_errcode("E00056") . "未开启 TaskWorker 进程,请先修改 global 配置文件启用!");
return false; return false;
} }
$r = server()->taskwait(["task" => $task_name, "params" => $params], $timeout); $r = server()->taskwait(["task" => $task_name, "params" => $params], $timeout);

View File

@@ -11,6 +11,7 @@ use ZM\Console\Console;
use ZM\Entity\MatchResult; use ZM\Entity\MatchResult;
use ZM\Event\EventManager; use ZM\Event\EventManager;
use ZM\Requests\ZMRequest; use ZM\Requests\ZMRequest;
use ZM\Utils\Manager\ProcessManager;
class MessageUtil class MessageUtil
{ {
@@ -25,7 +26,7 @@ class MessageUtil
if (!is_dir($path)) mkdir($path); if (!is_dir($path)) mkdir($path);
$path = realpath($path); $path = realpath($path);
if ($path === false) { if ($path === false) {
Console::warning("指定的路径错误不存在!"); Console::warning(zm_internal_errcode("E00059") . "指定的路径错误不存在!");
return false; return false;
} }
$files = []; $files = [];
@@ -34,7 +35,7 @@ class MessageUtil
if ($v->type == "image") { if ($v->type == "image") {
$result = ZMRequest::downloadFile($v->params["url"], $path . "/" . $v->params["file"]); $result = ZMRequest::downloadFile($v->params["url"], $path . "/" . $v->params["file"]);
if ($result === false) { if ($result === false) {
Console::warning("图片 " . $v->params["url"] . " 下载失败!"); Console::warning(zm_internal_errcode("E00060") . "图片 " . $v->params["url"] . " 下载失败!");
return false; return false;
} }
$files[] = $path . "/" . $v->params["file"]; $files[] = $path . "/" . $v->params["file"];
@@ -112,7 +113,7 @@ class MessageUtil
$ls = EventManager::$events[CQCommand::class] ?? []; $ls = EventManager::$events[CQCommand::class] ?? [];
$word = self::splitCommand($msg); $word = self::splitCommand($msg);
$matched = new MatchResult(); $matched = new MatchResult();
foreach ($ls as $k => $v) { foreach ($ls as $v) {
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue; if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue;
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) && elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) && ($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) &&

View File

@@ -0,0 +1,75 @@
<?php
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管理类
* Class SignalListener
* @package ZM\Utils
* @since 2.5
*/
class SignalListener
{
/**
* 监听Master进程的Ctrl+C
* @param Server $server
*/
public static function signalMaster(Server $server) {
Process::signal(SIGINT, function () use ($server) {
if (zm_atomic("_int_is_reload")->get() === 1) {
zm_atomic("_int_is_reload")->set(0);
$server->reload();
} 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);
}
});
}
/**
* 监听Manager进程的Ctrl+C
*/
public static function signalManager() {
$func = function () {
Console::verbose("Interrupted in manager!");
};
if (version_compare(SWOOLE_VERSION, "4.6.7") >= 0) {
Process::signal(SIGINT, $func);
} elseif (extension_loaded("pcntl")) {
pcntl_signal(SIGINT, $func);
}
}
/**
* 监听Worker/TaskWorker进程的Ctrl+C
* @param Server $server
* @param $worker_id
*/
public static function signalWorker(Server $server, $worker_id) {
Process::signal(SIGINT, function () use ($worker_id, $server) {
// do nothing
});
if ($server->taskworker === false) {
Process::signal(SIGUSR1, function () use ($worker_id) {
Timer::clearAll();
ProcessManager::resumeAllWorkerCoroutines();
});
}
}
}

View File

@@ -33,13 +33,13 @@ class Terminal
$dispatcher = new EventDispatcher(TerminalCommand::class); $dispatcher = new EventDispatcher(TerminalCommand::class);
$dispatcher->setRuleFunction(function ($v) use ($it) { $dispatcher->setRuleFunction(function ($v) use ($it) {
/** @var TerminalCommand $v */ /** @var TerminalCommand $v */
return $v->command == $it[0] || $v->alias == $it[0]; return !empty($it) && ($v->command == $it[0] || $v->alias == $it[0]);
}); });
$dispatcher->setReturnFunction(function () { $dispatcher->setReturnFunction(function () {
EventDispatcher::interrupt('none'); EventDispatcher::interrupt('none');
}); });
$dispatcher->dispatchEvents($it); $dispatcher->dispatchEvents($it);
if ($dispatcher->store !== 'none') { if ($dispatcher->store !== 'none' && $cmd !== "") {
Console::info("Command not found: " . $cmd); Console::info("Command not found: " . $cmd);
return true; return true;
} }
@@ -55,7 +55,7 @@ class Terminal
Console::$type($log_msg); Console::$type($log_msg);
$r = ob_get_clean(); $r = ob_get_clean();
$all = ManagerGM::getAllByName("terminal"); $all = ManagerGM::getAllByName("terminal");
foreach ($all as $k => $v) { foreach ($all as $v) {
server()->send($v->getFd(), "\r" . $r); server()->send($v->getFd(), "\r" . $r);
server()->send($v->getFd(), ">>> "); server()->send($v->getFd(), ">>> ");
} }
@@ -72,7 +72,7 @@ class Terminal
$class = new Terminal(); $class = new Terminal();
$reader = new AnnotationReader(); $reader = new AnnotationReader();
$reflection = new ReflectionClass($class); $reflection = new ReflectionClass($class);
foreach ($reflection->getMethods() as $k => $v) { foreach ($reflection->getMethods() as $v) {
$r = $reader->getMethodAnnotation($v, TerminalCommand::class); $r = $reader->getMethodAnnotation($v, TerminalCommand::class);
if ($r !== null) { if ($r !== null) {
Console::debug("adding command " . $r->command); Console::debug("adding command " . $r->command);
@@ -176,7 +176,7 @@ class Terminal
* @TerminalCommand(command="stop",description="停止框架") * @TerminalCommand(command="stop",description="停止框架")
*/ */
public function stop() { public function stop() {
Process::kill(server()->master_pid, SIGTERM); posix_kill(server()->master_pid, SIGTERM);
} }
/** /**

View File

@@ -18,13 +18,13 @@ class ZMUtil
* @throws Exception * @throws Exception
*/ */
public static function stop() { public static function stop() {
if (SpinLock::tryLock("_stop_signal") === false) return; if (SpinLock::tryLock('_stop_signal') === false) return;
Console::warning(Console::setColor("Stopping server...", "red")); Console::warning(Console::setColor('Stopping server...', 'red'));
if (Console::getLevel() >= 4) Console::trace(); if (Console::getLevel() >= 4) Console::trace();
ZMAtomic::get("stop_signal")->set(1); ZMAtomic::get('stop_signal')->set(1);
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) { for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
if (Process::kill(zm_atomic("_#worker_" . $i)->get(), 0)) if (Process::kill(zm_atomic('_#worker_' . $i)->get(), 0))
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1); Process::kill(zm_atomic('_#worker_' . $i)->get(), SIGUSR1);
} }
server()->shutdown(); server()->shutdown();
} }
@@ -38,7 +38,7 @@ class ZMUtil
public static function getModInstance($class) { public static function getModInstance($class) {
if (!isset(ZMBuf::$instance[$class])) { if (!isset(ZMBuf::$instance[$class])) {
//Console::debug("Class instance $class not exist, so I created it."); //Console::debug('Class instance $class not exist, so I created it.');
return ZMBuf::$instance[$class] = new $class(); return ZMBuf::$instance[$class] = new $class();
} else { } else {
return ZMBuf::$instance[$class]; return ZMBuf::$instance[$class];
@@ -52,11 +52,44 @@ class ZMUtil
public static function getReloadableFiles() { public static function getReloadableFiles() {
return array_map( return array_map(
function ($x) { function ($x) {
return str_replace(DataProvider::getWorkingDir() . "/", "", $x); return str_replace(DataProvider::getSourceRootDir() . '/', '', $x);
}, array_diff( }, array_diff(
get_included_files(), get_included_files(),
Framework::$loaded_files Framework::$loaded_files
) )
); );
} }
/**
* 使用Psr-4标准获取目录下的所有类
* @param $dir
* @param $base_namespace
* @param null|mixed $rule
* @param bool $return_kv
* @return String[]
*/
public static function getClassesPsr4($dir, $base_namespace, $rule = null, $return_kv = false) {
// 预先读取下composer的file列表
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . '/composer.json'), true);
$classes = [];
// 扫描目录使用递归模式相对路径模式因为下面此路径要用作转换成namespace
$files = DataProvider::scanDirFiles($dir, true, true);
foreach ($files as $v) {
$pathinfo = pathinfo($v);
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;
foreach (($composer['autoload']['files'] ?? []) as $fi) {
if (md5_file(DataProvider::getSourceRootDir().'/'.$fi) == md5_file($dir.'/'.$v)) continue 2;
}
} 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;
else $classes[] = $class_name;
}
}
return $classes;
}
} }

View File

@@ -5,7 +5,7 @@ use ZM\Utils\DataProvider;
define("ZM_START_TIME", microtime(true)); define("ZM_START_TIME", microtime(true));
define("ZM_DATA", ZMConfig::get("global", "zm_data")); define("ZM_DATA", ZMConfig::get("global", "zm_data"));
define("APP_VERSION", LOAD_MODE == 1 ? (json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true)["version"] ?? "unknown") : "unknown"); define("APP_VERSION", LOAD_MODE == 1 ? (json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true)["version"] ?? "unknown") : "unknown");
define("CRASH_DIR", ZMConfig::get("global", "crash_dir")); define("CRASH_DIR", ZMConfig::get("global", "crash_dir"));
define("MAIN_WORKER", ZMConfig::get("global", "worker_cache")["worker"] ?? 0); define("MAIN_WORKER", ZMConfig::get("global", "worker_cache")["worker"] ?? 0);
if (!is_dir(ZM_DATA)) @mkdir(ZM_DATA); if (!is_dir(ZM_DATA)) @mkdir(ZM_DATA);

View File

@@ -1,10 +1,10 @@
<?php #plain <?php #plain
use Swoole\Coroutine as Co;
use Swoole\Atomic; use Swoole\Atomic;
use Swoole\Coroutine; use Swoole\Coroutine;
use Swoole\WebSocket\Server; use Swoole\WebSocket\Server;
use Symfony\Component\VarDumper\VarDumper; use Symfony\Component\VarDumper\VarDumper;
use ZM\API\ZMRobot; use ZM\API\OneBotV11;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console; use ZM\Console\Console;
@@ -23,7 +23,7 @@ use ZM\Context\ContextInterface;
function getClassPath($class_name) { function getClassPath($class_name) {
$dir = str_replace("\\", "/", $class_name); $dir = str_replace("\\", "/", $class_name);
$dir2 = WORKING_DIR . "/src/" . $dir . ".php"; $dir2 = DataProvider::getSourceRootDir() . "/src/" . $dir . ".php";
//echo "@@@".$dir2.PHP_EOL; //echo "@@@".$dir2.PHP_EOL;
$dir2 = str_replace("\\", "/", $dir2); $dir2 = str_replace("\\", "/", $dir2);
if (file_exists($dir2)) return $dir2; if (file_exists($dir2)) return $dir2;
@@ -44,50 +44,20 @@ function explodeMsg($msg, $ban_comma = false): array {
} }
$msgs = explode("\n", $msg); $msgs = explode("\n", $msg);
$ls = []; $ls = [];
foreach ($msgs as $k => $v) { foreach ($msgs as $v) {
if (trim($v) == "") continue; if (trim($v) == "") continue;
$ls[] = trim($v); $ls[] = trim($v);
} }
return $ls; return $ls;
} }
/** @noinspection PhpUnused */
function unicode_decode($str) { function unicode_decode($str) {
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) { return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE"); return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str); }, $str);
} }
/**
* 获取模块文件夹下的每个类文件的类名称
* @param $dir
* @param $indoor_name
* @return array
*/
function getAllClasses($dir, $indoor_name): array {
if (!is_dir($dir)) return [];
$list = scandir($dir);
$classes = [];
if ($list[0] == '.') unset($list[0], $list[1]);
foreach ($list as $v) {
//echo "Finding " . $dir . $v . PHP_EOL;
//echo "At " . $indoor_name . PHP_EOL;
if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v));
elseif (mb_substr($v, -4) == ".php") {
if (substr(file_get_contents($dir . $v), 6, 6) == "#plain") continue;
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($composer["autoload"]["files"] as $fi) {
if (realpath(DataProvider::getWorkingDir() . "/" . $fi) == realpath($dir . $v)) {
continue 2;
}
}
if ($v == "global_function.php") continue;
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}
}
return $classes;
}
function matchPattern($pattern, $context): bool { function matchPattern($pattern, $context): bool {
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "") if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true; return true;
@@ -124,7 +94,7 @@ function split_explode($del, $str, $divide_en = false): array {
$str = implode($del, $str); $str = implode($del, $str);
//echo $str."\n"; //echo $str."\n";
$ls = []; $ls = [];
foreach (explode($del, $str) as $k => $v) { foreach (explode($del, $str) as $v) {
if (trim($v) == "") continue; if (trim($v) == "") continue;
$ls[] = $v; $ls[] = $v;
} }
@@ -178,8 +148,8 @@ function getAnnotations(): array {
$s = debug_backtrace()[1]; $s = debug_backtrace()[1];
//echo json_encode($s, 128|256); //echo json_encode($s, 128|256);
$list = []; $list = [];
foreach (EventManager::$events as $event => $v) { foreach (EventManager::$events as $v) {
foreach ($v as $ks => $vs) { foreach ($v as $vs) {
//echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL; //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL;
if ($vs->class == $s["class"] && $vs->method == $s["function"]) { if ($vs->class == $s["class"] && $vs->method == $s["function"]) {
$list[get_class($vs)][] = $vs; $list[get_class($vs)][] = $vs;
@@ -191,7 +161,7 @@ function getAnnotations(): array {
function set_coroutine_params($array) { function set_coroutine_params($array) {
$cid = Co::getCid(); $cid = Co::getCid();
if ($cid == -1) die("Cannot set coroutine params at none coroutine mode."); if ($cid == -1) die(zm_internal_errcode("E00061")."Cannot set coroutine params at none coroutine mode.");
if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array); if (isset(Context::$context[$cid])) Context::$context[$cid] = array_merge(Context::$context[$cid], $array);
else Context::$context[$cid] = $array; else Context::$context[$cid] = $array;
foreach (Context::$context as $c => $v) { foreach (Context::$context as $c => $v) {
@@ -261,11 +231,11 @@ function call_with_catch($callable) {
$callable(); $callable();
} catch (Exception $e) { } catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); Console::error(zm_internal_errcode("E00033") . "Uncaught exception " . get_class($e) . ": " . $error_msg);
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg); Console::error(zm_internal_errcode("E00033") . "Uncaught " . get_class($e) . ": " . $error_msg);
Console::trace(); Console::trace();
} }
} }
@@ -298,15 +268,15 @@ function server(): ?Server {
} }
/** /**
* @return ZMRobot * @return OneBotV11
* @throws RobotNotFoundException * @throws RobotNotFoundException
* @throws ZMException * @throws ZMException
*/ */
function bot(): ZMRobot { function bot() {
if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) { if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) {
return ZMRobot::getRandom(); return OneBotV11::getRandom();
} elseif ($conn != -1) { } elseif ($conn != -1) {
if (($obj = ManagerGM::get($conn)) !== null) return new ZMRobot($obj); if (($obj = ManagerGM::get($conn)) !== null) return new OneBotV11($obj);
else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!"); else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!");
} else { } else {
throw new RobotNotFoundException("没有任何机器人连接到框架!"); throw new RobotNotFoundException("没有任何机器人连接到框架!");
@@ -376,7 +346,11 @@ function zm_error($obj) { Console::error($obj); }
function zm_config($name, $key = null) { return ZMConfig::get($name, $key); } function zm_config($name, $key = null) { return ZMConfig::get($name, $key); }
function quick_reply_closure($reply) { function quick_reply_closure($reply) {
return function() use ($reply){ return function () use ($reply) {
return $reply; return $reply;
}; };
} }
function zm_internal_errcode($code): string {
return "[ErrCode:$code] ";
}

View File

@@ -0,0 +1,11 @@
<?php
function loader__generated_id__() {
$obj = json_decode(file_get_contents(__DIR__.'/zmplugin.json'), true);
foreach(($obj["autoload-psr-4"] ?? []) as $v) {
require_once Phar::running().'/'.$v;
}
}
echo "OK!\n";
return json_decode(file_get_contents(__DIR__.'/zmplugin.json'), true) ?? ['zm_module' => false];

View File

@@ -0,0 +1,59 @@
<?php
use Doctrine\Common\Annotations\AnnotationReader;
use ZM\Annotation\Swoole\OnSetup;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\ConsoleApplication;
use ZM\Utils\DataProvider;
use ZM\Utils\ZMUtil;
require_once ((!is_dir(__DIR__ . '/../../vendor')) ? getcwd() : (__DIR__ . "/../..")) . "/vendor/autoload.php";
try {
(new ConsoleApplication('zhamao'))->initEnv();
$base_path = DataProvider::getSourceRootDir();
$scan_paths = [];
$composer = json_decode(file_get_contents($base_path . "/composer.json"), true);
foreach (($composer["autoload"]["psr-4"] ?? []) as $k => $v) {
if (is_dir($base_path . "/" . $v) && !in_array($v, $composer["extra"]["exclude_annotate"] ?? [])) {
$scan_paths[trim($k, "\\")] = $base_path . "/" . $v;
}
}
$all_event_class = [];
foreach ($scan_paths as $namespace => $autoload_path) {
$all_event_class = array_merge($all_event_class, ZMUtil::getClassesPsr4($autoload_path, $namespace));
}
$reader = new AnnotationReader();
$event_list = [];
$setup_list = [];
foreach ($all_event_class as $v) {
$reflection_class = new ReflectionClass($v);
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $vs) {
$method_annotations = $reader->getMethodAnnotations($vs);
if ($method_annotations != []) {
$annotation = $method_annotations[0];
if ($annotation instanceof SwooleHandler) {
$event_list[] = [
"class" => $v,
"method" => $vs->getName(),
"event" => $annotation->event
];
} elseif ($annotation instanceof OnSetup) {
$setup_list[] = [
"class" => $v,
"method" => $vs->getName()
];
}
}
}
}
echo json_encode(["setup" => $setup_list, "event" => $event_list]);
} catch (Throwable $e) {
$stderr = fopen("php://stderr", "w");
fwrite($stderr, zm_internal_errcode("E00031") . $e->getMessage() . " in " . $e->getFile() . " at line " . $e->getLine() . PHP_EOL);
fclose($stderr);
exit(1);
}

View File

@@ -1,28 +0,0 @@
<?php
use PHPUnit\Framework\TestCase;
use ZM\Console\Console;
use ZM\Requests\ZMRequest;
use ZM\Utils\CoroutinePool;
class CoroutinePoolTest extends TestCase
{
public function testStart() {
$this->assertTrue(true);
Console::init(4);
CoroutinePool::setSize("default", 2);
CoroutinePool::defaultSize(50);
for ($i = 0; $i < 59; ++$i) {
CoroutinePool::go(function () use ($i) {
//Console::debug("第 $i 个马上进入睡眠...");
ZMRequest::get("http://localhost:9002/test/ping");
Console::verbose(strval($i));
});
}
}
public function testA() {
$this->assertTrue(true);
}
}

View File

@@ -1,26 +0,0 @@
<?php
use PHPUnit\Framework\TestCase;
use ZM\Store\LightCache;
class LightCacheTest extends TestCase
{
public function testCache() {
LightCache::init([
"size" => 2,
"max_strlen" => 4096,
"hash_conflict_proportion" => 0,
"persistence_path" => "../composer.json"
]);
//LightCache::set("bool", true);
$this->assertEquals(true, LightCache::set("2048", 123, 3));
$this->assertArrayHasKey("2048", LightCache::getAll());
sleep(3);
$this->assertArrayNotHasKey("2048", LightCache::getAll());
$this->assertEquals("Apache-2.0", LightCache::get("license"));
$this->assertEquals("zhamao/framework", LightCache::get("name"));
//$this->assertTrue(LightCache::set("storage", "asdasd", -2));
//LightCache::savePersistence();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace ZM\Utils;
use PHPUnit\Framework\TestCase;
use ZM\Config\ZMConfig;
class DataProviderTest extends TestCase
{
protected function setUp(): void {
ZMConfig::setDirectory(realpath(__DIR__ . "/../Mock"));
if (!defined('ZM_DATA'))
define("ZM_DATA", DataProvider::getWorkingDir() . "/zm_data/");
}
public function testScanDirFiles() {
zm_dump(DataProvider::scanDirFiles("/fwef/wegweg"));
$this->assertContains("Example/Hello.php", DataProvider::scanDirFiles(DataProvider::getSourceRootDir() . '/src/Module', true, true));
}
public function testGetDataFolder() {
DataProvider::getDataFolder("testFolder");
$this->assertDirectoryExists(DataProvider::getWorkingDir() . "/zm_data/testFolder");
rmdir(DataProvider::getWorkingDir() . "/zm_data/testFolder");
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace ZM\Utils\Manager;
use PHPUnit\Framework\TestCase;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Utils\DataProvider;
class ModuleManagerTest extends TestCase
{
public function setUp(): void {
file_put_contents(DataProvider::getSourceRootDir()."/src/Module/zm.json", json_encode([
"name" => "示例模块2"
]));
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");
}
//定义常量
include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php";
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
null,
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
date_default_timezone_set($timezone);
}
public function tearDown(): void {
unlink(DataProvider::getSourceRootDir()."/src/Module/zm.json");
}
public function testGetConfiguredModules() {
zm_dump(ModuleManager::getConfiguredModules());
$this->assertArrayHasKey("示例模块", ModuleManager::getConfiguredModules());
}
public function testPackModule() {
$list = ModuleManager::getConfiguredModules();
$this->assertTrue(ModuleManager::packModule(current($list)));
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace ZM\Utils;
use Module\Example\Hello;
use Module\Middleware\TimerMiddleware;
use PHPUnit\Framework\TestCase;
use ZM\Framework;
class ZMUtilTest extends TestCase
{
public function testGetClassesPsr4() {
$this->assertContains(Hello::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/Module", "Module"));
$this->assertContains(TimerMiddleware::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/Module", "Module"));
$this->assertContains(Framework::class, ZMUtil::getClassesPsr4(DataProvider::getSourceRootDir()."/src/ZM", "ZM"));
}
}

View File

@@ -7,6 +7,7 @@ namespace ZMTest\Testing;
use Module\Example\Hello; use Module\Example\Hello;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConsoleApplication;
use ZM\Utils\ZMUtil; use ZM\Utils\ZMUtil;
class ModuleTest extends TestCase class ModuleTest extends TestCase
@@ -14,6 +15,7 @@ class ModuleTest extends TestCase
protected function setUp(): void { protected function setUp(): void {
ZMConfig::setDirectory(realpath(__DIR__."/../Mock")); ZMConfig::setDirectory(realpath(__DIR__."/../Mock"));
set_coroutine_params([]); set_coroutine_params([]);
(new ConsoleApplication('zhamao-test'))->initEnv();
require_once __DIR__ . '/../../../src/ZM/global_defines.php'; require_once __DIR__ . '/../../../src/ZM/global_defines.php';
} }

13
test/bootstrap.php Normal file
View File

@@ -0,0 +1,13 @@
<?php
/**
* @since 2.5
*/
set_coroutine_params([]);
// 模拟define
chdir(__DIR__.'/../');
define("WORKING_DIR", getcwd());
define("SOURCE_ROOT_DIR", WORKING_DIR);
define("LOAD_MODE", 0);
define("FRAMEWORK_ROOT_DIR", realpath(__DIR__ . "/../"));

View File

@@ -1,10 +0,0 @@
<?php
use ZM\Exception\ZMException;
use ZM\Store\LightCache;
LightCache::getMemoryUsage();
try {
LightCache::getExpire('1');
} catch (ZMException $e) {
}