Compare commits

..

19 Commits

Author SHA1 Message Date
jerry
4ee16d4fc6 update to 2.5.0-b1 (build 408) 2021-06-16 00:17:30 +08:00
Whale
59d614a24e Merge pull request #40 from sunxyw/patch-1
add latest config file format to quickstart-robot guide
2021-06-15 18:20:12 +08:00
sunxyw
40037b3f4b add highlight to quickstart-robot guide 2021-06-15 17:55:21 +08:00
sunxyw
f91c8b6205 add latest config file format to quickstart-robot guide
After the v1.0.0-beta2 update of go-cqhttp, the config file format has been converted from `hjson` to `yaml`. Update the guide.
See https://github.com/Mrs4s/go-cqhttp/releases/tag/v1.0.0-beta2
2021-06-15 17:46:00 +08:00
Whale
8f73a99ff7 Merge pull request #39 from YiwanGi/patch-1
Update global.php
2021-06-10 23:39:45 +08:00
Wang
e0f07cb396 Update global.php
🙃 强迫症 √
2021-06-10 17:45:27 +08:00
Whale
2950ab7472 Update README.md 2021-05-26 16:56:55 +08:00
Whale
0ab4053dfb Update README.md 2021-05-26 16:32:37 +08:00
Whale
745aa0f268 Update README.md 2021-05-26 16:31:41 +08:00
Whale
12bb93c2f0 Update README.md 2021-05-08 20:45:49 +08:00
95ca175901 Merge remote-tracking branch 'origin/master' 2021-05-08 10:06:17 +08:00
71585ed29d update to build 407
change daemon command from system to Process::kill
add master_pid for motd information
add option --preview
delete server_event_handler_class and use process
go-cqhttp-down.sh script add arm64 support
add ./zhamao support
change build-runtime.sh to install-runtime.sh
add option --disable-safe-exit
adjust some Console output
set start script using /bin/sh for supporting auto searching php and framework
2021-05-08 10:02:41 +08:00
Whale
231a377718 Update README.md 2021-05-08 01:24:27 +08:00
jerry
a80ee902a9 Merge remote-tracking branch 'origin/master' 2021-04-06 01:20:07 +08:00
jerry
c2d3b5f92a update to build 406 version 2021-04-06 01:19:56 +08:00
Whale
64365af124 Update README.md 2021-03-31 00:10:43 +08:00
jerry
60619dbffc update docs 2021-03-29 17:18:19 +08:00
jerry
77e77e9cc3 update to 2.4.4 version (build 405)
change requirements: add pcntl as required extension
update docs
2021-03-29 17:12:09 +08:00
jerry
d72b41a902 update to build 404
fix ./zhamao command
fix warning when first time starting framework
2021-03-29 15:48:47 +08:00
118 changed files with 2863 additions and 1663 deletions

9
.gitignore vendored
View File

@@ -2,7 +2,6 @@
/src/test/
/src/webconsole/config/
/vendor/
zm.json
/zm_data/
composer.lock
/resources/server.phar
@@ -12,4 +11,10 @@ composer.lock
.phpunit.result.cache
.daemon_pid
/runtime/
/tmp/
/tmp/
/ext/go-cqhttp/data/
/ext/go-cqhttp/logs/
/ext/go-cqhttp/config.hjson
/ext/go-cqhttp/device.json
/ext/go-cqhttp/go-cqhttp
/ext/go-cqhttp/session.token

View File

@@ -13,12 +13,11 @@
</div>
## 开发者注意
开发者 QQ 群:**670821194** [点击加入群聊](https://jq.qq.com/?_wv=1027&k=YkNI3AIr)
当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!
**如果有愿意一起开发框架本身的开发者,请提出 PR 或 Issue 参与开发!如果对框架本身的核心设计有更好的想法,可与作者成立开发组(目前仅作者 1 人),参与 OneBot V12 生态和框架本身的开发。**
2.0 版本如果有问题请第一时间加群反馈!
**相关正在进行的版本任务见 Projects 一栏!**
## 简介
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
@@ -41,7 +40,16 @@ public function index() {
```
## 开始
框架首先需要部署环境,可以参考下方文档中部署环境和框架的方法进行。
如果你是初学者,可以直接使用以下脚本部署 PHP 环境和安装框架的脚手架:
```bash
# 新建一个自己喜欢名字的文件夹,运行一键安装脚本
mkdir zhamao-app/
cd zhamao-app/
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
# 启动
runtime/php vendor/bin/start server
```
## 文档v2 版本)
查看文档(国内自建):<https://docs-v2.zhamao.xin/>
@@ -60,6 +68,7 @@ public function index() {
- 自带 MySQL、Redis 等数据库连接池等数据库连接方案
- 本身为 HTTP 服务器、WebSocket 服务器,可以构建属于自己的 HTTP API 接口
- 静态文件服务器,可将前端合并到一起
- 自带 PHP + Swoole 环境无需手动编译安装by [crazywhalecc/static-php-cli](https://github.com/crazywhalecc/static-php-cli)
## 从 v1 升级
炸毛框架 v2 相对 v1 版本改动了不少内容,其中包括框架底层机制、注解事件分发、调试、命名空间等变化,详情可查看上方文档。

View File

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

View File

@@ -1,6 +1,34 @@
#!/usr/bin/env php
<?php /** @noinspection PhpIncludeInspection */
#!/bin/sh
require_once ((!is_dir(__DIR__ . '/../vendor')) ? getcwd() : (__DIR__ . "/..")) . "/vendor/autoload.php";
# shellcheck disable=SC2068
# shellcheck disable=SC2181
# author: crazywhalecc
# since: 2.5.0
(new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run();
if [ -f "$(pwd)/runtime/php" ]; then
executable="$(pwd)/runtime/php"
echo "* Framework started with built-in php."
else
which php >/dev/null 2>&1
if [ $? -eq 0 ]; then
executable=$(which php)
else
echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!'
exit 1
fi
fi
result=$(echo "$1" | grep -E "module|build")
if [ "$result" != "" ]; then
executable="$executable -d phar.readonly=off"
fi
if [ -f "$(pwd)/src/entry.php" ]; then
$executable "$(pwd)/src/entry.php" $@
elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
else
echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
exit 1
fi

View File

@@ -1,226 +0,0 @@
#!/usr/bin/env bash
_php_ver="7.4.16"
_libiconv_ver="1.15"
_openssl_ver="1.1.1j"
_swoole_ver="4.6.3"
_home_dir=$(pwd)"/"
function checkEnv() {
echo -n "检测核心组件... "
_msg="请通过包管理安装此依赖!"
type git >/dev/null 2>&1 || { echo "失败git 不存在!"$_msg; return 1; }
type gcc >/dev/null 2>&1 || { echo "失败gcc 不存在!"$_msg; return 1; }
type g++ >/dev/null 2>&1 || { echo "失败g++ 不存在!"$_msg; return 1; }
type unzip >/dev/null 2>&1 || { echo "失败unzip 不存在!"$_msg; return 1; }
type autoconf >/dev/null 2>&1 || { echo "失败autoconf 不存在!"; return 1; }
type pkg-config >/dev/null 2>&1 || { echo "失败pkg-config 不存在!"$_msg; return 1; }
type wget >/dev/null 2>&1 || type curl >/dev/null 2>&1 || { echo "失败curl/wget 不存在!"$_msg; return 1; }
echo "完成!"
echo "如果下载过程中出现错误,请删除 runtime/ 文件夹重试!"
echo "此脚本安装的php/swoole均为最小版本不含其他扩展如zip、xml、gd"
echo -n "如果编译过程缺少依赖,请通过包管理安装对应的依赖![按回车继续] "
# shellcheck disable=SC2034
read ents
}
function downloadIt() {
downloader="wget"
type wget >/dev/null 2>&1 || { downloader="curl"; }
if [ "$downloader" = "wget" ]; then
_down_prefix="O"
else
_down_prefix="o"
fi
_down_symbol=0
if [ ! -f "$2" ]; then
$downloader "$1" -$_down_prefix "$2" >/dev/null 2>&1 && \
echo "完成!" && _down_symbol=1
else
echo "已存在!" && _down_symbol=1
fi
if [ $_down_symbol == 0 ]; then
echo "失败!请检查网络连接!"
rm -rf "$2"
return 1
fi
return 0
}
function downloadAll() {
# 创建文件夹
mkdir "$_home_dir""runtime" >/dev/null 2>&1
mkdir "$_home_dir""runtime/tmp_download" >/dev/null 2>&1
mkdir "$_home_dir""runtime/cellar" >/dev/null 2>&1
_down_dir=$_home_dir"runtime/tmp_download/"
# 下载PHP
echo -n "正在下载 php 源码... "
downloadIt "http://mirrors.sohu.com/php/php-$_php_ver.tar.gz" "$_down_dir""php.tar.gz" || { exit; }
# 下载libiconv
echo -n "正在下载 libiconv 源码... "
downloadIt "https://mirrors.tuna.tsinghua.edu.cn/gnu/libiconv/libiconv-$_libiconv_ver.tar.gz" "$_down_dir""libiconv.tar.gz" || { exit; }
echo -n "正在下载 openssl 源码... "
downloadIt "http://mirrors.cloud.tencent.com/openssl/source/openssl-$_openssl_ver.tar.gz" "$_down_dir""openssl.tar.gz" || { exit; }
echo -n "正在下载 swoole 源码... "
downloadIt "https://dl.zhamao.me/swoole/swoole-$_swoole_ver.tgz" "$_down_dir""swoole.tar.gz" || { exit; }
echo -n "正在下载 composer ... "
downloadIt "https://mirrors.aliyun.com/composer/composer.phar" "$_home_dir""runtime/cellar/composer" || { exit; }
#echo -n "正在下载 libcurl 源码... "
#downloadIt "https://curl.se/download/curl-7.75.0.tar.gz" "$_down_dir""libcurl.tar.gz" || { exit; }
}
function compileIt() {
_down_dir="$_home_dir""runtime/tmp_download/"
_source_dir="$_home_dir""runtime/tmp_source/"
_cellar_dir="$_home_dir""runtime/cellar/"
case $1 in
"libiconv")
if [ -f "$_cellar_dir""libiconv/bin/iconv" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libiconv.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libiconv-"$_libiconv_ver && \
./configure --prefix="$_cellar_dir""libiconv" >/dev/null 2>&1 && \
make -j4 >/dev/null 2>&1 && \
make install >/dev/null 2>&1 && \
echo "完成!"
;;
"libzip")
if [ -f "$_cellar_dir""libzip/bin/libzip" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libzip.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libzip-1.7.3" && \
./configure --prefix="$_cellar_dir""libzip" && \
make -j4 && \
make install && \
echo "完成!"
;;
"libcurl")
if [ -f "$_cellar_dir""libcurl/bin/libcurl" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""libcurl.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""libcurl-7.75.0" && \
./configure --prefix="$_cellar_dir""libcurl" && \
make -j4 && \
make install && \
echo "完成!"
;;
"php")
if [ -f "$_cellar_dir""php/bin/php" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""php.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""php-"$_php_ver && \
./buildconf --force && \
PKG_CONFIG_PATH="$_cellar_dir""openssl/lib/pkgconfig" ./configure --prefix="$_cellar_dir""php" \
--with-config-file-path="$_home_dir""runtime/etc" \
--disable-fpm \
--enable-cli \
--enable-posix \
--enable-ctype \
--enable-mysqlnd \
--enable-pdo \
--enable-pcntl \
--with-openssl="$_cellar_dir""openssl" \
--enable-sockets \
--disable-xml \
--disable-xmlreader \
--disable-xmlwriter \
--without-libxml \
--disable-dom \
--without-sqlite3 \
--without-pdo-sqlite \
--disable-simplexml \
--with-pdo-mysql=mysqlnd \
--with-zlib \
--with-iconv="$_cellar_dir""libiconv" \
--enable-phar && \
make -j4 && \
make install && \
cp "$_source_dir""php-$_php_ver/php.ini-production" "$_home_dir""runtime/etc/php.ini" && \
echo "完成!"
;;
"openssl")
if [ -f "$_cellar_dir""openssl/bin/openssl" ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""openssl.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""openssl-""$_openssl_ver" && \
./config --prefix="$_cellar_dir""openssl" && \
make -j4 && \
make install && \
echo "完成!"
;;
"swoole")
"$_home_dir"runtime/cellar/php/bin/php --ri swoole >/dev/null 2>&1
# shellcheck disable=SC2181
if [ $? == 0 ]; then
echo "已编译!" && return
fi
tar -xf "$_down_dir""swoole.tar.gz" -C "$_source_dir" && \
cd "$_source_dir""swoole-""$_swoole_ver" && \
PATH="$_cellar_dir""php/bin:$PATH" phpize && \
PATH="$_cellar_dir""php/bin:$PATH" ./configure --prefix="$_cellar_dir""php" \
--enable-sockets \
--enable-http2 \
--enable-openssl \
--with-openssl-dir="$_cellar_dir""openssl" \
--enable-mysqlnd && \
make -j4 && \
make install && \
echo "extension=swoole.so" >> "$_home_dir""runtime/etc/php.ini" && \
echo "完成!"
;;
esac
}
function compileAll() {
_down_dir=$_home_dir"runtime/tmp_download/"
_source_dir=$_home_dir"runtime/tmp_source/"
mkdir "$_source_dir" >/dev/null 2>&1
mkdir "$_home_dir""runtime/etc" >/dev/null 2>&1
echo -n "正在编译 libiconv ... "
compileIt libiconv || { return 1; }
#echo -n "正在编译 libcurl ... "
#compileIt libcurl || { exit; }
echo -n "正在编译 openssl ... "
compileIt openssl || { return 1; }
#echo -n "正在编译 libzip ... "
#compileIt libzip || { exit; }
echo -n "正在编译 php ... "
compileIt php || { return 1; }
echo -n "正在编译 swoole ... "
compileIt swoole || { return 1; }
return 0
}
function linkBin(){
mkdir "$_home_dir""runtime/bin" >/dev/null 2>&1
ln -s "$_home_dir""runtime/cellar/php/bin/php" "$_home_dir""runtime/bin/php" >/dev/null 2>&1
echo "runtime/cellar/php/bin/php runtime/cellar/composer \$@" > "$_home_dir""runtime/bin/composer" && chmod +x "$_home_dir""runtime/bin/composer"
echo "Done!"
runtime/bin/composer config repo.packagist composer https://mirrors.aliyun.com/composer/
}
checkEnv && \
downloadAll && \
compileAll && \
linkBin && \
echo "成功部署所有环境!" && \
echo -e "composer更新依赖\t\"runtime/bin/composer update\"" && \
echo -e "启动框架(源码模式)\t\"runtime/bin/php bin/start server\"" && \
echo -e "启动框架(普通模式)\t\"runtime/bin/php vendor/bin/start server\""

View File

@@ -23,20 +23,24 @@
"php": ">=7.2",
"ext-json": "*",
"ext-posix": "*",
"doctrine/annotations": "~1.10",
"psy/psysh": "@stable",
"symfony/polyfill-ctype": "^1.20",
"symfony/polyfill-mbstring": "^1.20",
"symfony/console": "^5.1",
"symfony/routing": "^5.1",
"zhamao/console": "^1.0.10",
"doctrine/annotations": "~1.12 || ~1.4.0",
"symfony/polyfill-ctype": "^1.19",
"symfony/polyfill-mbstring": "^1.19",
"symfony/console": "~5.0 || ~4.0 || ~3.0",
"symfony/routing": "~5.0 || ~4.0 || ~3.0",
"zhamao/console": "^1.0",
"zhamao/config": "^1.0",
"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": {
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",
"ext-mbstring": "Use C/C++ extension instead of polyfill will be more efficient",
"ext-pdo_mysql": "If you use mysql in framework, you will need this extension",
"ext-redis": "If you use Redis in framework, you will need this extension",
"league/climate": "Display columns and status in terminal"
},
"autoload": {
@@ -48,6 +52,7 @@
]
},
"require-dev": {
"swoole/ide-helper": "@dev"
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^8.5 || ^9.0"
}
}
}

View File

@@ -9,13 +9,13 @@ $config['host'] = '0.0.0.0';
$config['port'] = 20001;
/** 框架开到公网或外部的HTTP访问链接通过 DataProvider::getFrameworkLink() 获取 */
$config['http_reverse_link'] = "http://127.0.0.1:" . $config['port'];
$config['http_reverse_link'] = 'http://127.0.0.1:' . $config['port'];
/** 框架是否启动debug模式 */
/** 框架是否启动debug模式当debug模式为true时启用热更新需要安装inotify扩展 */
$config['debug_mode'] = false;
/** 存放框架内文件数据的目录 */
$config['zm_data'] = realpath(__DIR__ . "/../") . '/zm_data/';
$config['zm_data'] = realpath(WORKING_DIR) . '/zm_data/';
/** 存放各个模块配置文件的目录 */
$config['config_dir'] = $config['zm_data'] . 'config/';
@@ -34,9 +34,14 @@ $config['swoole'] = [
//'task_enable_coroutine' => true
];
/** 一些框架与Swoole运行时设置的调整 */
$config['runtime'] = [
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL)
];
/** 轻量字符串缓存,默认开启 */
$config['light_cache'] = [
'size' => 512, //最多允许储存的条数需要2的倍数
'size' => 512, //最多允许储存的条数需要2的倍数
'max_strlen' => 32768, //单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多
'persistence_path' => $config['zm_data'] . '_cache.json',
@@ -44,9 +49,9 @@ $config['light_cache'] = [
];
/** 大容量跨进程变量存储2.2.0可用) */
$config["worker_cache"] = [
"worker" => 0,
"transaction_timeout" => 30000
$config['worker_cache'] = [
'worker' => 0,
'transaction_timeout' => 30000
];
/** MySQL数据库连接信息host留空则启动时不创建sql连接池 */
@@ -61,7 +66,7 @@ $config['sql_config'] = [
PDO::ATTR_EMULATE_PREPARES => false
],
'sql_no_exception' => false,
'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6
'sql_default_fetch_mode' => PDO::FETCH_ASSOC //added in 1.5.6
];
/** Redis连接信息host留空则启动时不创建Redis连接池 */
@@ -74,7 +79,7 @@ $config['redis_config'] = [
];
/** onebot连接约定的token */
$config["access_token"] = '';
$config['access_token'] = '';
/** HTTP服务器固定请求头的返回 */
$config['http_header'] = [
@@ -89,11 +94,11 @@ $config['http_default_code_page'] = [
/** zhamao-framework在框架启动时初始化的atomic们 */
$config['init_atomics'] = [
//'custom_atomic_name' => 0, //自定义添加的Atomic
//'custom_atomic_name' => 0, //自定义添加的Atomic
];
/** 终端日志显示等级0-4 */
$config["info_level"] = 2;
$config['info_level'] = 2;
/** 上下文接口类 implemented from ContextInterface */
$config['context_class'] = \ZM\Context\Context::class;
@@ -101,17 +106,12 @@ $config['context_class'] = \ZM\Context\Context::class;
/** 静态文件访问 */
$config['static_file_server'] = [
'status' => false,
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
'document_root' => realpath(__DIR__ . '/../') . '/resources/html',
'document_index' => [
'index.html'
]
];
/** 注册 Swoole Server 事件注解的类列表 */
$config['server_event_handler_class'] = [
// 这里添加例如 \ZM\Event\ServerEventHandler::class 这样的启动注解类
];
/** 机器人解析模块关闭后无法使用如CQCommand等注解(上面的modules即将废弃) */
$config['onebot'] = [
'status' => true,
@@ -127,4 +127,10 @@ $config['remote_terminal'] = [
'token' => ''
];
/** 模块(插件)加载器的相关设置 */
$config['module_loader'] = [
'enable_hotload' => true,
'load_path' => $config['zm_data'] . 'modules'
];
return $config;

View File

@@ -1,54 +0,0 @@
# FAQ
这里会写一些常见的疑难解答。
## 启动时报错 Address already in use
1. 检查是否开启了两次框架,每个端口只能开启一个框架。
2. 如果是之前已经在 20001 端口或者你设置了别的应用同样占用此端口,更换配置文件 `global.php` 中的 port 即可。
3. 如果是之前框架成功启动,但是使用 Ctrl+C 停止后再次启动导致的报错,请根据下面的步骤来检查是否存在僵尸进程。
- 如果系统内装有 `htop`,可以直接在 `htop` 中开启 Tree 模式并使用 filter 过滤 php检查残留的框架进程。
- 如果系统没有 `htop`,使用 `ps aux | grep vendor/bin/start | grep -v grep` 如果存在进程,请使用以下命令尝试杀掉:
```bash
# 如果确定框架的数据都已保存且没有需要保存的缓存数据,直接杀掉 SIGKILL 即可,输入下面这条
ps aux | grep vendor/bin/start | grep -v grep | awk '{print $2}' | xargs kill -9
# 如果不确定框架是不是还继续运行,想尝试正常关闭(走一遍储存保存数据的事件),使用下面这条
# 首先使用 'ps aux | grep vendor/bin/start | grep -v grep' 找到进程中第二列最小的pid
# 然后使用下面的这条命令假设最小的pid是23643
kill -INT 23643
# 如果使用 ps aux 看不到框架相关进程,证明关闭成功,否则需要使用第一条强行杀死
```
## 出现 deadlock 字样
一般情况下,如果误操作框架可能会报如下图的错误:
```
===================================================================
[FATAL ERROR]: all coroutines (count: 1) are asleep - deadlock!
===================================================================
[Coroutine-1]
--------------------------------------------------------------------
#0 Swoole\Coroutine\System::sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/global_functions.php:232]
#1 zm_sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/Module/Example/Hello.php:38]
#2 Module\Example\Hello->onStart() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:205]
#3 ZM\Event\EventDispatcher->dispatchEvent() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:89]
#4 ZM\Event\EventDispatcher->dispatchEvents() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/SwooleEvent/OnWorkerStart.php:130]
#5 ZM\Event\SwooleEvent\OnWorkerStart->onCall() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Framework.php:336]
```
这种错误的出现原因一般是因为协程未结束而 Worker 进程提前退出导致的,这个错误也可手动造成(在任意 Worker 进程内的位置使用 `zm_yield()` 且不使用 `zm_resume()` 恢复,期间使用 reload 或 stop 重启或停止框架就会报错)。
还有一种情况是数据库、文件读取或下载上传还没有传送结束,时间已经超时,在关闭或重启框架时不得不强行切断协程的运行。这种情况建议根据下方的打印输出栈进行插错,建议将协程运行时间长的过程缩短或调长 `swoole` 配置项下面的 `max_wait_time` 时间2.4.3 版本起此参数默认为 5 秒。
## 使用 LightCache 关闭时无法正常保存持久化
LightCache 因为是跨内存使用的,所以每次重启和关闭框架时,都只会让其中一个进程去保存。因为在 2.4.2 版本开始,持久化的逻辑发生了更改,不再支持 `expire = -2` 进行设置持久化(因为那样会很容易让开发者写错),仅支持使用 `LightCache::addPersistence($key)` 这样的方式进行设置持久化,所以在 2.4.2 版本以后,请使用此方法进行持久化设置,保证数据不丢失。
此外2.4.2 版本起,不再支持用户手动调用 `savePersistence()` 方法,普通用户不可手动调用此方法,否则会导致数据出错。
##

View File

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

View File

@@ -87,3 +87,29 @@ MessageUtil::splitCommand("我有 三个空格"); // ["我有","三个空格"]
返回:`\ZM\Entity\MatchObject` 对象,含有匹配成功与否,匹配到的注解对象,匹配到的分割词等,见 []
### addShortCommand()
快速添加一条静态消息回复命令。
定义:`addShortCommand($command, string $reply)`
参数 `$command` 为问的内容,如 `炸毛不聪明`
参数 `$reply` 为回复的内容,如 `其实还是很聪明的!`
这个命令推荐在 `@OnStart` 注解下使用,可以用这个来做一个动态的词库,从文件加载后使用。
```php
/**
* @OnStart()
*/
public function onStart() {
MessageUtil::addShortCommand("炸毛不聪明", "其实还是很聪明的!");
}
```
<chat-box>
) 炸毛不聪明
( 其实还是很聪明的!
</chat-box>

View File

@@ -0,0 +1,86 @@
# 图灵机器人 APITuringAPI
类定义:`\ZM\API\TuringAPI`
## 方法
### TuringAPI::getTuringMsg()
请求图灵接口,返回回复的消息。
定义:`getTuringMsg($msg, $user_id, $api)`
参数 `$msg` 为用户的消息内容,如果含有图片 CQ 码,则自动转换为图灵兼容的接口模式。
参数 `$user_id` 为用户 ID一般默认给 QQ 号码就可以了,注意最好不要有特殊字符(如 `./\<>*` 等),否则会间断性调用失败。
参数 `$api` 为图灵机器人的 `apikey`,可以到 <http://www.turingapi.com/> 申请免费或付费的 API key。
在框架的示例模块中,已经写好了一个正常机器人响应图灵回复的命令,如下:
```php
class Hello {
/**
* 图灵机器人的内置实现在www.turingapi.com申请一个apikey填入下方变量即可。
* @CQCommand(start_with="机器人",end_with="机器人",message_type="group")
* @CQMessage(message_type="private",level=1)
*/
public function turingAPI() {
$user_id = ctx()->getUserId();
$api = ""; // 请在这里填入你的图灵机器人的apikey
if ($api === "") return false; //如果没有填入apikey则此功能关闭
if (($this->_running_annotation ?? null) instanceof CQCommand) {
$msg = ctx()->getFullArg("我在!有什么事吗?");
} else {
$msg = ctx()->getMessage();
}
ctx()->setMessage($msg);
if (MessageUtil::matchCommand($msg, ctx()->getData())->status === false) {
return TuringAPI::getTuringMsg($msg, $user_id, $api);
} else {
QQBot::getInstance()->handle(ctx()->getData(), ctx()->getCache("level") + 1);
return true;
}
}
/**
* 响应at机器人的消息
* @CQBefore("message")
*/
public function changeAt() {
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
$msg = str_replace(CQ::at(ctx()->getRobotId()), "", ctx()->getMessage());
ctx()->setMessage("机器人" . $msg);
Console::info(ctx()->getMessage());
}
return true;
}
}
```
如上述代码,我们将申请的 apikey 填入变量 `$api` 中,启动机器人即可使用,以下是实测消息(我用自己申请的 key 做测试回复的消息)。
<chat-box>
) 你咋了
( 我没事哦,谢谢您的关心。
) 上海天气
( 上海:周一 03月29日,小雨 东南风转东风,最低气温14度最高气温24度。
^ 切换为群内
) 机器人
( 我在!有什么事吗?
) 你叫啥
( 我的名字叫炸毛,认识你很高兴呢!
</chat-box>
在默认示例模块中的例子是直接可以拿来用的,这段代码同时做了对 at 的处理、以及兼容用户自定义写的其他命令的方式,下面是默认模块填好 apikey 后可以用的各种方式提问:
<chat-box>
^ 切换为群内
) 我是一条普通消息,这条机器人不会回复我
) @机器人 你叫啥
( 我是聪明可爱的炸毛,认识你很高兴。
) 机器人
( 我在!有什么事吗?
) 一言
( 多少事,从来急,天地转,光阴迫,一万年太久,只争朝夕。
</chat-box>

View File

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

View File

@@ -4,6 +4,8 @@
## getClassPath()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L24)
根据加载的用户编写的代码类名来获取类所在的文件路径。
=== "src/Module/Example/Hello.php"
@@ -38,6 +40,8 @@
## explodeMsg()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L39)
切割字符串的函数支持多空格换行tab。
定义:`explodeMsg($msg, $ban_comma = false)`
@@ -49,6 +53,8 @@ echo json_encode($s, 128|256); // ["你好啊","你好你好","我还有多个
## unicode_decode()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L54)
Unicode 解码,一般用于被转义的 Unicode 转回来。
```php
@@ -57,6 +63,8 @@ echo unicode_decode("\u4f60\u597d"); // 你好
## matchPattern()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L91)
根据星号匹配字符串(非正则表达式)。
匹配示例:
@@ -81,6 +89,8 @@ matchPattern("*把*翻译成*", "请把你好翻译成阿拉伯语"); // true
## split_explode()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L103)
和 `explodeMsg()` 类似,用作分割字符串,不过此函数加入了对 `中文|数字` 两者的分割,也就是说中文和数字之间也会被分割。
定义:`split_explode($del, $str, $divide_en = false)`
@@ -97,6 +107,8 @@ split_explode(" ", "前进20 急啊急啊"); // ["前进","20","急啊急啊"]
## matchArgs()
[源码](https://github.com/zhamao-robot/zhamao-framework/blob/master/src/ZM/global_functions.php#L135)
`matchPattern()` 的扩展,如果 `matchPattern()` 格式的字符串和模式匹配成功,则通过星号位置来提取星号匹配到的内容,参数同 `matchPattern()`。
```php

View File

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

View File

@@ -118,7 +118,7 @@ dump(LightCache::getExpire("test")); // 返回 10
### LightCache::getExpireTS()
获取存储项要过期的时间戳。
获取存储项要过期的时间戳。2.4.3 起可用)
定义:`LightCache::getExpireTS(string $key)`
@@ -135,7 +135,7 @@ dump(LightCache::getExpire("test")); // 返回 null
获取轻量缓存使用的总空间大小(字节)
```php
LightCache::getMemoryUsage());
LightCache::getMemoryUsage();
```
轻量缓存的内存手工计算方式:(Table 结构体长度` + `KEY 长度 64 字节 + `$size`) * (1 + `$conflict_proportion`) * 列尺寸。

View File

@@ -281,6 +281,7 @@
| 参数名称 | 参数范围 | 默认 |
| ----------- | ------------------------------ | ---- |
| command | `string`**必填**,命令字符串 | |
| alias | `string`,可选,命令的别名 | |
| description | `string`,要显示的帮助文本 | 空 |
## 示例1机器人连接框架后输出信息

View File

@@ -333,6 +333,8 @@ TODO先放着有时间再更。
在设置了 `level` 参数后,如果设置了多个 `@CQBefore` 监听事件函数,更高 `level` 的事件函数返回了 `false`,则低 `level` 的绑定函数不会执行,所有 `@CQMessage` 绑定的事件也不会执行。
你也可以使用 `@CQBefore` 做一些消息的转发和过滤。比如你想去除用户发来的文字中的 emoji、图片等 CQ 码,只保留文本。
使用 `ctx()->waitMessage()` 时等待用户输入下一条消息功能和 CQBefore 配合过滤消息时需注意,见 [FAQ - CQBefore 过滤不了 waitMessage](/FAQ/wait-message-cqbefore/)
## CQAfter()

3
docs/faq/FAQ.md Normal file
View File

@@ -0,0 +1,3 @@
# FAQ
这里会写一些常见的疑难解答,点击左侧问题名称打开对应解决方法。

View File

@@ -0,0 +1,19 @@
# 启动时报错 Address already in use
1. 检查是否开启了两次框架,每个端口只能开启一个框架。
2. 如果是之前已经在 20001 端口或者你设置了别的应用同样占用此端口,更换配置文件 `global.php` 中的 port 即可。
3. 如果是之前框架成功启动,但是使用 Ctrl+C 停止后再次启动导致的报错,请根据下面的步骤来检查是否存在僵尸进程。
- 如果系统内装有 `htop`,可以直接在 `htop` 中开启 Tree 模式并使用 filter 过滤 php检查残留的框架进程。
- 如果系统没有 `htop`,使用 `ps aux | grep vendor/bin/start | grep -v grep` 如果存在进程,请使用以下命令尝试杀掉:
```bash
# 如果确定框架的数据都已保存且没有需要保存的缓存数据,直接杀掉 SIGKILL 即可,输入下面这条
ps aux | grep vendor/bin/start | grep -v grep | awk '{print $2}' | xargs kill -9
# 如果不确定框架是不是还继续运行,想尝试正常关闭(走一遍储存保存数据的事件),使用下面这条
# 首先使用 'ps aux | grep vendor/bin/start | grep -v grep' 找到进程中第二列最小的pid
# 然后使用下面的这条命令假设最小的pid是23643
kill -INT 23643
# 如果使用 ps aux 看不到框架相关进程,证明关闭成功,否则需要使用第一条强行杀死
```

View File

@@ -0,0 +1,22 @@
# 出现 deadlock 字样
一般情况下,如果误操作框架可能会报如下图的错误:
```
===================================================================
[FATAL ERROR]: all coroutines (count: 1) are asleep - deadlock!
===================================================================
[Coroutine-1]
--------------------------------------------------------------------
#0 Swoole\Coroutine\System::sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/global_functions.php:232]
#1 zm_sleep() called at [/Users/jerry/project/git-project/zhamao-framework/src/Module/Example/Hello.php:38]
#2 Module\Example\Hello->onStart() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:205]
#3 ZM\Event\EventDispatcher->dispatchEvent() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/EventDispatcher.php:89]
#4 ZM\Event\EventDispatcher->dispatchEvents() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Event/SwooleEvent/OnWorkerStart.php:130]
#5 ZM\Event\SwooleEvent\OnWorkerStart->onCall() called at [/Users/jerry/project/git-project/zhamao-framework/src/ZM/Framework.php:336]
```
这种错误的出现原因一般是因为协程未结束而 Worker 进程提前退出导致的,这个错误也可手动造成(在任意 Worker 进程内的位置使用 `zm_yield()` 且不使用 `zm_resume()` 恢复,期间使用 reload 或 stop 重启或停止框架就会报错)。
还有一种情况是数据库、文件读取或下载上传还没有传送结束,时间已经超时,在关闭或重启框架时不得不强行切断协程的运行。这种情况建议根据下方的打印输出栈进行插错,建议将协程运行时间长的过程缩短或调长 `swoole` 配置项下面的 `max_wait_time` 时间2.4.3 版本起此参数默认为 5 秒。

View File

@@ -0,0 +1,5 @@
# 使用 LightCache 关闭时无法正常保存持久化
LightCache 因为是跨内存使用的,所以每次重启和关闭框架时,都只会让其中一个进程去保存。因为在 2.4.2 版本开始,持久化的逻辑发生了更改,不再支持 `expire = -2` 进行设置持久化(因为那样会很容易让开发者写错),仅支持使用 `LightCache::addPersistence($key)` 这样的方式进行设置持久化,所以在 2.4.2 版本以后,请使用此方法进行持久化设置,保证数据不丢失。
此外2.4.2 版本起,不再支持用户手动调用 `savePersistence()` 方法,普通用户不可手动调用此方法,否则会导致数据出错。

View File

@@ -0,0 +1,23 @@
# CQBefore 过滤不了 waitMessage
因为 `waitMessage()` 功能是要等待接收下一个消息事件的,而消息事件又会被 CQBefore 走一遍。但是这里就会有一个问题,那 `waitMessage()` 的消息会不会走 CQBefore 呢?(显然不会啊!这个问题的题目就是这个!)
框架在 2.4.2 版本之前是无法过滤 waitMessage() 的(之前在 2.1 版本左右的几个版本是可以的,但这里不讨论历史版本),从 2.4.2 版本起支持过滤 `waitMessage`,但是需要设置一下 `@CQBefore` 的级别。
```php
/**
* @CQBefore("message",level=201)
*/
public function filter1() {
return true;
}
/**
* @CQBefore("message")
*/
public function filter2() {
return true;
}
```
如果 `level >= 200`,那么此注解事件则会过滤 `waitMessage()`,如果 `level < 200`,则不会。(`@CQBefore` 的默认 level 为 20所以默认情况下是不会过滤 waitMessage 的)

View File

@@ -2,111 +2,49 @@
> 这篇为炸毛框架以及环境的部署教程。
框架部署分为环境部署和框架部署。框架部署非常简单,只需要通用的指令,下方主要说环境部署
框架部署分为两部分,一部分是安装 PHP 环境,另一部分是通过 Composer 或 GitHub 拉取框架的脚手架
## Docker 部署 PHP 环境
如果你不想干扰主机的环境,可以使用 Docker 进行拉取框架适用的 PHP7 with Swoole Extension Docker Container。本框架安装教程中使用的 DockerHub 及 Dockerfile 构建文件所构建的容器均为独立的容器,和框架无关,此 Docker 也可以用作运行**其他基于 php-cli 模式的项目**。
## 一键下载静态 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 权限):
从 2.4.4 版本起,炸毛框架支持一键拉取一个静态的 PHP 运行时和脚手架,只需运行下面的脚本即可。(开发环境推荐此方法
```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)
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
# 安装完成后的启动框架命令2.5.0 版本后可省略掉 runtime/php 前缀)
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
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
```
启动后你会看到和下方类似的初始化内容,表明启动成功了
```verilog
@@ -114,17 +52,13 @@ $ vendor/bin/start server
host: 0.0.0.0 | port: 20001
log_level: 2 | version: 2.0.0
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] [#0] Worker #0
[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 ,或者你部署到了服务器后需要输入服务器地址。
!!! 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 等工具开发代码

View File

@@ -30,7 +30,103 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`
=== "config.hjson新格式)"
=== "config.yml新格式)"
```yaml hl_lines="2 3 78 83"
account: # 账号相关
uin: 1233456 # QQ账号
password: '' # 密码为空时使用扫码登录
encrypt: false # 是否开启密码加密
status: 0 # 在线状态 请参考 https://github.com/Mrs4s/go-cqhttp/blob/dev/docs/config.md#在线状态
relogin: # 重连设置
delay: 3 # 首次重连延迟, 单位秒
interval: 3 # 重连间隔
max-times: 0 # 最大重连次数, 0为无限制
# 是否使用服务器下发的新地址进行重连
# 注意, 此设置可能导致在海外服务器上连接情况更差
use-sso-address: true
heartbeat:
# 心跳频率, 单位秒
# -1 为关闭心跳
interval: 5
message:
# 上报数据类型
# 可选: string,array
post-format: string
# 是否忽略无效的CQ码, 如果为假将原样发送
ignore-invalid-cqcode: false
# 是否强制分片发送消息
# 分片发送将会带来更快的速度
# 但是兼容性会有些问题
force-fragment: false
# 是否将url分片发送
fix-url: false
# 下载图片等请求网络代理
proxy-rewrite: ''
# 是否上报自身消息
report-self-message: false
# 移除服务端的Reply附带的At
remove-reply-at: false
# 为Reply附加更多信息
extra-reply-data: false
output:
# 日志等级 trace,debug,info,warn,error
log-level: warn
# 是否启用 DEBUG
debug: false # 开启调试模式
# 默认中间件锚点
default-middlewares: &default
# 访问密钥, 强烈推荐在公网的服务器设置
access-token: ''
# 事件过滤器文件目录
filter: ''
# API限速设置
# 该设置为全局生效
# 原 cqhttp 虽然启用了 rate_limit 后缀, 但是基本没插件适配
# 目前该限速设置为令牌桶算法, 请参考:
# https://baike.baidu.com/item/%E4%BB%A4%E7%89%8C%E6%A1%B6%E7%AE%97%E6%B3%95/6597000?fr=aladdin
rate-limit:
enabled: false # 是否启用限速
frequency: 1 # 令牌回复频率, 单位秒
bucket: 1 # 令牌桶大小
database: # 数据库相关设置
leveldb:
# 是否启用内置leveldb数据库
# 启用将会增加10-20MB的内存占用和一定的磁盘空间
# 关闭将无法使用 撤回 回复 get_msg 等上下文相关功能
enable: true
# 连接服务列表
servers:
# 添加方式,同一连接方式可添加多个,具体配置说明请查看文档
#- http: # http 通信
#- ws: # 正向 Websocket
#- ws-reverse: # 反向 Websocket
#- pprof: #性能分析服务器
# Zhamao Framework 所需要的服务器配置
- ws-reverse:
# 是否禁用当前反向WS服务
disabled: false
# 反向WS Universal 地址
# 注意 设置了此项地址后下面两项将会被忽略
universal: ws://127.0.0.1:20001/
# 反向WS API 地址
api: ws://your_websocket_api.server
# 反向WS Event 地址
event: ws://your_websocket_event.server
# 重连间隔 单位毫秒
reconnect-interval: 3000
middlewares:
<<: *default # 引用默认中间件
```
=== "config.hjsonv1.0.0-beta2或更早版本所用格式"
``` json hl_lines="3 5 81 84"
{

View File

@@ -1,5 +1,76 @@
# 更新日志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)
> 更新时间2021.3.29
以下是可能不兼容的变更:
- 新增依赖:框架需要 PHP 安装 pcntl 扩展以及开启 `pcntl_signal` 函数(一般情况下编译安装的都会有,宝塔面板请手动解除函数禁用)
## v2.4.3 (build 403)
> 更新时间2021.3.29

34
ext/go-cqhttp/go-cqhttp-down.sh Executable file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
echo "正在检查最新版本的go-cqhttp..."
if [ "$(uname -m)" = "x86_64" ]; then
arch_type="amd64"
elif [ "$(uname -m)" = "i386" ]; then
arch_type="386"
elif [ "$(uname -m)" = "aarch64" ]; then
arch_type="arm64"
else
echo "Not supported architecture: $(uname -m)"
exit 1
fi
aas=$(uname -s | tr 'A-Z' 'a-z')
ver=$(wget -qO- -t1 -T2 "https://fgit-api.zhamao.me/repos/Mrs4s/go-cqhttp/releases" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
if [ "$ver" != "" ]; then
echo "最新版本:"$ver
echo -n "是否下载到本地?[y/N] "
read option
if [ "$option" = "y" ]; then
wget https://fgit.zhamao.me/Mrs4s/go-cqhttp/releases/download/$ver/go-cqhttp-$ver-$aas-$arch_type.tar.gz -O temp.tar.gz
if [ $? != 0 ]; then
wget https://fgit.zhamao.me/Mrs4s/go-cqhttp/releases/download/$ver/go-cqhttp_"$aas""_""$arch_type"".tar.gz" -O temp.tar.gz
fi
tar -zxvf temp.tar.gz go-cqhttp
rm temp.tar.gz
echo "下载完成,启动命令:./go-cqhttp"
echo "首次启动后先编辑config文件"
fi
fi

58
install-runtime.sh Executable file
View File

@@ -0,0 +1,58 @@
#!/usr/bin/env bash
function download_file() {
downloader="wget"
type wget >/dev/null 2>&1 || { downloader="curl"; }
if [ "$downloader" = "wget" ]; then
_down_prefix="O"
else
_down_prefix="o"
fi
_down_symbol=0
if [ ! -f "$2" ]; then
echo $1
$downloader "$1" -$_down_prefix "$2" >/dev/null 2>&1 && \
echo "完成!" && _down_symbol=1
else
echo "已存在!" && _down_symbol=1
fi
if [ $_down_symbol == 0 ]; then
echo "失败!请检查网络连接!"
rm -rf "$2"
return 1
fi
return 0
}
function test_composer_and_php() {
succ=$("$(pwd)/runtime/composer" -n about | grep Manage)
if [ "$succ" = "" ]; then
echo "Download PHP binary and composer failed!"
return 1
fi
return 0
}
mkdir "$(pwd)/runtime" >/dev/null 2>&1
if [ ! -f "$(pwd)/runtime/php" ]; then
download_file "https://dl.zhamao.me/php-bin/down.php?php_ver=7.4&arch=$(uname -m)" "$(pwd)/runtime/php.tar.gz"
if [ $? -ne 0 ]; then
exit 1
fi
tar -xf "$(pwd)/runtime/php.tar.gz" -C "$(pwd)/runtime/"
fi
if [ ! -f "$(pwd)/runtime/composer" ]; then
download_file "https://mirrors.aliyun.com/composer/composer.phar" "$(pwd)/runtime/composer.phar"
if [ $? -ne 0 ]; then
exit 1
fi
echo '$(dirname $0)/php $(dirname $0)/composer.phar $@' > $(pwd)/runtime/composer
chmod +x $(pwd)/runtime/composer
test_composer_and_php
fi
if [ $? -ne 0 ]; then
exit 1
fi
echo "成功下载!" && \
echo -e "PHP使用\truntime/php -v" && \
echo -e "Composer使用\truntime/composer"

View File

@@ -1,4 +1,4 @@
site_name: 炸毛框架 v2
site_name: 炸毛框架文档
repo_name: '炸毛框架'
repo_url: 'https://github.com/zhamao-robot/zhamao-framework'
@@ -32,7 +32,7 @@ markdown_extensions:
linenums_style: pymdownx.inline
extra:
version:
method: mike
provider: mike
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>
@@ -50,7 +50,7 @@ copyright: 'Copyright &copy; 2019 - 2021 CrazyBot Team&nbsp;&nbsp;&nbsp;&nbsp;&n
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:
- 指南:
@@ -74,17 +74,18 @@ nav:
- 框架组件: component/index.md
- 上下文: component/context.md
- 聊天机器人组件:
- 机器人 API: component/robot-api.md
- CQ 码(多媒体消息): component/cqcode.md
- 机器人消息处理: component/message-util.md
- Token 验证: component/access-token.md
- 机器人 API: component/bot/robot-api.md
- CQ 码(多媒体消息): component/bot/cqcode.md
- 机器人消息处理: component/bot/message-util.md
- Token 验证: component/bot/access-token.md
- 图灵机器人 API: component/bot/turing-api.md
- 存储:
- LightCache 轻量缓存: component/light-cache.md
- MySQL 数据库: component/mysql.md
- Redis 数据库: component/redis.md
- ZMAtomic 原子计数器: component/atomics.md
- SpinLock 自旋锁: component/spin-lock.md
- 文件管理: component/data-provider.md
- LightCache 轻量缓存: component/store/light-cache.md
- MySQL 数据库: component/store/mysql.md
- Redis 数据库: component/store/redis.md
- ZMAtomic 原子计数器: component/store/atomics.md
- SpinLock 自旋锁: component/store/spin-lock.md
- 文件管理: component/store/data-provider.md
- HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- HTTP 路由管理: component/route-manager.md
@@ -99,6 +100,7 @@ nav:
- 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md
- 框架启动模式: advanced/custom-start.md
- 手动安装环境: advanced/manually-install.md
- 从 v1 升级: advanced/to-v2.md
- 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
@@ -106,7 +108,12 @@ nav:
- TaskWorker 提高并发: advanced/task-worker.md
- 开发实战教程:
- 编写管理员才能触发的功能: advanced/example/admin.md
- FAQ: FAQ.md
- FAQ:
- FAQ: faq/FAQ.md
- 启动时报错 Address already in use: faq/address-already-in-use.md
- 出现 deadlock 字样: faq/display-deadlock.md
- 使用 LightCache 关闭时无法正常保存持久化: faq/light-cache-wrong.md
- CQBefore 过滤不了 waitMessage: faq/wait-message-cqbefore.md
- 更新日志:
- 更新日志v2: update/v2.md
- 更新日志v1: update/v1.md

View File

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

View File

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

View File

@@ -22,8 +22,6 @@ trait CQAPI
return $this->processWebsocketAPI($connection, $reply, $function);
else
return $this->processHttpAPI($connection, $reply, $function);
}
public function processWebsocketAPI($connection, $reply, $function = false) {
@@ -41,7 +39,7 @@ trait CQAPI
}
return true;
} else {
Console::warning("CQAPI send failed, websocket push error.");
Console::warning(zm_internal_errcode("E00036") . "CQAPI send failed, websocket push error.");
$response = [
"status" => "failed",
"retcode" => -1000,
@@ -69,7 +67,6 @@ trait CQAPI
return false;
}
/** @noinspection PhpMissingReturnTypeInspection */
public function __call($name, $arguments) {
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) {
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);
return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?";
}
$result = $api_return["results"];
//Console::info(Console::setColor(json_encode($result, 128 | 256), "green"));
$final = "";
foreach ($result as $k => $v) {
foreach ($result as $v) {
switch ($v["resultType"]) {
case "url":
$final .= "\n" . $v["values"]["url"];

View File

@@ -5,687 +5,13 @@
namespace ZM\API;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Exception\RobotNotFoundException;
/**
* Class ZMRobot
* @package ZM\Utils
* @since 1.2
* @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

@@ -12,7 +12,7 @@ abstract class AnnotationBase
public $class;
public function __toString(): string {
public function __toString() {
$str = __CLASS__ . ": ";
foreach ($this as $k => $v) {
$str .= "\n\t" . $k . " => ";

View File

@@ -9,10 +9,16 @@ use ZM\Console\Console;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\Http\{HandleAfter, HandleBefore, HandleException, Middleware, MiddlewareClass, RequestMapping};
use ZM\Annotation\Http\HandleAfter;
use ZM\Annotation\Http\HandleBefore;
use ZM\Annotation\Http\HandleException;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Http\MiddlewareClass;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed;
use ZM\Http\RouteManager;
use ZM\Utils\Manager\RouteManager;
use ZM\Utils\ZMUtil;
class AnnotationParser
{
@@ -48,7 +54,7 @@ class AnnotationParser
public function registerMods() {
foreach ($this->path_list as $path) {
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();
foreach ($all_class as $v) {
Console::debug("正在检索 " . $v);
@@ -83,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;
//预处理1将适用于每一个函数的注解到类注解重新注解到每个函数下面
@@ -125,14 +131,14 @@ class AnnotationParser
Console::debug("解析注解完毕!");
}
public function generateAnnotationEvents(): array {
public function generateAnnotationEvents() {
$o = [];
foreach ($this->annotation_map as $module => $obj) {
foreach ($this->annotation_map as $obj) {
foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
if ($class_annotation instanceof ErgodicAnnotation) continue;
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) {
$o[get_class($annotation)][] = $annotation;
}

View File

@@ -26,7 +26,7 @@ class CQAfter extends AnnotationBase implements Level
/**
* @return mixed
*/
public function getLevel(): int {
public function getLevel() {
return $this->level;
}

View File

@@ -48,6 +48,6 @@ class CQCommand extends AnnotationBase implements Level
/**
* @param int $level
*/
public function setLevel(int $level) { $this->level = $level; }
public function setLevel($level) { $this->level = $level; }
}

View File

@@ -34,7 +34,7 @@ class CQMessage extends AnnotationBase implements Level
public function getLevel(): int { return $this->level; }
public function setLevel(int $level) {
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -32,7 +32,7 @@ class CQMetaEvent extends AnnotationBase implements Level
/**
* @param int $level
*/
public function setLevel(int $level) {
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -36,7 +36,7 @@ class CQNotice extends AnnotationBase implements Level
/**
* @param int $level
*/
public function setLevel(int $level) {
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -36,7 +36,7 @@ class CQRequest extends AnnotationBase implements Level
/**
* @param int $level
*/
public function setLevel(int $level) {
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -8,5 +8,5 @@ interface Level
{
public function getLevel();
public function setLevel(int $level);
public function setLevel($level);
}

View File

@@ -43,7 +43,7 @@ abstract class OnSwooleEventBase extends AnnotationBase implements Level, Rule
/**
* @param int $level
*/
public function setLevel(int $level) {
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -9,6 +9,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Console\TermColor;
use ZM\Utils\DataProvider;
class BuildCommand extends Command
{
@@ -21,53 +22,77 @@ class BuildCommand extends Command
protected function configure() {
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
$this->setHelp("此功能将会把整个项目打包为phar");
$this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
// ...
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$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 ($target_dir === false) {
$output->writeln(TermColor::color8(31) . "Error: No such file or directory (" . __DIR__ . '/../../../resources/' . ")" . TermColor::RESET);
return Command::FAILURE;
$output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory (" . $target_dir . ")" . TermColor::RESET);
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 (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");
return Command::FAILURE;
return 1;
}
if (!is_dir($target_dir)) {
$output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET);
return Command::FAILURE;
$output->writeln(TermColor::color8(31) . zm_internal_errcode("E00039") . "Error: No such file or directory ($target_dir)" . TermColor::RESET);
return 1;
}
$filename = "server.phar";
$this->build($target_dir, $filename);
return Command::SUCCESS;
return 0;
}
private function build($target_dir, $filename) {
@unlink($target_dir . $filename);
$phar = new Phar($target_dir . $filename);
$phar->startBuffering();
$src = realpath(__DIR__ . '/../../zhamao-framework/');
$hello = file_get_contents($src . '/src/Module/Example/Hello.php');
$middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php');
unlink($src . '/src/Module/Example/Hello.php');
unlink($src . '/src/Module/Middleware/TimerMiddleware.php');
$phar->buildFromDirectory($src);
$phar->addFromString('tmp/Hello.php.bak', $hello);
$phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware);
//$phar->compressFiles(Phar::GZ);
$phar->setStub($phar->createDefaultStub('phar-starter.php'));
$allow_dir = ["bin", "config", "resources", "src", "vendor", "composer.json", "README.md", "zhamao"];
$archive_dir = DataProvider::getSourceRootDir();
$scan = scandir($archive_dir);
if ($scan[0] == ".") {
unset($scan[0], $scan[1]);
}
foreach ($scan as $v) {
if (in_array($v, $allow_dir)) {
if (is_dir($archive_dir . "/" . $v)) {
$this->addDirectory($phar, $archive_dir . "/" . $v, $v);
} elseif (is_file($archive_dir . "/" . $v)) {
$phar->addFile($archive_dir . "/" . $v, $v);
}
}
}
$phar->setStub(
"#!/usr/bin/env php\n" .
$phar->createDefaultStub(LOAD_MODE == 0 ? "src/entry.php" : "vendor/zhamao/framework/src/entry.php")
);
$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");
}
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

@@ -20,7 +20,7 @@ class CheckConfigCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output): int {
if (LOAD_MODE !== 1) {
$output->writeln("<error>仅限在Composer依赖模式中使用此命令</error>");
return Command::FAILURE;
return 1;
}
$current_cfg = getcwd() . "/config/";
$remote_cfg = include_once FRAMEWORK_ROOT_DIR . "/config/global.php";
@@ -42,7 +42,7 @@ class CheckConfigCommand extends Command
$output->writeln("<info>配置文件暂无更新!</info>");
}
return Command::SUCCESS;
return 0;
}
/**

View File

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

View File

@@ -1,9 +1,9 @@
<?php
namespace ZM\Command;
namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -17,8 +17,8 @@ class DaemonReloadCommand extends DaemonCommand
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
system("kill -USR1 " . intval($this->daemon_file["pid"]));
Process::kill(intval($this->daemon_file["pid"]), SIGUSR1);
$output->writeln("<info>成功重载!</info>");
return Command::SUCCESS;
return 0;
}
}

View File

@@ -1,9 +1,8 @@
<?php
namespace ZM\Command;
namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -25,6 +24,6 @@ class DaemonStatusCommand extends DaemonCommand
if (isset($stdout[count($stdout) - $i]))
echo $stdout[count($stdout) - $i] . PHP_EOL;
}
return Command::SUCCESS;
return 0;
}
}

View File

@@ -1,9 +1,9 @@
<?php
namespace ZM\Command;
namespace ZM\Command\Daemon;
use Symfony\Component\Console\Command\Command;
use Swoole\Process;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
@@ -18,9 +18,9 @@ class DaemonStopCommand extends DaemonCommand
protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output);
system("kill -INT " . intval($this->daemon_file["pid"]));
Process::kill(intval($this->daemon_file["pid"]), SIGINT);
unlink(DataProvider::getWorkingDir() . "/.daemon_pid");
$output->writeln("<info>成功停止!</info>");
return Command::SUCCESS;
return 0;
}
}

View File

@@ -12,6 +12,7 @@ use Symfony\Component\Console\Output\OutputInterface;
class InitCommand extends Command
{
private $extract_files = [
"/zhamao",
"/config/global.php",
"/.gitignore",
"/config/file_header.json",
@@ -44,18 +45,13 @@ class InitCommand extends Command
$info = pathinfo($file);
@mkdir($base_path . $info["dirname"], 0777, true);
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);
} else {
echo "Skipping " . $file . " , file exists." . PHP_EOL;
}
}
echo "Copying ./zhamao\n";
file_put_contents(
$base_path."/zhamao",
"#!/usr/bin/env php\n<?php require_once \"vendor/autoload.php\";(new ZM\ConsoleApplication(\"zhamao-framework\"))->initEnv()->run();"
);
chmod($base_path."/zhamao", 0755);
chmod($base_path . "/zhamao", 0755);
$autoload = [
"psr-4" => [
"Module\\" => "src/Module",
@@ -73,7 +69,7 @@ class InitCommand extends Command
foreach ($autoload["psr-4"] as $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;
}
}
@@ -82,11 +78,11 @@ class InitCommand extends Command
exec("composer dump-autoload");
echo PHP_EOL;
} else {
echo("Error occurred. Please check your updates.\n");
return Command::FAILURE;
echo(zm_internal_errcode("E00041") . "Error occurred. Please check your updates.\n");
return 1;
}
$output->writeln("<info>Done!</info>");
return Command::SUCCESS;
return 0;
} elseif (LOAD_MODE === 2) { //从phar启动的框架包初始化的模式
$phar_link = new Phar(__DIR__);
$current_dir = pathinfo($phar_link->getPath())["dirname"];
@@ -103,8 +99,8 @@ class InitCommand extends Command
}
}
}
$output->writeln("initialization must be started with composer-project mode!");
return Command::FAILURE;
$output->writeln(zm_internal_errcode("E00042") . "initialization must be started with composer-project mode!");
return 1;
}
private function getExtractFiles(): array {

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>");
return self::FAILURE;
}
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
$global = ZMConfig::get("global");
$host = $input->getOption("host") ?? $global["host"];
$port = $input->getOption("port") ?? $global["port"];
@@ -89,10 +89,10 @@ class PureHttpCommand extends Command
$server->start();
// return this if there was no problem running the command
// (it's equivalent to returning int(0))
return Command::SUCCESS;
return 0;
// or return this if some error happened during the execution
// (it's equivalent to returning int(1))
// return Command::FAILURE;
// return 1;
}
}

View File

@@ -23,6 +23,7 @@ class RunServerCommand extends Command
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
new InputOption("interact", null, null, "打开终端输入"),
new InputOption("remote-terminal", null, null, "启用远程终端配置使用global.php中的"),
new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
@@ -31,6 +32,9 @@ class RunServerCommand extends Command
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"),
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
new InputOption("disable-safe-exit", null, null, "关闭安全退出关闭后按CtrlC时直接杀死进程"),
new InputOption("preview", null, null, "只显示参数,不启动服务器"),
new InputOption("force-load-module", null, InputOption::VALUE_OPTIONAL, "强制打包状态下加载模块(使用英文逗号分割多个)")
]);
$this->setDescription("Run zhamao-framework | 启动框架");
$this->setHelp("直接运行可以启动");
@@ -40,10 +44,10 @@ class RunServerCommand extends Command
if (($opt = $input->getOption("env")) !== null) {
if (!in_array($opt, ["production", "staging", "development", ""])) {
$output->writeln("<error> \"--env\" option only accept production, development, staging and [empty] ! </error>");
return Command::FAILURE;
return 1;
}
}
(new Framework($input->getOptions()))->start();
return Command::SUCCESS;
return 0;
}
}

View File

@@ -21,7 +21,7 @@ class SystemdCommand extends Command
$path = $this->generate();
$output->writeln("<info>成功生成 systemd 文件,位置:".$path."</info>");
$output->writeln("<info>有关如何使用 systemd 配置文件,请访问 `https://github.com/zhamao-robot/zhamao-framework/issues/36`</info>");
return Command::SUCCESS;
return 0;
}
private function generate() {
@@ -29,11 +29,8 @@ class SystemdCommand extends Command
$s .= "\nUser=" . exec("whoami");
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd();
if (LOAD_MODE == 1) {
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server";
} else {
$s .= "\nExecStart=" . getcwd() . "/bin/start server";
}
global $argv;
$s .= "\nExecStart=".PHP_BINARY." {$argv[0]} server";
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s);

View File

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

View File

@@ -4,8 +4,8 @@
namespace ZM\Context;
use Co;
use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
@@ -104,8 +104,7 @@ class Context implements ContextInterface
* only can used by cq->message event function
* @param $msg
* @param bool $yield
* @return mixed
* @noinspection PhpMissingReturnTypeInspection
* @return array|bool
*/
public function reply($msg, $yield = false) {
$data = $this->getData();
@@ -136,7 +135,7 @@ class Context implements ContextInterface
/**
* @param $msg
* @param false $yield
* @return mixed|void
* @return void
* @throws InterruptException
*/
public function finalReply($msg, $yield = false) {
@@ -170,42 +169,6 @@ class Context implements ContextInterface
throw new WaitTimeoutException($this, $timeout_prompt);
}
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 */
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();
}

View File

@@ -54,9 +54,9 @@ class EventManager
try {
$dispatcher->dispatchEvent($vss, null);
} 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) {
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");
Console::error("Please check your code!");
}

View File

@@ -4,9 +4,9 @@
namespace ZM\Event\SwooleEvent;
use Co;
use Error;
use Exception;
use Swoole\Coroutine;
use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Annotation\Swoole\SwooleHandler;
@@ -27,10 +27,10 @@ class OnClose implements SwooleEvent
{
/** @noinspection PhpUnreachableStatementInspection */
public function onCall($server, $fd) {
unset(Context::$context[Co::getCid()]);
unset(Context::$context[Coroutine::getCid()]);
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
$conn = ManagerGM::get($fd);
if ($conn === null) return;
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]);
$dispatcher1 = new EventDispatcher(OnCloseEvent::class);
@@ -61,11 +61,11 @@ class OnClose implements SwooleEvent
$dispatcher->dispatchEvents($conn);
} catch (Exception $e) {
$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();
} catch (Error $e) {
$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();
}
ManagerGM::popConnect($fd);

View File

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

View File

@@ -0,0 +1,21 @@
<?php
namespace ZM\Event\SwooleEvent;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
/**
* Class OnManagerStop
* @package ZM\Event\SwooleEvent
* @SwooleHandler("ManagerStop")
*/
class OnManagerStop implements SwooleEvent
{
public function onCall() {
Console::verbose("进程 Manager 已停止!");
}
}

View File

@@ -56,11 +56,11 @@ class OnMessage implements SwooleEvent
//Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!");
} catch (Exception $e) {
$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();
} catch (Error $e) {
$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();
}

View File

@@ -5,9 +5,9 @@ namespace ZM\Event\SwooleEvent;
use Closure;
use Co;
use Error;
use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnSwooleEvent;
@@ -30,20 +30,20 @@ class OnOpen implements SwooleEvent
/** @noinspection PhpUnreachableStatementInspection */
public function onCall($server, Request $request) {
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"] ?? "");
$access_token = explode(" ", $request->header["authorization"] ?? "")[1] ?? $request->get["token"] ?? "";
$token = ZMConfig::get("global", "access_token");
if ($token instanceof Closure) {
if (!$token($access_token)) {
$server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token);
Console::warning(zm_internal_errcode("E00018") . "Unauthorized access_token: " . $access_token);
return;
}
} elseif (is_string($token)) {
if ($access_token !== $token && $token !== "") {
$server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token);
Console::warning(zm_internal_errcode("E00019") . "Unauthorized access_token: " . $access_token);
return;
}
}
@@ -81,11 +81,11 @@ class OnOpen implements SwooleEvent
$dispatcher->dispatchEvents($conn);
} catch (Exception $e) {
$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();
} catch (Error $e) {
$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();
}
}

View File

@@ -4,10 +4,13 @@
namespace ZM\Event\SwooleEvent;
use Error;
use Exception;
use Swoole\Server;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
use ZM\Utils\ProcessManager;
use ZM\Utils\Manager\ProcessManager;
/**
* Class OnPipeMessage
@@ -18,6 +21,16 @@ class OnPipeMessage implements SwooleEvent
{
public function onCall(Server $server, $src_worker_id, $data) {
$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;
use Co;
use Error;
use Exception;
use Swoole\Coroutine;
use Swoole\Http\Request;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnRequestEvent;
@@ -33,7 +33,7 @@ class OnRequest implements SwooleEvent
foreach (ZMConfig::get("global")["http_header"] as $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);
set_coroutine_params(["request" => $request, "response" => $response]);
@@ -86,11 +86,11 @@ class OnRequest implements SwooleEvent
);
if (!$response->isEnd()) {
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
$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");
} catch (Error $e) {
$response->status(500);
@@ -100,11 +100,11 @@ class OnRequest implements SwooleEvent
if (!$response->isEnd()) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
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
$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");
}
}

View File

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

View File

@@ -65,11 +65,11 @@ class OnTask implements SwooleEvent
$dispatcher->dispatchEvents();
} catch (Exception $e) {
$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();
} catch (Error $e) {
$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();
}
}

View File

@@ -12,7 +12,6 @@ use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Process;
use Swoole\Server;
use Swoole\Timer;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\OnSwooleEvent;
@@ -26,14 +25,14 @@ use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Event\SwooleEvent;
use ZM\Exception\DbException;
use ZM\Exception\ZMException;
use ZM\Framework;
use ZM\Module\QQBot;
use ZM\Store\LightCacheInside;
use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool;
use ZM\Utils\DataProvider;
use ZM\Utils\ProcessManager;
use ZM\Utils\ZMUtil;
use ZM\Utils\SignalListener;
/**
* Class OnWorkerStart
@@ -43,15 +42,11 @@ use ZM\Utils\ZMUtil;
class OnWorkerStart implements SwooleEvent
{
public function onCall(Server $server, $worker_id) {
Process::signal(SIGINT, function () use ($worker_id, $server) {
});
if (!Framework::$argv["disable-safe-exit"]) {
SignalListener::signalWorker($server, $worker_id);
}
unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) {
Process::signal(SIGUSR1, function () use ($worker_id) {
Timer::clearAll();
ProcessManager::resumeAllWorkerCoroutines();
});
zm_atomic("_#worker_" . $worker_id)->set($server->worker_pid);
if (LightCacheInside::get("wait_api", "wait_api") !== null) {
LightCacheInside::unset("wait_api", "wait_api");
@@ -59,8 +54,9 @@ class OnWorkerStart implements SwooleEvent
try {
register_shutdown_function(function () use ($server) {
$error = error_get_last();
if ($error["type"] != 0) {
Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
if (($error["type"] ?? -1) != 0) {
Console::error(zm_internal_errcode("E00027") . "Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
zm_dump($error);
}
//DataProvider::saveBuffer();
/** @var Server $server */
@@ -68,7 +64,7 @@ class OnWorkerStart implements SwooleEvent
else server()->shutdown();
});
Console::verbose("Worker #{$server->worker_id} 启动中");
Console::verbose("Worker #{$server->worker_id} starting");
Framework::$server = $server;
//ZMBuf::resetCache(); //清空变量缓存
//ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
@@ -84,15 +80,15 @@ class OnWorkerStart implements SwooleEvent
}
Console::info("新建SQL连接池中");
ob_start();
phpinfo();
phpinfo(); //这个phpinfo是有用的不能删除
$str = ob_get_clean();
$str = explode("\n", $str);
foreach ($str as $k => $v) {
foreach ($str as $v) {
$v = trim($v);
if ($v == "") continue;
if (mb_strpos($v, "API Extensions") === false) continue;
if (mb_strpos($v, "pdo_mysql") === false) {
throw new DbException("未安装 mysqlnd php-mysql扩展。");
throw new DbException(zm_internal_errcode("E00028") . "未安装 mysqlnd php-mysql扩展。");
}
}
$sql = ZMConfig::get("global", "sql_config");
@@ -112,7 +108,7 @@ class OnWorkerStart implements SwooleEvent
// 开箱即用的Redis
$redis = ZMConfig::get("global", "redis_config");
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);
}
@@ -130,14 +126,14 @@ class OnWorkerStart implements SwooleEvent
$dispatcher->dispatchEvents($server, $worker_id);
if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) Console::debug("@OnStart 执行完毕");
else Console::warning("@OnStart 执行异常!");
Console::success("Worker #" . $worker_id . " 已启动");
Console::success("Worker #" . $worker_id . " started");
} catch (Exception $e) {
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);
return;
} 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::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM);
@@ -147,14 +143,14 @@ class OnWorkerStart implements SwooleEvent
try {
Framework::$server = $server;
$this->loadAnnotations();
Console::success("TaskWorker #" . $server->worker_id . " 已启动");
Console::success("TaskWorker #" . $server->worker_id . " started");
} catch (Exception $e) {
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);
return;
} 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::log($e->getTraceAsString(), 'gray');
Process::kill($server->master_pid, SIGTERM);
@@ -186,15 +182,13 @@ class OnWorkerStart implements SwooleEvent
//加载各个模块的注解类,以及反射
Console::debug("检索Module中");
$parser = new AnnotationParser();
$path = DataProvider::getWorkingDir() . "/src/";
$dir = scandir($path);
unset($dir[0], $dir[1]);
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($dir as $v) {
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 . " to register path");
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v);
$composer = json_decode(file_get_contents(DataProvider::getSourceRootDir() . "/composer.json"), true);
foreach ($composer["autoload"]["psr-4"] as $k => $v) {
if (is_dir(DataProvider::getSourceRootDir() . "/" . $v)) {
if (in_array(trim($k, "\\") . "\\", $composer["extra"]["exclude_annotate"] ?? [])) continue;
if (trim($k, "\\") == "ZM") continue;
if (\server()->worker_id == 0) Console::verbose("Add " . $v . ":$k to register path");
$parser->addRegisterPath(DataProvider::getSourceRootDir() . "/" . $v . "/", trim($k, "\\"));
}
}
$parser->registerMods();
@@ -204,7 +198,7 @@ class OnWorkerStart implements SwooleEvent
Console::debug("加载自定义上下文中...");
$context_class = ZMConfig::get("global", "context_class");
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;
use Throwable;
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;
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
{
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,12 +3,11 @@
namespace ZM;
use Doctrine\Common\Annotations\AnnotationReader;
use Error;
use Exception;
use Swoole\Server\Port;
use ZM\Annotation\Swoole\OnSetup;
use Throwable;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
@@ -19,7 +18,6 @@ use ZM\Store\ZMAtomic;
use ZM\Utils\DataProvider;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use Swoole\Runtime;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleHandler;
@@ -45,17 +43,19 @@ class Framework
* @var array|bool|mixed|null
*/
private $server_set;
/**
* @var array
*/
private $setup_events = [];
/** @noinspection PhpUnusedParameterInspection */
public function __construct($args = []) {
$tty_width = $this->getTtyWidth();
self::$argv = $args;
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量
@@ -78,9 +78,10 @@ class Framework
]
]);
} catch (ConnectionManager\TableException $e) {
die($e->getMessage());
die(zm_internal_errcode("E00008") . $e->getMessage());
}
try {
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
self::$server,
@@ -95,13 +96,18 @@ class Framework
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
$this->loadServerEvents();
$this->parseCliArgs(self::$argv, $add_port);
if (!isset($this->server_set["max_wait_time"])) {
$this->server_set["max_wait_time"] = 5;
}
$worker = $this->server_set["worker_num"] ?? swoole_cpu_num();
define("ZM_WORKER_NUM", $worker);
ZMAtomic::init();
// 打印初始信息
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
@@ -109,10 +115,14 @@ class Framework
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
$out["log_level"] = Console::getLevel();
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
$out["master_pid"] = posix_getpid();
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (isset($this->server_set["task_worker_num"])) {
$out["task_worker"] = $this->server_set["task_worker_num"];
}
if (!isset($this->server_set["pid_file"])) {
$this->server_set["pid_file"] = ZMConfig::get("crash_dir") . ".zm.pid";
}
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
$conf = ZMConfig::get("global", "sql_config");
$out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"];
@@ -135,6 +145,10 @@ class Framework
$out["working_dir"] = DataProvider::getWorkingDir();
self::printProps($out, $tty_width, $args["log-theme"] === null);
if ($args["preview"]) {
exit();
}
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
@@ -191,11 +205,11 @@ class Framework
Terminal::executeCommand(trim($data));
} catch (Exception $e) {
$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();
} catch (Error $e) {
$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();
}
@@ -219,11 +233,11 @@ class Framework
// 注册 Swoole Server 的事件
$this->registerServerEvents();
$r = ZMConfig::get("global", "light_cache") ?? [
"size" => 1024,
"max_strlen" => 8192,
"hash_conflict_proportion" => 0.6,
"persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"),
"auto_save_interval" => 900
'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);
LightCacheInside::init();
@@ -264,15 +278,15 @@ class Framework
}, E_ALL | E_STRICT);
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage());
Console::error(zm_internal_errcode("E00010") . $e->getMessage());
Console::debug($e);
die;
}
}
private static function printMotd($tty_width) {
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
if (file_exists(DataProvider::getSourceRootDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getSourceRootDir() . "/config/motd.txt");
} else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
}
@@ -285,10 +299,27 @@ class Framework
}
public function start() {
self::$loaded_files = get_included_files();
self::$server->start();
zm_atomic("server_is_stopped")->set(1);
Console::log("zhamao-framework is stopped.");
try {
self::$loaded_files = get_included_files();
self::$server->start();
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;
}
/**
@@ -297,47 +328,31 @@ class Framework
* @throws ReflectionException
*/
private function registerServerEvents() {
$event_list = [];
$reader = new AnnotationReader();
$all = getAllClasses(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
$all = ZMUtil::getClassesPsr4(FRAMEWORK_ROOT_DIR . "/src/ZM/Event/SwooleEvent/", "ZM\\Event\\SwooleEvent");
foreach ($all as $v) {
$class = new $v();
$reflection_class = new ReflectionClass($class);
$anno_class = $reader->getClassAnnotation($reflection_class, SwooleHandler::class);
if ($anno_class !== null) { // 类名形式的注解
$anno_class->class = $v;
$anno_class->method = "onCall";
$event_list[strtolower($anno_class->event)] = $anno_class;
$this->setup_events["event"][] = [
"class" => $v,
"method" => "onCall",
"event" => $anno_class->event
];
}
}
$all_event_class = ZMConfig::get("global", "server_event_handler_class") ?? [];
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) {
$annotation->class = $v;
$annotation->method = $vs->getName();
$event_list[strtolower($annotation->event)] = $annotation;
} elseif ($annotation instanceof OnSetup) {
$annotation->class = $v;
$annotation->method = $vs->getName();
$c = new $v();
$m = $annotation->method;
$c->$m();
}
}
}
foreach (($this->setup_events["setup"] ?? []) as $v) {
$c = ZMUtil::getModInstance($v["class"]);
$method = $v["method"];
$c->$method();
}
foreach ($event_list as $k => $v) {
$c = ZMUtil::getModInstance($v->class);
$m = $v->method;
self::$server->on($k, function (...$param) use ($c, $m) { $c->$m(...$param); });
foreach ($this->setup_events["event"] as $v) {
self::$server->on($v["event"], function (...$param) use ($v) {
ZMUtil::getModInstance($v["class"])->{$v["method"]}(...$param);
});
}
}
@@ -357,7 +372,7 @@ class Framework
if (intval($y) >= 1 && intval($y) <= 1024) {
$this->server_set["worker_num"] = intval($y);
} 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;
case 'task-worker-num':
@@ -365,7 +380,7 @@ class Framework
$this->server_set["task_worker_num"] = intval($y);
$this->server_set["task_enable_coroutine"] = true;
} 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;
case 'disable-coroutine':
@@ -374,7 +389,8 @@ class Framework
case 'debug-mode':
$coroutine_mode = false;
$terminal_id = null;
Console::warning("You are in debug mode, do not use in production!");
self::$argv["watch"] = true;
echo("* You are in debug mode, do not use in production!\n");
break;
case 'daemon':
$this->server_set["daemonize"] = 1;
@@ -416,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);
}
@@ -507,7 +524,9 @@ class Framework
}
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() {

View File

@@ -3,58 +3,12 @@
namespace ZM\Http;
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\Store\LightCacheInside;
class RouteManager
/**
* 为了兼容豁出去了2.5),此兼容类
* Class RouteManager
* @package ZM\Http
* @deprecated
*/
class RouteManager extends \ZM\Utils\Manager\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\Exception\ZMException;
use ZM\Framework;
use ZM\Utils\ProcessManager;
use ZM\Utils\Manager\ProcessManager;
class LightCache
{
@@ -26,7 +26,6 @@ class LightCache
* @param $config
* @return bool|mixed
* @throws Exception
* @noinspection PhpMissingReturnTypeInspection
*/
public static function init($config) {
self::$config = $config;
@@ -44,17 +43,17 @@ class LightCache
$write = self::set($k, $v);
Console::verbose("Writing LightCache: " . $k);
if ($write === false) {
self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
self::$last_error = zm_internal_errcode("E00051") . '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
return false;
}
}
}
}
if ($result === false) {
self::$last_error = '系统内存不足,申请失败';
self::$last_error = zm_internal_errcode("E00050") . '系统内存不足,申请失败';
} else {
$obj = Framework::loadFrameworkState();
foreach(($obj["expiring_light_cache"] ?? []) as $k => $v) {
foreach (($obj["expiring_light_cache"] ?? []) as $k => $v) {
$value = $v["value"];
if (is_array($value)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
@@ -87,7 +86,7 @@ class LightCache
* @throws ZMException
*/
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);
$r = self::$kv_table->get($key);
return $r === false ? null : self::parseGet($r);
@@ -99,7 +98,7 @@ class LightCache
* @throws ZMException
*/
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);
$r = self::$kv_table->get($key, "expire");
return $r === false ? null : $r - time();
@@ -112,7 +111,7 @@ class LightCache
* @since 2.4.3
*/
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);
$r = self::$kv_table->get($key, "expire");
return $r === false ? null : $r;
@@ -124,10 +123,9 @@ class LightCache
* @param int $expire
* @return mixed
* @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/
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)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
if (strlen($value) >= self::$config["max_strlen"]) return false;
@@ -140,7 +138,7 @@ class LightCache
$data_type = "bool";
$value = json_encode($value);
} 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 {
return self::$kv_table->set($key, [
@@ -158,10 +156,9 @@ class LightCache
* @param $value
* @return bool|mixed
* @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/
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)) {
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
if (strlen($value) >= self::$config["max_strlen"]) return false;
@@ -171,7 +168,7 @@ class LightCache
} elseif (is_int($value)) {
$data_type = "int";
} 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 {
if (self::$kv_table->get($key) === false) return false;
@@ -256,7 +253,11 @@ class LightCache
if (self::$kv_table === null) return;
if (!empty(self::$config["persistence_path"])) {
$r = json_decode(file_get_contents(self::$config["persistence_path"]), true);
if (file_exists(self::$config["persistence_path"])) {
$r = json_decode(file_get_contents(self::$config["persistence_path"]), true);
} else {
$r = [];
}
if ($r === null) $r = [];
foreach ($r as $k => $v) {
Console::verbose("Saving " . $k);

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,6 +38,10 @@ class CoroutinePool
self::$sizes[$name] = $size;
}
public static function getRunningCoroutineCount($name = "default") {
return count(self::$cids[$name]);
}
private static function checkCids($name) {
if (in_array(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 = [];
/**
* 返回资源目录
* @return string
*/
public static function getResourceFolder(): string {
return self::getWorkingDir() . '/resources/';
}
/**
* 返回工作目录,不带最右边文件夹的斜杠(/
* @return false|string
*/
public static function getWorkingDir() {
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() {
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;
}
/**
* 将变量保存在zm_data下的数据目录传入数组
* @param $filename
* @param $file_array
* @return false|int
*/
public static function saveToJson($filename, $file_array) {
$path = ZMConfig::get("global", "config_dir");
$r = explode("/", $filename);
if(count($r) == 2) {
$path = $path . $r[0]."/";
if(!is_dir($path)) mkdir($path);
if (count($r) == 2) {
$path = $path . $r[0] . "/";
if (!is_dir($path)) mkdir($path);
$name = $r[1];
} elseif (count($r) != 1) {
Console::warning("存储失败,文件名只能有一级目录");
Console::warning(zm_internal_errcode("E00057") . "存储失败,文件名只能有一级目录");
return false;
} else {
$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) {
$path = ZMConfig::get("global", "config_dir");
if(file_exists($path.$filename.".json")) {
return json_decode(file_get_contents($path.$filename.".json"), true);
if (file_exists($path . $filename . ".json")) {
return json_decode(file_get_contents($path . $filename . ".json"), true);
} else {
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;
use Co;
use Swoole\Coroutine;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Matcher\UrlMatcher;
@@ -13,7 +13,7 @@ use Symfony\Component\Routing\RouteCollection;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Http\Response;
use ZM\Http\RouteManager;
use ZM\Utils\Manager\RouteManager;
class HttpUtil
{
@@ -51,7 +51,7 @@ class HttpUtil
public static function getHttpCodePage(int $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;
}

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 */
namespace ZM\Utils;
namespace ZM\Utils\Manager;
use Co;
use Swoole\Coroutine;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Console\Console;
@@ -38,7 +38,7 @@ class ProcessManager
break;
case "resume_ws_message":
$obj = $data["data"];
Co::resume($obj["coroutine"]);
Coroutine::resume($obj["coroutine"]);
break;
case "getWorkerCache":
$r = WorkerCache::get($data["key"]);
@@ -99,7 +99,7 @@ class ProcessManager
public static function sendActionToWorker($worker_id, $action, $data) {
$obj = ["action" => $action, "data" => $data];
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;
}
if (server()->worker_id == $worker_id) {
@@ -114,9 +114,9 @@ class ProcessManager
Console::warning("Cannot call '" . __FUNCTION__ . "' in non-worker process!");
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 (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 */
namespace ZM\Utils;
namespace ZM\Utils\Manager;
use ZM\Console\Console;
@@ -9,15 +9,14 @@ use ZM\Console\Console;
class TaskManager
{
/**
* @noinspection PhpMissingReturnTypeInspection
* @param $task_name
* @param int $timeout
* @param mixed ...$params
* @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"])) {
Console::warning("未开启 TaskWorker 进程,请先修改 global 配置文件启用!");
Console::warning(zm_internal_errcode("E00056") . "未开启 TaskWorker 进程,请先修改 global 配置文件启用!");
return false;
}
$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\Event\EventManager;
use ZM\Requests\ZMRequest;
use ZM\Utils\Manager\ProcessManager;
class MessageUtil
{
@@ -25,7 +26,7 @@ class MessageUtil
if (!is_dir($path)) mkdir($path);
$path = realpath($path);
if ($path === false) {
Console::warning("指定的路径错误不存在!");
Console::warning(zm_internal_errcode("E00059") . "指定的路径错误不存在!");
return false;
}
$files = [];
@@ -34,7 +35,7 @@ class MessageUtil
if ($v->type == "image") {
$result = ZMRequest::downloadFile($v->params["url"], $path . "/" . $v->params["file"]);
if ($result === false) {
Console::warning("图片 " . $v->params["url"] . " 下载失败!");
Console::warning(zm_internal_errcode("E00060") . "图片 " . $v->params["url"] . " 下载失败!");
return false;
}
$files[] = $path . "/" . $v->params["file"];
@@ -112,7 +113,7 @@ class MessageUtil
$ls = EventManager::$events[CQCommand::class] ?? [];
$word = self::splitCommand($msg);
$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;
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))) &&

Some files were not shown because too many files have changed in this diff Show More