Compare commits

...

55 Commits
2.2.5 ... 2.3.2

Author SHA1 Message Date
Whale
c20e3324d4 Merge pull request #35 from zhamao-robot/2.3.x
2.3.x
2021-03-23 13:28:18 +08:00
303f44cd2b update to version 2.3.2 (build 395)
fix overflow bug
2021-03-23 13:11:59 +08:00
66b73973b4 update to version 2.3.2 (build 394)
fix mysql error bugs
2021-03-23 12:47:00 +08:00
e77b9d4970 update to 2.3.1 version (build 388)
cleanup code and fix a bug
2021-03-18 14:56:35 +08:00
jerry
456b102c15 update docs 2021-03-16 01:39:55 +08:00
jerry
cc57997abc update to build 387 2021-03-16 01:35:01 +08:00
jerry
19e61c7cc3 update to build 386
fix ZM_DATA equals null
add containsImage, getImageCQFromLocal function for MessageUtil
2021-03-16 01:34:17 +08:00
jerry
f908513dca update to build 385
add CQObject for CQ
add after-stop action(set terminal level 0)
update global.php modules, add http_proxy_server
add MessageUtil.php for message parsing
add RouteManager::addStaticFileRoute() for quick handling static file
finish onTask function finally!!
add TaskManager::runTask()
2021-03-15 02:54:16 +08:00
jerry
7dc39e6ada update to 2.2.11 verion
add build version (start from 384)
make 启动中 log as verbose
remove console input
fix pure http server bug
add error handler for zm_timer_tick and zm_timer_after
2021-03-13 15:16:10 +08:00
jerry
b0be53554d Merge remote-tracking branch 'origin/master' 2021-03-13 03:03:14 +08:00
jerry
b98048bd39 update docs 2021-03-13 03:03:01 +08:00
Whale
fffd3fdc95 Update README.md 2021-03-12 19:45:53 +08:00
jerry
dea9ed2ccd update docs 2021-03-08 00:56:35 +08:00
jerry
de5744c9e4 update to 2.2.10 version
add build-runtime.sh
remove debug msg when stopping
add --show-php-ver argument for server
2021-03-08 00:48:51 +08:00
jerry
a23f3d8f16 update to 2.2.9 version
update reply() to support quick operation
fix reload bug
fix reply() bug
2021-03-06 17:22:42 +08:00
jerry
0c24bfdedd fix a motd bug 2021-03-02 21:31:06 +08:00
jerry
c0b95c6840 delete workflows 2021-03-02 21:27:04 +08:00
jerry
e977b09e20 Merge remote-tracking branch 'origin/master' 2021-03-02 21:24:53 +08:00
jerry
4ff75cf199 update to 2.2.8 version
update motd message
2021-03-02 21:24:31 +08:00
Whale
24e70c70ce Update deploy-docs.yml 2021-03-02 14:27:55 +08:00
Whale
275a7bf00b Update deploy-docs.yml 2021-03-02 14:26:02 +08:00
Whale
455fc79818 Update deploy-docs.yml 2021-03-02 14:22:40 +08:00
Whale
8740c3c255 Update deploy-docs.yml 2021-03-02 14:19:51 +08:00
Whale
98bfca5bb9 Update deploy-docs.yml 2021-03-02 14:18:40 +08:00
Whale
fc8d01ad5f Update deploy-docs.yml 2021-03-02 13:53:12 +08:00
Whale
d9b8df1725 Update deploy-docs.yml 2021-03-02 13:50:52 +08:00
Whale
9b7802ac04 Update deploy-docs.yml 2021-03-02 13:50:39 +08:00
Whale
6e1f3e0406 Update deploy-docs.yml 2021-03-02 13:43:45 +08:00
Whale
a2d4bab062 Update index.md 2021-03-02 13:40:21 +08:00
Whale
f1cefad910 Create deploy-docs.yml 2021-03-02 13:37:07 +08:00
jerry
957c69bd1e update to 2.2.7 version
fix reply() bug
fix access_token bug
2021-02-27 16:19:18 +08:00
Whale
2902c5e805 Merge pull request #33 from YiwanGi/master
Update ServerEventHandler.php
2021-02-27 16:13:46 +08:00
Wang
faf9f5d988 Update ServerEventHandler.php
-When the token is incorrect, repeated connection events occur in the framework
2021-02-27 00:04:02 +08:00
Whale
874f061468 Update README.md 2021-02-26 11:05:04 +08:00
jerry
69521a1f1f cleanup code, update some features
add Hitokoto API
add Closure for access_token
add working_dir() global function
adjust reply() method to .handle_quick_operation
2021-02-24 23:37:00 +08:00
Whale
fb9dbed306 Merge pull request #32 from wen1014/master
warning bug fix
2021-02-23 23:24:45 +08:00
Whale
d42158ac90 Merge branch 'master' into master 2021-02-23 23:24:24 +08:00
Whale
ff3ebec562 Merge pull request #31 from YiwanGi/patch-8
Update spin-lock.md
2021-02-23 23:22:01 +08:00
wenhao
ea79de617e warning bug fix 2021-02-23 17:04:10 +08:00
Wang
9e1ad6a983 Update spin-lock.md
-Forgotten data content
2021-02-22 19:34:06 +08:00
Whale
c17248df31 Merge pull request #30 from YiwanGi/patch-7
Update light-cache.md
2021-02-22 11:33:52 +08:00
Whale
4c116ebd86 Merge pull request #29 from YiwanGi/patch-5
Update console.md
2021-02-22 11:33:29 +08:00
Whale
c490fe0c1c Merge pull request #28 from YiwanGi/patch-6
Update route-annotations.md
2021-02-22 11:32:46 +08:00
Whale
cefdf23799 Merge pull request #27 from YiwanGi/patch-4
Update README.md
2021-02-22 11:32:06 +08:00
Wang
7f70642606 Update light-cache.md
-Follow up the latest configuration data
2021-02-22 02:51:35 +08:00
Wang
1d5b2609f9 Update console.md 2021-02-22 02:03:22 +08:00
Wang
a206fe8b87 Update route-annotations.md
-Correction of typos
2021-02-22 01:18:12 +08:00
Wang
fb4f6c45ce Update README.md
-Detail optimization
2021-02-22 01:07:35 +08:00
jerry
c50ae245bd commitment, nothing 2021-02-21 22:17:34 +08:00
Whale
f6c2131ebf Merge pull request #26 from YiwanGi/patch-3
Update README.md
2021-02-21 22:15:39 +08:00
Whale
543d1d2922 Merge pull request #25 from YiwanGi/patch-2
Update basic-config.md
2021-02-21 22:14:27 +08:00
Whale
bb61e6f6a2 Merge pull request #24 from YiwanGi/patch-1
Update quickstart-robot.md
2021-02-21 22:13:06 +08:00
YiwanGi
2d1bbf6b48 Update README.md
-Adjust the display format appropriately
-Solve the problem of no access to images in China
2021-02-21 12:48:54 +08:00
YiwanGi
67e42cfe3e Update basic-config.md
-Better display
2021-02-21 11:28:01 +08:00
YiwanGi
429a2cf230 Update quickstart-robot.md
-Better display
2021-02-21 10:48:23 +08:00
87 changed files with 1411 additions and 1040 deletions

View File

@@ -1,5 +0,0 @@
#!/bin/bash
if [ ! -d "/app/zhamao-framework/bin" ]; then
cp -r /app/zhamao-framework-bak/* /app/zhamao-framework/
fi
php /app/zhamao-framework/bin/start

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ composer.lock
/bin/.phpunit.result.cache /bin/.phpunit.result.cache
/resources/zhamao.service /resources/zhamao.service
.phpunit.result.cache .phpunit.result.cache
.daemon_pid .daemon_pid
/runtime/

View File

@@ -1,12 +1,12 @@
<div align="center"> <div align="center">
<img src="/resources/images/logo_trans.png" height = "150" alt="炸毛框架"><br> <img src="https://cdn.jsdelivr.net/gh/zhamao-robot/zhamao-framework/resources/images/logo_trans.png" width = "150" height = "150" alt="炸毛框架"><br>
<h2>炸毛框架</h2> <h2>炸毛框架</h2>
炸毛框架 (zhamao-framework) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br> 炸毛框架 (zhamao-framework) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br>
[![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]() [![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)](http://wpa.qq.com/msgrd?v=3&uin=627577391&site=qq&menu=yes)
[![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE) [![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
[![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework) [![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework)
[![Banner](https://img.shields.io/badge/OneBot-v11-black)]() [![Banner](https://img.shields.io/badge/OneBot-v11-success)](https://github.com/howmanybots/onebot)
[![注解数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/AnnotationBase.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=AnnotationBase) [![注解数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/AnnotationBase.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=AnnotationBase)
[![TODO 数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO) [![TODO 数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
@@ -14,7 +14,7 @@
</div> </div>
## 开发者注意 ## 开发者注意
开发者 QQ 群:**670821194** 开发者 QQ 群:**670821194** [点击加入群聊](https://jq.qq.com/?_wv=1027&k=YkNI3AIr)
当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支! 当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!
@@ -44,9 +44,9 @@ public function index() {
框架首先需要部署环境,可以参考下方文档中部署环境和框架的方法进行。 框架首先需要部署环境,可以参考下方文档中部署环境和框架的方法进行。
## 文档v2 版本) ## 文档v2 版本)
查看文档[https://docs-v2.zhamao.xin/](https://docs-v2.zhamao.xin/) 查看文档(国内自建):<https://docs-v2.zhamao.xin/>
备用链接[https://docs-v2.zhamao.me/](https://docs-v2.zhamao.me/) 备用链接(国外托管):<https://docs-v2.zhamao.me/>
自行构建文档:`mkdocs build -d distribute` 自行构建文档:`mkdocs build -d distribute`
@@ -66,6 +66,11 @@ public function index() {
如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。 如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。
## 下载源码
框架源码可直接克隆本仓库进行编辑,如果你在国内,访问 GitHub 和 clone 仓库比较慢,可以将 `github.com` 替换为 `fgit.zhamao.me` 进行加速。
例如:`git clone https://fgit.zhamao.me/zhamao-robot/zhamao-framework.git`
## 贡献和捐赠 ## 贡献和捐赠
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。 如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。
@@ -76,7 +81,7 @@ public function index() {
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。[捐赠列表](https://github.com/zhamao-robot/thanks) 我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。[捐赠列表](https://github.com/zhamao-robot/thanks)
### 支付宝 ### 支付宝
![支付宝二维码](/resources/images/alipay_img.jpg) ![支付宝二维码](https://cdn.jsdelivr.net/gh/zhamao-robot/zhamao-framework/resources/images/alipay_img.jpg)
如果你对我们的周边感兴趣,我们还有炸毛机器人定制 logo 的雨伞,详情咨询作者 QQ我们会作为您捐助了本项目 如果你对我们的周边感兴趣,我们还有炸毛机器人定制 logo 的雨伞,详情咨询作者 QQ我们会作为您捐助了本项目
@@ -85,7 +90,7 @@ public function index() {
作者的炸毛机器人已从2018年初起稳定运行了**三年**,并且持续迭代。 作者的炸毛机器人已从2018年初起稳定运行了**三年**,并且持续迭代。
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ627577391或提交 Issue 进行疑难解答。 欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ[627577391](http://wpa.qq.com/msgrd?v=3&uin=627577391&site=qq&menu=yes))或提交 Issue 进行疑难解答。
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。 本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。

View File

@@ -1,5 +1,5 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php /** @noinspection ALL */<?php
/** /**
* Copyright: Swlib * Copyright: Swlib
* Author: Twosee <twose@qq.com> * Author: Twosee <twose@qq.com>
@@ -52,6 +52,7 @@ if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
} }
} }
} }
/** @noinspection PhpIncludeInspection */
require PHPUNIT_COMPOSER_INSTALL; require PHPUNIT_COMPOSER_INSTALL;
$starttime = microtime(true); $starttime = microtime(true);
go(function (){ go(function (){

View File

@@ -1,14 +1,6 @@
#!/usr/bin/env php #!/usr/bin/env php
<?php <?php /** @noinspection PhpIncludeInspection */
if (!is_dir(__DIR__ . '/../vendor')) { require_once ((!is_dir(__DIR__ . '/../vendor')) ? getcwd() : (__DIR__ . "/..")) . "/vendor/autoload.php";
define("LOAD_MODE", 1); //composer项目模式
define("LOAD_MODE_COMPOSER_PATH", getcwd());
/** @noinspection PhpIncludeInspection */
require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php";
} else {
define("LOAD_MODE", 0); //源码模式
require_once __DIR__ . "/../vendor/autoload.php";
}
(new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run(); (new ZM\ConsoleApplication("zhamao-framework"))->initEnv()->run();

View File

@@ -21,9 +21,9 @@ function generate($argv) {
$s .= "\nGroup=" . exec("groups | awk '{print $1}'"); $s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd(); $s .= "\nWorkingDirectory=" . getcwd();
if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd')) if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd'))
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server --disable-console-input"; $s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server";
else else
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input"; $s .= "\nExecStart=" . getcwd() . "/bin/start server";
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n"; $s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/"); @mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s); file_put_contents(getcwd() . "/resources/zhamao.service", $s);

226
build-runtime.sh Executable file
View File

@@ -0,0 +1,226 @@
#!/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

@@ -1,9 +1,8 @@
{ {
"name": "zhamao/framework", "name": "zhamao/framework",
"description": "High performance QQ robot and web server development framework", "description": "High performance chat robot and web server development framework",
"minimum-stability": "stable", "minimum-stability": "stable",
"license": "Apache-2.0", "license": "Apache-2.0",
"version": "2.2.5",
"extra": { "extra": {
"exclude_annotate": [ "exclude_annotate": [
"src/ZM" "src/ZM"
@@ -11,12 +10,8 @@
}, },
"authors": [ "authors": [
{ {
"name": "whale", "name": "jerry",
"email": "crazysnowcc@gmail.com" "email": "admin@zhamao.me"
},
{
"name": "swift",
"email": "hugo_swift@yahoo.com"
} }
], ],
"prefer-stable": true, "prefer-stable": true,
@@ -26,19 +21,18 @@
], ],
"require": { "require": {
"php": ">=7.2", "php": ">=7.2",
"doctrine/annotations": "~1.10",
"ext-json": "*", "ext-json": "*",
"ext-posix": "*",
"doctrine/annotations": "~1.10",
"psy/psysh": "@stable", "psy/psysh": "@stable",
"symfony/polyfill-ctype": "^1.20", "symfony/polyfill-ctype": "^1.20",
"symfony/polyfill-mbstring": "^1.20", "symfony/polyfill-mbstring": "^1.20",
"symfony/console": "^5.1", "symfony/console": "^5.1",
"symfony/routing": "^5.1",
"zhamao/connection-manager": "*@dev", "zhamao/connection-manager": "*@dev",
"zhamao/console": "^1.0", "zhamao/console": "^1.0",
"zhamao/config": "^1.0", "zhamao/config": "^1.0",
"zhamao/request": "*@dev", "zhamao/request": "*@dev"
"symfony/routing": "^5.1",
"symfony/polyfill-php80": "^1.20",
"ext-posix": "*"
}, },
"suggest": { "suggest": {
"ext-ctype": "*", "ext-ctype": "*",
@@ -53,7 +47,6 @@
] ]
}, },
"require-dev": { "require-dev": {
"swoole/ide-helper": "@dev", "swoole/ide-helper": "@dev"
"phpunit/phpunit": "^9.5"
} }
} }

View File

@@ -27,7 +27,7 @@ $config['crash_dir'] = $config['zm_data'] . 'crash/';
/** 对应swoole的server->set参数 */ /** 对应swoole的server->set参数 */
$config['swoole'] = [ $config['swoole'] = [
'log_file' => $config['crash_dir'] . 'swoole_error.log', 'log_file' => $config['crash_dir'] . 'swoole_error.log',
'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量 //'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算则可把这里改为1使用全局变量
'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode 'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
'max_coroutine' => 300000, 'max_coroutine' => 300000,
//'task_worker_num' => 4, //'task_worker_num' => 4,
@@ -109,15 +109,25 @@ $config['static_file_server'] = [
/** 注册 Swoole Server 事件注解的类列表 */ /** 注册 Swoole Server 事件注解的类列表 */
$config['server_event_handler_class'] = [ $config['server_event_handler_class'] = [
\ZM\Event\ServerEventHandler::class, // 这里添加例如 \ZM\Event\ServerEventHandler::class 这样的启动注解类
]; ];
/** 服务器启用的外部第三方和内部插件 */ /** 服务器启用的外部第三方和内部插件 */
$config['modules'] = [ $config['modules'] = [
'onebot' => [ 'onebot' => [ // 机器人解析模块,关闭后无法使用如@CQCommand等注解
'status' => true, 'status' => true,
'single_bot_mode' => false 'single_bot_mode' => false
], // QQ机器人事件解析器如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ],
'http_proxy_server' => [ // 一个内置的简单HTTP代理服务器目前还没有认证功能预计2.4.0版本完成
'status' => false,
'host' => '0.0.0.0',
'port' => 8083,
'swoole_set_override' => [
'backlog' => 128,
'buffer_output_size' => 1024 * 1024 * 128,
'socket_buffer_size' => 1024 * 1024 * 1
]
],
]; ];
return $config; return $config;

View File

@@ -1,6 +1,6 @@
______ ______
|__ / |__ __ _ _ __ ___ __ _ ___ |__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \ / /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) | / /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/ /____|_| |_|\__,_|_| |_| |_|\__,_|\___/

View File

@@ -1 +1,3 @@
# FAQ # FAQ
这里会写一些常见的疑难解答。

View File

@@ -85,7 +85,6 @@ bin/start server # 通过源码模式启动框架
- `--debug-mode`:启用调试模式,调试模式的作用是关闭一键协程化和终端交互,减少 Swoole 本身对代码逻辑的干扰(比如执行 `shell_exec()` 报错的话可以开启这个进行调试)。 - `--debug-mode`:启用调试模式,调试模式的作用是关闭一键协程化和终端交互,减少 Swoole 本身对代码逻辑的干扰(比如执行 `shell_exec()` 报错的话可以开启这个进行调试)。
- `--log-{mode}`:设置 log 等级。支持 `--log-debug``--log-verbose``--log-info``--log-warning``--log-error` - `--log-{mode}`:设置 log 等级。支持 `--log-debug``--log-verbose``--log-info``--log-warning``--log-error`
- `--log-theme`:设置终端信息的主题。这个选项适用于多种终端信息显示的兼容,例如白色终端和不支持颜色的终端。详见 [Console - 主题设置](/component/console/#_2)。 - `--log-theme`:设置终端信息的主题。这个选项适用于多种终端信息显示的兼容,例如白色终端和不支持颜色的终端。详见 [Console - 主题设置](/component/console/#_2)。
- `--disable-console-input`:关闭终端交互,如果你使用的不是 tmux、screen 而是直接将进程使用 systemd 等方式运行到 init 守护进程下,则需要关闭终端交互输入,关闭后不可以使用 `stop, reload, logtest` 等交互命令。
- `--disable-coroutine`:关闭一键协程化。 - `--disable-coroutine`:关闭一键协程化。
- `--daemon`:以守护进程方式运行框架,此参数将直接在输出 motd 后将进程挂到 init 下运行,后台常驻。 - `--daemon`:以守护进程方式运行框架,此参数将直接在输出 motd 后将进程挂到 init 下运行,后台常驻。
- `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展inotify。使用 pecl 就可以安装:`pecl install inotify` - `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展inotify。使用 pecl 就可以安装:`pecl install inotify`

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
# Token 验证
为了保障安全,框架支持给接入的 WebSocket 连接验证 Token如果不设置 Token 同时又将框架的端口暴露在公网将会非常危险。
炸毛框架兼容 OneBot 标准的机器人客户端,所以自带一个 Token 验证器。
关于 Access Token 方面的标准规范,请参考下面内容:
- [OneBot - 鉴权](https://github.com/howmanybots/onebot/blob/master/v11/specs/communication/authorization.md)
- [go-cqhttp - 配置](https://github.com/Mrs4s/go-cqhttp/blob/master/docs/config.md)
> 以 go-cqhttp 举例,如果要设置验证,则将 go-cqhttp 配置文件中的 `access_token` 项填入内容即可。
## 验证位置
框架对 Token 的验证是内置的,在事件 `open`WebSocket 连接接入时)触发。
如果是兼容 OneBot 标准的客户端接入,则一切都是兼容的。
如果是自定义的其他 WebSocket 客户端也想接入框架,那么其他 WebSocket 客户端也需要进行相应的设置才能利用此 Token 验证。
如果验证成功Token 符合要求)则分发事件 `@OnOpenEvent`,否则此事件不触发,同时断开 WebSocket 连接。
## 标准验证(字符串形式)
默认的情况下,在框架的全局配置文件 `global.php` 中,对配置项 `access_token` 填入与 OneBot 客户端相同的 `access_token` 即可实现鉴权。下面是一个最基本的和 go-cqhttp 设置鉴权配置:
go-cqhttp 的配置段:
```hjson
// 访问密钥, 强烈推荐在公网的服务器设置
access_token: "emhhbWFvLXJvYm90"
```
框架的配置文件配置段:
```php
/** onebot连接约定的token */
$config["access_token"] = 'emhhbWFvLXJvYm90';
```
然后重启框架和 go-cqhttp 即可。(其他 OneBot 客户端同理)
## 自定义验证Token 验证)
有些情况下,使用一个单一的字符串可能无法满足你对 Token 验证的安全需求,需要自定义一些判断模式才能满足,所以框架的 `access_token` 配置项支持动态的闭包函数自行编写判断逻辑,例如下面的一个例子,我可以让框架同时允许接入多个不同 token 的 WebSocket 连接:
```php
/** onebot连接约定的token */
$config["access_token"] = function($token){
$allow = ['emhhbWFvLXJvYm90','aXMtdmVyeS1nb29k'];
if (in_array($token, $allow)) return true;
else return false;
};
```
## 自定义验证open 事件)
当然,这里设置了自定义方式,其实你也可以在下一层的 `@OnOpenEvent` 注解事件中进行自定义内容和判断,具体见 `@OnOpenEvent` 的相关章节。

View File

@@ -25,7 +25,7 @@ vendor/bin/start server --log-error # 以 error 等级启动框架
vendor/bin/start server --log-warning # 以 warning 等级启动框架 vendor/bin/start server --log-warning # 以 warning 等级启动框架
vendor/bin/start server --log-info # 以 info 等级启动框架 vendor/bin/start server --log-info # 以 info 等级启动框架
vendor/bin/start server --log-verbose # 以 verbose 等级启动框架 vendor/bin/start server --log-verbose # 以 verbose 等级启动框架
vendor/bin/start server --log-debug # 以 debug 等级 启动框架 vendor/bin/start server --log-debug # 以 debug 等级启动框架
``` ```
## 使用 Log 输出内容 ## 使用 Log 输出内容
@@ -100,11 +100,9 @@ $str = Console::setColor("I am gold color.", "gold");
炸毛框架支持从终端输入命令来进行一些操作,例如重启框架、停止框架、执行函数等。 炸毛框架支持从终端输入命令来进行一些操作,例如重启框架、停止框架、执行函数等。
::: warning 注意 !!! warning 注意
在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。 在 Docker、systemd、daemon 状态下启动的框架会自动关闭终端等待输入,交互不可用。
:::
### reload ### reload
@@ -160,6 +158,8 @@ color green 我是绿色的字
文件位置:`config/motd.txt` 文件位置:`config/motd.txt`
其中,默认的 `Zhamao` 字样的 MOTD 是使用 **figlet** 命令生成的,`figlet "Zhamao"`,你也可以针对自己的机器人名称或品牌进行生成。
## 设置输出主题 ## 设置输出主题
Console 组件支持为多种不同的终端设置不同的主题,比如有些人喜欢使用白色的终端,但是白色终端下 info 的颜色很浅,看不到,还有人使用不能显示颜色的黑白终端..... Console 组件支持为多种不同的终端设置不同的主题,比如有些人喜欢使用白色的终端,但是白色终端下 info 的颜色很浅,看不到,还有人使用不能显示颜色的黑白终端.....

View File

@@ -51,7 +51,7 @@ public function hello() {
* @CQCommand("测试fd") * @CQCommand("测试fd")
*/ */
public function testfd() { public function testfd() {
ctx()->reply("当前机器人连接的fd是".ctx()->getFd()"机器人QQ是".ctx()->getRobotId()); ctx()->reply("当前机器人连接的fd是".ctx()->getFd()."机器人QQ是".ctx()->getRobotId());
} }
``` ```
@@ -421,4 +421,5 @@ public function argTest1() {
<chat-box> <chat-box>
) test abc 334 argtest ) test abc 334 argtest
( 参数内容abc, 334, argtest ( 参数内容abc, 334, argtest
</chat-box> </chat-box>

View File

@@ -128,8 +128,9 @@ $str = CQ::removeCQ("[CQ:at,qq=all]这是带表情的全体消息[CQ:face,id=8]"
解析 CQ 码。 解析 CQ 码。
- 参数`getCQ($msg);`:要解析出 CQ 码的消息。 - 定义`getCQ($msg, $is_object = false)`
- 返回:`数组 | null`,见下表 - 参数 `$is_object` 为 true 时,返回一个 `\ZM\Entity\CQObject` 对象此对象的属性和下表相同。2.3.0+ 版本可用)
- 返回:`数组 | CQObject | null`,见下表。
| 键名 | 说明 | | 键名 | 说明 |
| ------ | ------------------------------------------------------------ | | ------ | ------------------------------------------------------------ |
@@ -140,6 +141,10 @@ $str = CQ::removeCQ("[CQ:at,qq=all]这是带表情的全体消息[CQ:face,id=8]"
### CQ::getAllCQ() ### CQ::getAllCQ()
定义:`CQ::getAllCQ($msg, $is_object = false)`
参数 `$is_object` 为 true 时,返回一个 `\ZM\Entity\CQObject[]` 对象数组此对象的属性和上面的表格内相同。2.3.0+ 版本可用)
解析 CQ 码,和 `getCQ()` 的区别是,这个会将字符串中的所有 CQ 码都解析出来,并以同样上方解析出来的数组格式返回。 解析 CQ 码,和 `getCQ()` 的区别是,这个会将字符串中的所有 CQ 码都解析出来,并以同样上方解析出来的数组格式返回。
```php ```php
@@ -174,7 +179,7 @@ CQ::getAllCQ("[CQ:at,qq=123]你好啊[CQ:at,qq=456]");
定义:`CQ::face($id)` 定义:`CQ::face($id)`
参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [QQ 对应表情ID表](/assets/face_id.html)。 参数:`$id` 为 QQ 表情对应的 ID 号,一些常见的表情 ID 对应的表情样式见 [QQ 对应表情ID表](https://static.zhamao.me/face_id.html)。
```php ```php
/** /**

View File

@@ -0,0 +1,54 @@
# 存储管理(文件)
DataProvider 是框架内提供的一个简易的文件管理类。
定义:`\ZM\Utils\DataProvider`
## DataProvider::getWorkingDir()
`working_dir()`
## DataProvider::getFrameworkLink()
`ZMConfig::get("global", "http_reverse_link")`,获取反向代理的链接。
## DataProvider::getDataFolder()
获取配置项 `zm_data` 指定的目录。
## DataProvider::saveToJson()
将变量内容保存为 json 格式的文件,存储在 `zm_data/config/` 目录下或子目录下。
定义:`saveToJson($filename, $file_array)`
`$filename` 是文件名,不需要加后缀,比如你想保存成 `foo/bar.json`,这里写 `foo/bar` 就好。如果不想要二级目录,就直接写 `bar`,不需要加 `.json` 后缀。
这里只支持二级目录,不支持更多级的子目录。
`$file_array` 为内容,一般是数组,比如你缓存了一个 API 接口返回的数据,然后直接解析成数组后丢给它就好了。
## DataProvider::loadFromJson()
从 json 文件加载内容至变量。
定义:`loadFromJson($filename)`
文件名同上 `saveToJson()` 的定义,解析后的返回值为原先的内容或 `null`(如果文件不存在或 json 解析失败)。
## 其他文件读取
框架比较贴近原生的 PHP所以推荐直接使用原生的方法来读写文件`file_get_contents``file_put_contents`)。但有一点要注意,框架内最好使用**工作目录或者绝对路径**。
```php
// 读取框架工作目录的文件 composer.json 文件
$r = file_get_contents(working_dir() . "/composer.json");
// 写入 Linux 临时目录下的文件
file_put_contents("/tmp/test.txt", "hello world");
```
!!! warning "注意"
在默认的情况里,框架的根目录均为可写可读的,在读写文件时务必要注意目录的位置和权限。使用 `working_dir()` 获取目录后面需要加 `/` 再追加自己的文件名或子目录名。

View File

@@ -227,5 +227,32 @@ bot()->sendPrivateMsg(123456, "你好啊!!");
// 等同于 ZMRobot::getRandom()->sendPrivateMsg(123456, "你好啊!!"); // 等同于 ZMRobot::getRandom()->sendPrivateMsg(123456, "你好啊!!");
``` ```
## zm_atomic()
获取计时器,效果同 `\ZM\Store\ZMAtomic::get($name)`。
定义:`zm_atmoic($name)`
## uuidgen()
> 2.2.5 版本起可用。
生成一个随机的 uuid支持大写或小写。
定义:`uuidgen($uppercase = false)`
当 `$uppercase` 为 `true` 时,返回的 uuid 中字母都是大写。
## working_dir()
> 2.2.6 版本起可用。
获取框架运行的工作目录。例如你是从 `/root/framework-starter/` 目录启动的框架,`vendor/bin/start server`,那么 `working_dir()` 返回的就是 `/root/framework-starter`。(注意,返回的目录最后没有斜杠,请自行添加。)
## getAllFdByConnectType()
获取同类型的所有连接的描述符 ID。
定义:`getAllFdByConnectType(string $type = 'default'): array`
当 `$type` 为 `qq` 时,则返回所有 OneBot 机器人接入的 WebSocket 连接号。

View File

@@ -25,8 +25,8 @@
```php ```php
/** 轻量字符串缓存,默认开启 */ /** 轻量字符串缓存,默认开启 */
$config['light_cache'] = [ $config['light_cache'] = [
'size' => 1024, //最多允许储存的条数需要2的倍数 'size' => 512, //最多允许储存的条数需要2的倍数
'max_strlen' => 16384, //单行字符串最大长度需要2的倍数 'max_strlen' => 32768, //单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多 'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多
'persistence_path' => $config['zm_data'].'_cache.json', 'persistence_path' => $config['zm_data'].'_cache.json',
'auto_save_interval' => 900 'auto_save_interval' => 900

View File

@@ -31,7 +31,7 @@ SpinLock::unlock("foo");
给信号量 `$key` 上锁。如果该信号量已经被上锁,则立刻返回 false。 给信号量 `$key` 上锁。如果该信号量已经被上锁,则立刻返回 false。
```php ```php
SpinLock::lock("foo"); SpinLock::trylock("foo");
``` ```
## 综合实例 ## 综合实例
@@ -70,4 +70,4 @@ public function test() {
## 性能 ## 性能
使用自旋锁几乎没有性能损失,自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下使用自旋锁和不适用自旋锁的测试成绩时间分别为7.4s 和 6.9s。 使用自旋锁几乎没有性能损失,自旋锁要比其他类型的锁性能强很多,在上方举例使用的 `ab` 压测工具测试 100万请求 下使用自旋锁和不适用自旋锁的测试成绩时间分别为7.4s 和 6.9s。

View File

@@ -221,6 +221,27 @@
| tick_ms | `int`**必填**,间隔的毫秒数,例如 1 秒间隔为 `1000`,范围大于 0小于 86400000。 | | | | tick_ms | `int`**必填**,间隔的毫秒数,例如 1 秒间隔为 `1000`,范围大于 0小于 86400000。 | | |
| worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0范围是 0{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | | | worker_id | `int`,要在哪个 Worker 进程上执行,默认为 0范围是 0{你设定的 Worker 数量-1},如果是 -1 的话,则会在所有 Worker 进程上触发。 | 限定只执行的 Worker 进程 | |
## OnTask()
定义一个在工作进程中运行的任务函数。详情见 [进阶 - 使用 TaskWorker 进程处理密集运算](/advanced/task-worker)。
### 属性
| 类型 | 值 |
| ---------- | ----------------------------- |
| 名称 | `@OnTask` |
| 触发前提 | 在框架加载后激活 |
| 命名空间 | `ZM\Annotation\Swoole\OnTask` |
| 适用位置 | 方法 |
| 返回值处理 | 有,返回 Worker 进程的结果 |
### 注解参数
| 参数名称 | 参数范围 | 用途 | 默认 |
| --------- | ------------------------------------------------------------ | ------------ | ---- |
| task_name | `string`**必填**,任务函数的名称,不建议重复。 | | |
| rule | 设置触发前提PHP 代码,返回 bool 值即可,参考 OnRequestEvent | 限定是否执行 | 空 |
## OnSetup() ## OnSetup()
在框架加载前执行的代码。此部分代码是在主进程执行的,不可在此事件中使用任何协程相关的功能。 在框架加载前执行的代码。此部分代码是在主进程执行的,不可在此事件中使用任何协程相关的功能。

View File

@@ -4,7 +4,7 @@
!!! quote "开发提示" !!! quote "开发提示"
本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不嗦。 本章节涉及的路由和控制器概念可能和其他传统框架有一些出入,而且炸毛框架非绝对根据 PSR 标准进行开发,目的是使用上一些常见的东西尽可能地灵活和不嗦。
## 控制器和路由 ## 控制器和路由
@@ -228,4 +228,4 @@ public function staticImage($param) {
} }
``` ```
这样当用户访问 `http://框架地址/images/aaa.jpg` 就可以快速地调用此路由下的局部文件服务器功能了。 这样当用户访问 `http://框架地址/images/aaa.jpg` 就可以快速地调用此路由下的局部文件服务器功能了。

View File

@@ -4,55 +4,63 @@
!!! error "警告" !!! error "警告"
因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将敏感信息写入配置文件并提交至开源仓库! 因为炸毛框架的全局配置中含有数据库名称和密码以及 access_token 等敏感字段,在使用版本控制软件过程中请不要将敏感信息写入配置文件并提交至开源仓库!
## 全局配置文件 global.php ## 全局配置文件 global.php
框架的全局配置文件在 `config/global.php` 文件中。下面是配置文件的各个选项,请根据自己的需要自行配置。 框架的全局配置文件在 `config/global.php` 文件中。下面是配置文件的各个选项,请根据自己的需要自行配置。
| 配置名称 | 说明 | 默认值 | | 配置名称 | 说明 | 默认值 |
| :--------------------------- | ------------------------------------------------ | ---------------------------- | | :--------------------------- | ------------------------------------------------------------ | ---------------------------- |
| `host` | 框架监听的地址 | 0.0.0.0 | | `host` | 框架监听的地址 | 0.0.0.0 |
| `port` | 框架监听的端口 | 20001 | | `port` | 框架监听的端口 | 20001 |
| `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 | | `http_reverse_link` | 框架开到公网或外部的 HTTP 反代链接 | 见配置文件 |
| `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` | | `zm_data` | 框架的配置文件、日志文件等文件目录 | `./` 下的 `zm_data/` |
| `debug_mode` | 框架是否启动 debug 模式 | false | | `debug_mode` | 框架是否启动 debug 模式 | false |
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` | | `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
| `swoole` | 对应 Swoole server 中 set 的参数参考Swoole文档 | 见子表 `swoole` | | `swoole` | 对应 Swoole server 中 set 的参数参考Swoole文档 | 见子表 `swoole` |
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` | | `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` | | `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` | | `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` | | `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
| `access_token` | OneBot 客户端连接约定的token留空则无 | 空 | | `access_token` | OneBot 客户端连接约定的token留空则无,相关设置见 [组件 - Access Token 验证](component/access-token) | 空 |
| `http_header` | HTTP 请求自定义返回的header | 见配置文件 | | `http_header` | HTTP 请求自定义返回的header | 见配置文件 |
| `http_default_code_page` | HTTP服务器在指定状态码下回复的默认页面 | 见配置文件 | | `http_default_code_page` | HTTP服务器在指定状态码下回复的默认页面 | 见配置文件 |
| `init_atomics` | 框架启动时初始化的原子计数器列表 | 见配置文件 | | `init_atomics` | 框架启动时初始化的原子计数器列表 | 见配置文件 |
| `info_level` | 终端日志显示等级0-4 | 2 | | `info_level` | 终端日志显示等级0-4 | 2 |
| `context_class` | 上下文所定义的类,待上下文完善后见对应文档 | `\ZM\Context\Context::class` | | `context_class` | 上下文所定义的类,待上下文完善后见对应文档 | `\ZM\Context\Context::class` |
| `static_file_server` | 静态文件服务器配置项 | 见子表 `static_file_server` | | `static_file_server` | 静态文件服务器配置项 | 见子表 `static_file_server` |
| `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表 | 见配置文件 | | `server_event_handler_class` | 注册 Swoole Server 事件注解的类列表 | 见配置文件 |
| `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 | | `command_register_class` | 注册自定义命令行选项指令的类 | 见配置文件 |
| `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` | | `modules` | 服务器启用的外部第三方和内部插件 | `['onebot' => true]` |
### 子表 **swoole** ### 子表 **swoole**
| 配置名称 | 说明 | 默认值 | | 配置名称 | 说明 | 默认值 |
| --------------- | ------------------------------------------------------------ | ----------------------------------- | | ----------------------- | ------------------------------------------------------------ | ----------------------------------- |
| `log_file` | Swoole 的日志文件 | `crash_dir` 下的 `swoole_error.log` | | `log_file` | Swoole 的日志文件 | `crash_dir` 下的 `swoole_error.log` |
| `worker_num` | Worker 工作进程数 | 运行框架的主机 CPU 核心数 | | `worker_num` | Worker 工作进程数 | 运行框架的主机 CPU 核心数 |
| `dispatch_mode` | 数据包分发策略,见 [文档](https://wiki.swoole.com/#/server/setting?id=dispatch_mode) | 2 | | `dispatch_mode` | 数据包分发策略,见 [文档](https://wiki.swoole.com/#/server/setting?id=dispatch_mode) | 2 |
| `max_coroutine` | 最大协程并发数 | 300000 | | `max_coroutine` | 最大协程并发数 | 300000 |
| `task_worker_num` | TaskWorker 工作进程数 | 默认不开启(此参数被注释) |
| `task_enable_coroutine` | TaskWorker 工作进程启用协程 | 默认不开启(此参数被注释)或 `bool` |
### 子表 **light_cache** ### 子表 **light_cache**
| 配置名称 | 说明 | 默认值 | | 配置名称 | 说明 | 默认值 |
| -------------------------- | ----------------------------------------------- | ---------------------------- | | -------------------------- | ----------------------------------------------- | ---------------------------- |
| `size` | 最多可以缓存的 k-v 条目数(必须是 2 的 n 次方) | 1024 | | `size` | 最多可以缓存的 k-v 条目数(必须是 2 的 n 次方) | 512 |
| `max_strlen` | 作为 value 字符串的最大长度 | 16384 | | `max_strlen` | 作为 value 字符串的最大长度 | 32768 |
| `hash_conflict_proportion` | Hash冲突率越大越好但是需要的内存更多 | 0.6 | | `hash_conflict_proportion` | Hash冲突率越大越好但是需要的内存更多 | 0.6 |
| `persistence_path` | 持久化的键值对的存储路径 | `zm_data` 下的 `_cache.json` | | `persistence_path` | 持久化的键值对的存储路径 | `zm_data` 下的 `_cache.json` |
| `auto_save_interval` | 持久化的键值对自动保存时间间隔(秒) | 900 | | `auto_save_interval` | 持久化的键值对自动保存时间间隔(秒) | 900 |
### 子表 worker_cache
| 配置名称 | 说明 | 默认值 |
| -------- | --------------------------- | ------ |
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
### 子表 **sql_config** ### 子表 **sql_config**
| 配置名称 | 说明 | 默认值 | | 配置名称 | 说明 | 默认值 |
@@ -83,19 +91,13 @@
| `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` | | `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` |
| `document_index` | 默认索引的文件名列表 | `["index.html"]` | | `document_index` | 默认索引的文件名列表 | `["index.html"]` |
### 子表 worker_cache
| 配置名称 | 说明 | 默认值 |
| -------- | --------------------------- | ------ |
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
## 多环境下的配置文件 ## 多环境下的配置文件
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(production / development / staging来编写配置文件。 炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境development / staging / production)来编写配置文件。
### 使用环境参数 ### 使用环境参数
在启动框架时,额外增加参数 `--env` 可以指定当前的环境,从而使用不同的配置文件。现在框架支持以下几种环境: `production``staging``development` 在启动框架时,额外增加参数 `--env` 可以指定当前的环境,从而使用不同的配置文件。现在框架支持以下几种环境: `development``staging``production`
```bash ```bash
vendor/bin/start server --env=development vendor/bin/start server --env=development
@@ -103,7 +105,7 @@ vendor/bin/start server --env=development
### 不同环境配置文件 ### 不同环境配置文件
由于框架默认只带有 `global.php` 文件,所以假设你现在需要区分开发环境和生产环境的配置,将 `global.php` 文件复制或改名为 `global.development.php``global.production.php` 即可。 由于框架默认只带有 `global.php` 文件,所以假设你现在需要区分开发环境和生产环境的配置,将 `global.php` 文件复制并重命名为 `global.development.php``global.production.php` 即可。
### 优先级 ### 优先级
@@ -146,4 +148,4 @@ $r = ZMConfig::get("example_a", "key1"); # $r == "value1"
$time = ZMConfig::get("example_a", "starttime"); # $time == 服务器启动时间 $time = ZMConfig::get("example_a", "starttime"); # $time == 服务器启动时间
``` ```
同时,自定义配置文件也支持环境变量,例如:`example_a.development.json``example_a.production.php` 均可。 同时,自定义配置文件也支持环境变量,例如:`example_a.development.json``example_a.production.php` 均可。

View File

@@ -82,8 +82,19 @@ cd zhamao-framework-starter
./run-docker.sh # 在正式版炸毛框架 v2 发布后可用,测试版暂不放出 ./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 ```bash
cd zhamao-framework-starter cd zhamao-framework-starter

View File

@@ -8,7 +8,7 @@
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 **机器人问答模块** 的准备。 一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 **机器人问答模块** 的准备。
炸毛框架和机器人客户端是什么关系呢?炸毛框架就好比我们传统的一系列例如 Spring 框架、ThinkPHP 框架等,是服务端,而机器人客户端是一个 HTTP / WebSocket 客户端,时刻准备着连接到炸毛框架 炸毛框架和机器人客户端是什么关系呢?炸毛框架就好比我们传统的一系列例如 Spring 框架、ThinkPHP 框架等,是服务端,而机器人客户端是一个 HTTP / WebSocket 客户端,时刻准备着连接到炸毛框架。
## 机器人客户端 ## 机器人客户端
@@ -30,51 +30,6 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json` 由于 go-cqhttp 项目还处于开发期,而且配置文件格式也发生了多次变化,但大体内容没有变(比如编写此文档时发布的版本中配置文件格式变成了 `hjson` 取代了原来的 `json`
=== "config.json旧格式"
``` json hl_lines="2 3 30 31"
{
"uin": 你的QQ号,
"password": "你的密码",
"encrypt_password": false,
"password_encrypted": "",
"enable_db": true,
"access_token": "",
"relogin": {
"enabled": true,
"relogin_delay": 3,
"max_relogin_times": 0
},
"ignore_invalid_cqcode": false,
"force_fragmented": true,
"heartbeat_interval": 0,
"http_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 5700,
"timeout": 0,
"post_urls": {}
},
"ws_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 6700
},
"ws_reverse_servers": [
{
"enabled": true,
"reverse_url": "ws://127.0.0.1:20001/",
"reverse_api_url": "",
"reverse_event_url": "",
"reverse_reconnect_interval": 3000
}
],
"post_message_format": "string",
"debug": false,
"log_level": ""
}
```
=== "config.hjson新格式" === "config.hjson新格式"
``` json hl_lines="3 5 81 84" ``` json hl_lines="3 5 81 84"
@@ -193,6 +148,51 @@ OneBot 机器人部分的选择详情见 [OneBot 实例](/guide/OneBot实例/)
} }
``` ```
=== "config.json旧格式"
``` json hl_lines="2 3 30 31"
{
"uin": 你的QQ号,
"password": "你的密码",
"encrypt_password": false,
"password_encrypted": "",
"enable_db": true,
"access_token": "",
"relogin": {
"enabled": true,
"relogin_delay": 3,
"max_relogin_times": 0
},
"ignore_invalid_cqcode": false,
"force_fragmented": true,
"heartbeat_interval": 0,
"http_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 5700,
"timeout": 0,
"post_urls": {}
},
"ws_config": {
"enabled": false,
"host": "0.0.0.0",
"port": 6700
},
"ws_reverse_servers": [
{
"enabled": true,
"reverse_url": "ws://127.0.0.1:20001/",
"reverse_api_url": "",
"reverse_event_url": "",
"reverse_reconnect_interval": 3000
}
],
"post_message_format": "string",
"debug": false,
"log_level": ""
}
```
其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT 其中 ws://127.0.0.1:20001/ 中的 127.0.0.1 和 20001 应分别对应炸毛框架配置的 HOST 和 PORT
## 第一次对话 ## 第一次对话
@@ -229,8 +229,8 @@ public function repeat() {
( 你好啊 ( 你好啊
) echo ) echo
( 请输入你要回复的内容 ( 请输入你要回复的内容
) 哦豁,完蛋 ) 哦豁
( 哦豁,完蛋 ( 哦豁
</chat-box> </chat-box>
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。 > 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。

View File

@@ -8,14 +8,13 @@
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档 编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 标准的机器人对接),包含 WebSocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能 框架主要用途为 HTTP/WS 服务器,机器人搭建框架。尤其对于聊天机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。
在 HTTP 和 WebSocket 服务器上PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。 在 HTTP 和 WebSocket 服务器上PHP 的扩展 Swoole 提供了高性能的支持,使其效率可媲美 nginx 静态网页处理的效率。
此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快,未来也会兼容微信公众号开发者模式 此外QQ 机器人方面此框架基于 OneBot 标准的反向 WebSocket 连接,比传统 HTTP 通信更快。
```php ```php
/** /**
@@ -39,9 +38,9 @@ public function index() {
首先,你需要了解你需要知道哪些事情才能开始着手使用框架: 首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
1. Linux 命令行(会跑 Linux 程序) 1. Linux 命令行(会跑 Linux 程序)
2. php 7.2+ 开发环境 2. php 7.2+ 开发环境(项目会持续支持最新的 PHP 版本)
3. HTTP 协议(可选) 3. HTTP/WebSocket 协议
4. OneBot 机器人聊天接口标准(可选) 4. OneBot 机器人聊天接口标准
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。 需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。

View File

@@ -1,8 +1,87 @@
# 更新日志v2 版本) # 更新日志v2 版本)
## 2.3.0
> 更新时间2021.3.16
- 新增MessageUtil 消息处理工具类
- 新增TaskManager封装了 TaskWorker 进程的应用
- 新增CQObject使用 `CQ::getCQ()` 可获取对象形式的 CQ 码解析结果
- 新增:`@OnTask` 注解,绑定任务函数
- 新增RouteManager 路由管理类,可快速添加路由
- 修复:`ZM_DATA``DataProvider::getDataFolder()` 返回 false 的问题
- 优化:关闭显示停止框架后多余的输出信息
注:本次升级建议升级后合并全局配置文件,有一些新加的内容。
## 2.2.11
> 更新时间2021.3.13
- 新增:内部 ID 版本号ZM_VERSION_ID
- 优化:启动时 log 的等级
- 移除:终端输入命令
- 修复:纯 HTTP 服务器的启动 bug
- 新增:`zm_timer` 的报错处理,防止服务器直接崩掉
## v2.2.10
> 更新时间2021.3.8
- 新增:用户态 php 编译脚本 `build-runtime.sh`
- 移除:无用的调试信息
- 新增:`--show-php-ver` 启动参数
## v2.2.9
> 更新时间2021.3.6
- 更新:`reply()` 方法传入数组则变为快速相应的 API 操作
- 修复:在 Worker 进程下调用 `ZMUtil::reload()` 会导致一些奇怪的 bug
- 修复:`reply()` 时会 at 私聊成员的 bug由 go-cqhttp 导致)
## v2.2.8
> 更新时间2021.3.2
- 更新MOTD 显示的方式,更加直观和炫酷
## v2.2.7
> 更新时间2021.2.27
- 修复2.2.6 版本下 `reply()` 方法在群里调用会 at 成员的 bug
- 修复:空 `access_token` 的情况下会无法连入的 bug
- 修复:使用 Closure 闭包函数自行编写逻辑的判断返回 false 无法阻断连接的 bug
## v2.2.6
> 更新时间2021.2.26
- 新增:`uuidgen()` 全局函数,快速生成 uuid
- 修复MySQL `rawQuery()` 在参数为非数组时会报 Warning 的 bug
- 新增:示例模块的 API 示例:一言查询
- 优化:删减部分无用代码
- 更改:`ctx()->reply()` 方法改为调用隐藏方法:`.handle_quick_operation`
- 修复:`ctx()->finalReply()` 一直以来的 bug未阻断事件
- 新增:`access_token` 配置项支持闭包函数自行设计判断方式和逻辑
- 新增:全局函数 `working_dir()`
## v2.2.5
> 更新时间2021.2.20
- 新增:`saveToJson()``loadFromJson()` 方法DataProvider 类)
- 修复:`@OnSave` 注解事件无法工作的 bug
- 调整:自定义计时器创建时的性能调优
- 新增WorkerCache 方法:`hasKey()`
- 新增SpinLock 方法:`transaction()`(直接在事务中上锁)
- 新增CQ 方法:`getAllCQ()``_custom()`(获取消息中的所有 CQ 码)
- 修复CQ 类中的部分 bug
## v2.2.4 ## v2.2.4
> 更新事件2021.2.7 > 更新时间2021.2.7
- 修复:终端交互导致的 ssh 断掉后 CPU 占用过高的问题 - 修复:终端交互导致的 ssh 断掉后 CPU 占用过高的问题
- 修复WorkerCache 在缺少配置文件下工作异常的问题 - 修复WorkerCache 在缺少配置文件下工作异常的问题

View File

@@ -10,8 +10,8 @@ theme:
favicon: assets/favicon.png favicon: assets/favicon.png
language: zh language: zh
palette: palette:
primary: red primary: indigo
accent: red accent: indigo
features: features:
- navigation.tabs - navigation.tabs
extra_javascript: extra_javascript:
@@ -72,21 +72,28 @@ nav:
- 事件分发器: event/event-dispatcher.md - 事件分发器: event/event-dispatcher.md
- 框架组件: - 框架组件:
- 框架组件: component/index.md - 框架组件: component/index.md
- 机器人 API: component/robot-api.md
- CQ 码(多媒体消息): component/cqcode.md
- 上下文: component/context.md - 上下文: component/context.md
- 聊天机器人组件:
- 机器人 API: component/robot-api.md
- CQ 码(多媒体消息): component/cqcode.md
- 机器人消息处理: component/message-util.md
- Token 验证: component/access-token.md
- 存储: - 存储:
- LightCache 轻量缓存: component/light-cache.md - LightCache 轻量缓存: component/light-cache.md
- MySQL 数据库: component/mysql.md - MySQL 数据库: component/mysql.md
- Redis 数据库: component/redis.md - Redis 数据库: component/redis.md
- ZMAtomic 原子计数器: component/atomics.md - ZMAtomic 原子计数器: component/atomics.md
- SpinLock 自旋锁: component/spin-lock.md - SpinLock 自旋锁: component/spin-lock.md
- 文件管理: component/data-provider.md
- HTTP 服务器工具类:
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- HTTP 路由管理: component/route-manager.md
- 协程池: component/coroutine-pool.md - 协程池: component/coroutine-pool.md
- 单例类: component/singleton-trait.md - 单例类: component/singleton-trait.md
- ZMUtil 杂项: component/zmutil.md - ZMUtil 杂项: component/zmutil.md
- 全局方法: component/global-functions.md - 全局方法: component/global-functions.md
- HTTP 和 WebSocket 客户端: component/zmrequest.md
- Console 终端: component/console.md - Console 终端: component/console.md
- TaskWorker 管理: component/task-worker.md
- 进阶开发: - 进阶开发:
- 进阶开发: advanced/index.md - 进阶开发: advanced/index.md
- 框架剖析: advanced/framework-structure.md - 框架剖析: advanced/framework-structure.md
@@ -95,6 +102,7 @@ nav:
- 内部类文件手册: advanced/inside-class.md - 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md - 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 框架多进程: advanced/multi-process.md - 框架多进程: advanced/multi-process.md
- TaskWorker 提高并发: advanced/task-worker.md
- 开发实战教程: - 开发实战教程:
- 编写管理员才能触发的功能: advanced/example/admin.md - 编写管理员才能触发的功能: advanced/example/admin.md
- FAQ: FAQ.md - FAQ: FAQ.md

View File

@@ -1,6 +1,11 @@
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */ #plain <?php /** @noinspection PhpFullyQualifiedNameUsageInspection */ #plain
//这里写你的全局函数 //这里写你的全局函数
/**
* @param callable $func
* @param string $name
* @noinspection PhpUnused
*/
function pgo(callable $func, $name = "default") { function pgo(callable $func, $name = "default") {
\ZM\Utils\CoroutinePool::go($func, $name); \ZM\Utils\CoroutinePool::go($func, $name);
} }

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpMissingReturnTypeInspection */
namespace Module\Example; namespace Module\Example;
@@ -11,6 +11,8 @@ use ZM\Console\Console;
use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Http\RequestMapping;
use ZM\Event\EventDispatcher; use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Requests\ZMRequest;
use ZM\Utils\ZMUtil; use ZM\Utils\ZMUtil;
/** /**
@@ -20,6 +22,14 @@ use ZM\Utils\ZMUtil;
*/ */
class Hello class Hello
{ {
/*
* 默认的图片监听路由对应目录,如需要使用可取消下面的注释,把上面的 /* 换成 /**
* @OnStart(-1)
*/
//public function onStart() {
// \ZM\Http\RouteManager::addStaticFileRoute("/images/", \ZM\Utils\DataProvider::getWorkingDir()."/zm_data/images/");
//}
/** /**
* 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ * 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ
* @CQCommand(".reload",user_id=627577391) * @CQCommand(".reload",user_id=627577391)
@@ -45,6 +55,18 @@ class Hello
return "你好啊,我是由炸毛框架构建的机器人!"; return "你好啊,我是由炸毛框架构建的机器人!";
} }
/**
* 一个最基本的第三方 API 接口使用示例
* @CQCommand("一言")
*/
public function hitokoto() {
$api_result = ZMRequest::get("https://v1.hitokoto.cn/");
if ($api_result === false) return "接口请求出错,请稍后再试!";
$obj = json_decode($api_result, true);
if ($obj === null) return "接口解析出错!可能返回了非法数据!";
return $obj["hitokoto"] . "\n----「" . $obj["from"] . "";
}
/** /**
* 一个简单随机数的功能demo * 一个简单随机数的功能demo
* 问法1随机数 1 20 * 问法1随机数 1 20
@@ -89,7 +111,7 @@ class Hello
* @return string * @return string
*/ */
public function paramGet($param) { public function paramGet($param) {
return "Hello, ".$param["name"]; return "Hello, " . $param["name"];
} }
/** /**
@@ -113,6 +135,7 @@ class Hello
/** /**
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰 * 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200) * @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
* @throws InterruptException
*/ */
public function onRequest() { public function onRequest() {
EventDispatcher::interrupt(); EventDispatcher::interrupt();

View File

@@ -24,7 +24,7 @@ class TimerMiddleware implements MiddlewareInterface
* @HandleBefore() * @HandleBefore()
* @return bool * @return bool
*/ */
public function onBefore() { public function onBefore(): bool {
$this->starttime = microtime(true); $this->starttime = microtime(true);
return true; return true;
} }

View File

@@ -1,10 +1,13 @@
<?php <?php /** @noinspection PhpUnused */
/** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\API; namespace ZM\API;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Entity\CQObject;
class CQ class CQ
{ {
@@ -305,9 +308,10 @@ class CQ
/** /**
* 获取消息中第一个CQ码 * 获取消息中第一个CQ码
* @param $msg * @param $msg
* @return array|null * @param bool $is_object
* @return array|CQObject|null
*/ */
public static function getCQ($msg) { public static function getCQ($msg, $is_object = false) {
if (($head = mb_strpos($msg, "[CQ:")) !== false) { if (($head = mb_strpos($msg, "[CQ:")) !== false) {
$key_offset = mb_substr($msg, $head); $key_offset = mb_substr($msg, $head);
$close = mb_strpos($key_offset, "]"); $close = mb_strpos($key_offset, "]");
@@ -322,7 +326,7 @@ class CQ
} }
$cq["start"] = $head; $cq["start"] = $head;
$cq["end"] = $close + $head; $cq["end"] = $close + $head;
return $cq; return !$is_object ? $cq : CQObject::fromArray($cq);
} else { } else {
return null; return null;
} }
@@ -331,9 +335,10 @@ class CQ
/** /**
* 获取消息中所有的CQ码 * 获取消息中所有的CQ码
* @param $msg * @param $msg
* @return array * @param bool $is_object
* @return array|CQObject[]
*/ */
public static function getAllCQ($msg) { public static function getAllCQ($msg, $is_object = false) {
$cqs = []; $cqs = [];
$offset = 0; $offset = 0;
while (($head = mb_strpos(($submsg = mb_substr($msg, $offset)), "[CQ:")) !== false) { while (($head = mb_strpos(($submsg = mb_substr($msg, $offset)), "[CQ:")) !== false) {
@@ -352,7 +357,7 @@ class CQ
$cq["start"] = $offset + $head; $cq["start"] = $offset + $head;
$cq["end"] = $offset + $tmpmsg + $head; $cq["end"] = $offset + $tmpmsg + $head;
$offset += $tmpmsg + 1; $offset += $tmpmsg + 1;
$cqs[] = $cq; $cqs[] = (!$is_object ? $cq : CQObject::fromArray($cq));
} }
return $cqs; return $cqs;
} }

View File

@@ -29,25 +29,15 @@ trait CQAPI
public function processWebsocketAPI($connection, $reply, $function = false) { public function processWebsocketAPI($connection, $reply, $function = false) {
$api_id = ZMAtomic::get("wait_msg_id")->add(1); $api_id = ZMAtomic::get("wait_msg_id")->add(1);
$reply["echo"] = $api_id; $reply["echo"] = $api_id;
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
$r[$api_id] = [
"data" => $reply,
"time" => microtime(true),
"self_id" => $connection->getOption("connect_id"),
"echo" => $api_id
];
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
if (server()->push($connection->getFd(), json_encode($reply))) { if (server()->push($connection->getFd(), json_encode($reply))) {
if ($function === true) { if ($function === true) {
return CoMessage::yieldByWS($r[$api_id], ["echo"], 60); $obj = [
} else { "data" => $reply,
SpinLock::lock("wait_api"); "time" => microtime(true),
$r = LightCacheInside::get("wait_api", "wait_api"); "self_id" => $connection->getOption("connect_id"),
unset($r[$api_id]); "echo" => $api_id
LightCacheInside::set("wait_api", "wait_api", $r); ];
SpinLock::unlock("wait_api"); return CoMessage::yieldByWS($obj, ["echo"], 60);
} }
return true; return true;
} else { } else {
@@ -75,10 +65,11 @@ trait CQAPI
* @return bool * @return bool
* @noinspection PhpUnusedParameterInspection * @noinspection PhpUnusedParameterInspection
*/ */
public function processHttpAPI($connection, $reply, $function = null) { public function processHttpAPI($connection, $reply, $function = null): bool {
return false; return false;
} }
/** @noinspection PhpMissingReturnTypeInspection */
public function __call($name, $arguments) { public function __call($name, $arguments) {
return false; return false;
} }

View File

@@ -1,4 +1,6 @@
<?php /** @noinspection PhpUnused */ <?php /** @noinspection PhpMissingReturnTypeInspection */
/** @noinspection PhpUnused */
namespace ZM\API; namespace ZM\API;

View File

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

View File

@@ -125,10 +125,7 @@ class AnnotationParser
Console::debug("解析注解完毕!"); Console::debug("解析注解完毕!");
} }
/** public function generateAnnotationEvents(): array {
* @return array
*/
public function generateAnnotationEvents() {
$o = []; $o = [];
foreach ($this->annotation_map as $module => $obj) { foreach ($this->annotation_map as $module => $obj) {
foreach (($obj["class_annotations"] ?? []) as $class_annotation) { foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
@@ -151,17 +148,17 @@ class AnnotationParser
/** /**
* @return array * @return array
*/ */
public function getMiddlewares() { return $this->middlewares; } public function getMiddlewares(): array { return $this->middlewares; }
/** /**
* @return array * @return array
*/ */
public function getMiddlewareMap() { return $this->middleware_map; } public function getMiddlewareMap(): array { return $this->middleware_map; }
/** /**
* @return array * @return array
*/ */
public function getReqMapping() { return $this->req_mapping; } public function getReqMapping(): array { return $this->req_mapping; }
/** /**
* @param $path * @param $path
@@ -171,7 +168,7 @@ class AnnotationParser
//private function below //private function below
private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class) { private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class): array {
$result = [ $result = [
"class" => "\\" . $reflection_class->getName(), "class" => "\\" . $reflection_class->getName(),
"name" => $vs->name "name" => $vs->name

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,36 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Rule;
/**
* Class OnTask
* @package ZM\Annotation\Swoole
* @Annotation
* @Target("METHOD")
*/
class OnTask extends AnnotationBase implements Rule
{
/**
* @var string
* @Required()
*/
public $task_name;
/**
* @var string
*/
public $rule = "";
/**
* @return mixed
*/
public function getRule(): string {
return $this->rule;
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* Class OnTaskEvent
* @package ZM\Annotation\Swoole
* @Annotation
* @Target("METHOD")
*/
class OnTaskEvent extends OnSwooleEventBase
{
}

View File

@@ -26,7 +26,7 @@ class BuildCommand extends Command
// ... // ...
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
$this->output = $output; $this->output = $output;
$target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/'); $target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/');
if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir); if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir);

View File

@@ -13,7 +13,7 @@ abstract class DaemonCommand extends Command
{ {
protected $daemon_file = null; protected $daemon_file = null;
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid"; $pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
if (!file_exists($pid_path)) { if (!file_exists($pid_path)) {
$output->writeln("<comment>没有检测到正在运行的守护进程!</comment>"); $output->writeln("<comment>没有检测到正在运行的守护进程!</comment>");

View File

@@ -15,7 +15,7 @@ class DaemonReloadCommand extends DaemonCommand
$this->setDescription("重载守护进程下的用户代码(仅限--daemon模式可用"); $this->setDescription("重载守护进程下的用户代码(仅限--daemon模式可用");
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output); parent::execute($input, $output);
system("kill -USR1 " . intval($this->daemon_file["pid"])); system("kill -USR1 " . intval($this->daemon_file["pid"]));
$output->writeln("<info>成功重载!</info>"); $output->writeln("<info>成功重载!</info>");

View File

@@ -15,7 +15,7 @@ class DaemonStatusCommand extends DaemonCommand
$this->setDescription("查看守护进程框架的运行状态(仅限--daemon模式可用"); $this->setDescription("查看守护进程框架的运行状态(仅限--daemon模式可用");
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output); parent::execute($input, $output);
$output->writeln("<info>框架运行中pid" . $this->daemon_file["pid"] . "</info>"); $output->writeln("<info>框架运行中pid" . $this->daemon_file["pid"] . "</info>");
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>"); $output->writeln("<comment>----- 以下是stdout内容 -----</comment>");

View File

@@ -16,7 +16,7 @@ class DaemonStopCommand extends DaemonCommand
$this->setDescription("停止守护进程下运行的框架(仅限--daemon模式可用"); $this->setDescription("停止守护进程下运行的框架(仅限--daemon模式可用");
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
parent::execute($input, $output); parent::execute($input, $output);
system("kill -TERM " . intval($this->daemon_file["pid"])); system("kill -TERM " . intval($this->daemon_file["pid"]));
unlink(DataProvider::getWorkingDir() . "/.daemon_pid"); unlink(DataProvider::getWorkingDir() . "/.daemon_pid");

View File

@@ -30,7 +30,7 @@ class InitCommand extends Command
// ... // ...
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式最基本的需要初始化的模式 if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式最基本的需要初始化的模式
$output->writeln("<comment>Initializing files</comment>"); $output->writeln("<comment>Initializing files</comment>");
$base_path = LOAD_MODE_COMPOSER_PATH; $base_path = LOAD_MODE_COMPOSER_PATH;
@@ -96,7 +96,7 @@ class InitCommand extends Command
return Command::FAILURE; return Command::FAILURE;
} }
private function getExtractFiles() { private function getExtractFiles(): array {
return $this->extract_files; return $this->extract_files;
} }
} }

View File

@@ -5,7 +5,6 @@ namespace ZM\Command;
use Swoole\Atomic; use Swoole\Atomic;
use Swoole\Coroutine;
use Swoole\Http\Request; use Swoole\Http\Request;
use Swoole\Http\Response; use Swoole\Http\Response;
use Swoole\Http\Server; use Swoole\Http\Server;
@@ -17,7 +16,9 @@ use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Framework;
use ZM\Store\ZMAtomic; use ZM\Store\ZMAtomic;
use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil; use ZM\Utils\HttpUtil;
class PureHttpCommand extends Command class PureHttpCommand extends Command
@@ -34,23 +35,32 @@ class PureHttpCommand extends Command
// ... // ...
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
$tty_width = explode(" ", trim(exec("stty size")))[1]; $tty_width = explode(" ", trim(exec("stty size")))[1];
if (realpath($input->getArgument('dir') ?? '.') === false) { if (realpath($input->getArgument('dir') ?? '.') === false) {
$output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>"); $output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>");
return self::FAILURE; return self::FAILURE;
} }
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
$global = ZMConfig::get("global"); $global = ZMConfig::get("global");
$host = $input->getOption("host") ?? $global["host"]; $host = $input->getOption("host") ?? $global["host"];
$port = $input->getOption("port") ?? $global["port"]; $port = $input->getOption("port") ?? $global["port"];
$index = ["index.html", "index.htm"];
$out = [
"listen" => $host.":".$port,
"version" => ZM_VERSION,
"web_root" => realpath($input->getArgument('dir') ?? '.'),
"index" => implode(",", $index)
];
Framework::printProps($out, $tty_width);
$server = new Server($host, $port); $server = new Server($host, $port);
$server->set(ZMConfig::get("global", "swoole")); $server->set(ZMConfig::get("global", "swoole"));
Console::init(0, $server); Console::init(2, $server);
ZMAtomic::$atomics["request"] = []; ZMAtomic::$atomics["request"] = [];
for ($i = 0; $i < 32; ++$i) { for ($i = 0; $i < 32; ++$i) {
ZMAtomic::$atomics["request"][$i] = new Atomic(0); ZMAtomic::$atomics["request"][$i] = new Atomic(0);
} }
$index = ["index.html", "index.htm"];
$server->on("request", function (Request $request, Response $response) use ($input, $index, $server) { $server->on("request", function (Request $request, Response $response) use ($input, $index, $server) {
ZMAtomic::$atomics["request"][$server->worker_id]->add(1); ZMAtomic::$atomics["request"][$server->worker_id]->add(1);
HttpUtil::handleStaticPage( HttpUtil::handleStaticPage(
@@ -60,10 +70,11 @@ class PureHttpCommand extends Command
"document_root" => realpath($input->getArgument('dir') ?? '.'), "document_root" => realpath($input->getArgument('dir') ?? '.'),
"document_index" => $index "document_index" => $index
]); ]);
echo "\r" . Coroutine::stats()["coroutine_peak_num"]; //echo "\r" . Coroutine::stats()["coroutine_peak_num"];
}); });
$server->on("start", function ($server) { $server->on("start", function ($server) {
Process::signal(SIGINT, function () use ($server) { Process::signal(SIGINT, function () use ($server) {
echo "\r";
Console::warning("Server interrupted by keyboard."); Console::warning("Server interrupted by keyboard.");
for ($i = 0; $i < 32; ++$i) { for ($i = 0; $i < 32; ++$i) {
$num = ZMAtomic::$atomics["request"][$i]->get(); $num = ZMAtomic::$atomics["request"][$i]->get();
@@ -75,13 +86,6 @@ class PureHttpCommand extends Command
}); });
Console::success("Server started. Use Ctrl+C to stop."); Console::success("Server started. Use Ctrl+C to stop.");
}); });
$out = [
"host" => $host,
"port" => $port,
"document_root" => realpath($input->getArgument('dir') ?? '.'),
"document_index" => implode(", ", $index)
];
Console::printProps($out, $tty_width);
$server->start(); $server->start();
// return this if there was no problem running the command // return this if there was no problem running the command
// (it's equivalent to returning int(0)) // (it's equivalent to returning int(0))

View File

@@ -22,24 +22,24 @@ class RunServerCommand extends Command
new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"), new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"),
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"), new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"), new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"), new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
new InputOption("disable-coroutine", null, null, "关闭协程Hook"), new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
new InputOption("daemon", null, null, "以守护进程的方式运行框架"), new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"), 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("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
]); ]);
$this->setDescription("Run zhamao-framework | 启动框架"); $this->setDescription("Run zhamao-framework | 启动框架");
$this->setHelp("直接运行可以启动"); $this->setHelp("直接运行可以启动");
} }
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
if (($opt = $input->getOption("env")) !== null) { if (($opt = $input->getOption("env")) !== null) {
if (!in_array($opt, ["production", "staging", "development", ""])) { if (!in_array($opt, ["production", "staging", "development", ""])) {
$output->writeln("<error> \"--env\" option only accept production, development, staging and [empty] ! </error>"); $output->writeln("<error> \"--env\" option only accept production, development, staging and [empty] ! </error>");
return Command::FAILURE; return Command::FAILURE;
} }
} }
if (LOAD_MODE == 0) echo "* This is repository mode.\n";
(new Framework($input->getOptions()))->start(); (new Framework($input->getOptions()))->start();
return Command::SUCCESS; return Command::SUCCESS;
} }

View File

@@ -13,7 +13,7 @@ class SystemdCommand extends Command
// the name of the command (the part after "bin/console") // the name of the command (the part after "bin/console")
protected static $defaultName = 'systemd:generate'; protected static $defaultName = 'systemd:generate';
protected function execute(InputInterface $input, OutputInterface $output) { protected function execute(InputInterface $input, OutputInterface $output): int {
//TODO: 写一个生成systemd配置的功能给2.0 //TODO: 写一个生成systemd配置的功能给2.0
return Command::SUCCESS; return Command::SUCCESS;
} }

View File

@@ -18,31 +18,33 @@ use ZM\Utils\DataProvider;
class ConsoleApplication extends Application class ConsoleApplication extends Application
{ {
const VERSION_ID = 395;
const VERSION = "2.3.2";
public function __construct(string $name = 'UNKNOWN') { public function __construct(string $name = 'UNKNOWN') {
$version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN"; define("ZM_VERSION_ID", self::VERSION_ID);
parent::__construct($name, $version); define("ZM_VERSION", self::VERSION);
parent::__construct($name, ZM_VERSION);
} }
public function initEnv() { public function initEnv(): ConsoleApplication {
$this->selfCheck(); $this->selfCheck();
if (!is_dir(__DIR__ . '/../../vendor')) {
define("LOAD_MODE", 1); // composer项目模式
define("LOAD_MODE_COMPOSER_PATH", getcwd());
} else {
define("LOAD_MODE", 0); // 源码模式
}
//if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令 //if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令
if (LOAD_MODE === 0) define("WORKING_DIR", getcwd()); if (LOAD_MODE === 0) define("WORKING_DIR", getcwd());
elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../")); elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) { if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
/** @noinspection PhpIncludeInspection */ /** @noinspection PhpIncludeInspection */
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php"; require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
} }
if (LOAD_MODE == 2) { if (LOAD_MODE == 0) {
// Phar 模式2.0 不提供哦
//require_once FRAMEWORK_DIR . "/vendor/autoload.php";
spl_autoload_register('phar_classloader');
} elseif (LOAD_MODE == 0) {
/** @noinspection PhpIncludeInspection
* @noinspection RedundantSuppression
*/
require_once WORKING_DIR . "/vendor/autoload.php";
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) { if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] "; echo "框架源码模式需要在autoload文件中添加Module目录为自动加载是否添加[Y/n] ";
@@ -52,7 +54,7 @@ class ConsoleApplication extends Application
$composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom"; $composer["autoload"]["psr-4"]["Custom\\"] = "src/Custom";
$r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); $r = file_put_contents(DataProvider::getWorkingDir() . "/composer.json", json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
if ($r !== false) { if ($r !== false) {
echo "成功添加!请重新进行 composer update \n"; echo "成功添加!请行 composer dump-autoload \n";
exit(1); exit(1);
} else { } else {
echo "添加失败!请按任意键继续!"; echo "添加失败!请按任意键继续!";
@@ -88,7 +90,7 @@ class ConsoleApplication extends Application
* @param OutputInterface|null $output * @param OutputInterface|null $output
* @return int * @return int
*/ */
public function run(InputInterface $input = null, OutputInterface $output = null) { public function run(InputInterface $input = null, OutputInterface $output = null): int {
try { try {
return parent::run($input, $output); return parent::run($input, $output);
} catch (Exception $e) { } catch (Exception $e) {
@@ -96,15 +98,10 @@ class ConsoleApplication extends Application
} }
} }
private function selfCheck() { private function selfCheck(): bool {
if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19"); 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.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !");
//if (!extension_loaded("gd")) die("Can not find gd extension.\n"); if (version_compare(PHP_VERSION, "7.2") == -1) die("PHP >= 7.2 required.");
//if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
if (substr(PHP_VERSION, 0, 1) < "7") die("PHP >=7 required.\n");
//if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
//if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
return true; return true;
} }
} }

View File

@@ -8,10 +8,12 @@ use Co;
use Exception; use Exception;
use Swoole\Http\Request; use Swoole\Http\Request;
use Swoole\WebSocket\Frame; use Swoole\WebSocket\Frame;
use swoole_server; use Swoole\WebSocket\Server;
use ZM\ConnectionManager\ConnectionObject; use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Exception\InvalidArgumentException; use ZM\Exception\InvalidArgumentException;
use ZM\Exception\WaitTimeoutException; use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response; use ZM\Http\Response;
@@ -26,19 +28,19 @@ class Context implements ContextInterface
public function __construct($cid) { $this->cid = $cid; } public function __construct($cid) { $this->cid = $cid; }
/** /**
* @return swoole_server|null * @return Server
*/ */
public function getServer() { return self::$context[$this->cid]["server"] ?? server(); } public function getServer(): ?Server { return self::$context[$this->cid]["server"] ?? server(); }
/** /**
* @return Frame|null * @return Frame|null
*/ */
public function getFrame() { return self::$context[$this->cid]["frame"] ?? null; } public function getFrame(): ?Frame { return self::$context[$this->cid]["frame"] ?? null; }
public function getFd() { return self::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; } public function getFd(): ?int { return self::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; }
/** /**
* @return array|null * @return mixed
*/ */
public function getData() { return self::$context[$this->cid]["data"] ?? null; } public function getData() { return self::$context[$this->cid]["data"] ?? null; }
@@ -47,25 +49,25 @@ class Context implements ContextInterface
/** /**
* @return Request|null * @return Request|null
*/ */
public function getRequest() { return self::$context[$this->cid]["request"] ?? null; } public function getRequest(): ?Request { return self::$context[$this->cid]["request"] ?? null; }
/** /**
* @return Response|null * @return Response|null
*/ */
public function getResponse() { return self::$context[$this->cid]["response"] ?? null; } public function getResponse(): ?Response { return self::$context[$this->cid]["response"] ?? null; }
/** @return ConnectionObject|null */ /** @return ConnectionObject|null|Response */
public function getConnection() { return ManagerGM::get($this->getFd()); } public function getConnection() { return ManagerGM::get($this->getFd()); }
/** /**
* @return int|null * @return int|null
*/ */
public function getCid() { return $this->cid; } public function getCid(): ?int { return $this->cid; }
/** /**
* @return ZMRobot|null * @return ZMRobot|null
*/ */
public function getRobot() { public function getRobot(): ?ZMRobot {
$conn = ManagerGM::get($this->getFrame()->fd); $conn = ManagerGM::get($this->getFrame()->fd);
return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null; return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
} }
@@ -86,7 +88,7 @@ class Context implements ContextInterface
public function setDiscussId($id) { self::$context[$this->cid]["data"]["discuss_id"] = $id; } public function setDiscussId($id) { self::$context[$this->cid]["data"]["discuss_id"] = $id; }
public function getMessageType() { return $this->getData()["message_type"] ?? null; } public function getMessageType(): ?string { return $this->getData()["message_type"] ?? null; }
public function setMessageType($type) { self::$context[$this->cid]["data"]["message_type"] = $type; } public function setMessageType($type) { self::$context[$this->cid]["data"]["message_type"] = $type; }
@@ -103,30 +105,44 @@ class Context implements ContextInterface
* @param $msg * @param $msg
* @param bool $yield * @param bool $yield
* @return mixed * @return mixed
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function reply($msg, $yield = false) { public function reply($msg, $yield = false) {
switch ($this->getData()["message_type"]) { $data = $this->getData();
case "group": $conn = $this->getConnection();
case "private": if (!is_array($msg)) {
case "discuss": switch ($this->getData()["message_type"]) {
$this->setCache("has_reply", true); case "group":
$data = $this->getData(); case "private":
$conn = $this->getConnection(); case "discuss":
switch ($data["message_type"]) { $this->setCache("has_reply", true);
case "group": $operation["reply"] = $msg;
return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg); $operation["at_sender"] = false;
case "private": return (new ZMRobot($conn))->setCallback($yield)->callExtendedAPI(".handle_quick_operation", [
return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg); "context" => $data,
} "operation" => $operation
return null; ]);
}
return false;
} else {
$operation = $msg;
return (new ZMRobot($conn))->setCallback(false)->callExtendedAPI(".handle_quick_operation", [
"context" => $data,
"operation" => $operation
]);
} }
return false;
} }
/**
* @param $msg
* @param false $yield
* @return mixed|void
* @throws InterruptException
*/
public function finalReply($msg, $yield = false) { public function finalReply($msg, $yield = false) {
self::$context[$this->cid]["cache"]["block_continue"] = true; self::$context[$this->cid]["cache"]["block_continue"] = true;
if ($msg == "") return true; if ($msg != "") $this->reply($msg, $yield);
return $this->reply($msg, $yield); EventDispatcher::interrupt();
} }
/** /**
@@ -136,6 +152,7 @@ class Context implements ContextInterface
* @return string * @return string
* @throws InvalidArgumentException * @throws InvalidArgumentException
* @throws WaitTimeoutException * @throws WaitTimeoutException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") { public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"])) if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"]))
@@ -250,6 +267,7 @@ class Context implements ContextInterface
*/ */
public function getNumArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_NUMBER, $prompt_msg); } public function getNumArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_NUMBER, $prompt_msg); }
/** @noinspection PhpMissingReturnTypeInspection */
public function cloneFromParent() { public function cloneFromParent() {
set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]);
return context(); return context();

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\Context; namespace ZM\Context;

View File

@@ -1,4 +1,6 @@
<?php /** @noinspection PhpComposerExtensionStubsInspection */ <?php /** @noinspection PhpUnused */
/** @noinspection PhpComposerExtensionStubsInspection */
namespace ZM\DB; namespace ZM\DB;
@@ -34,7 +36,7 @@ class DB
* @return Table * @return Table
* @throws DbException * @throws DbException
*/ */
public static function table($table_name) { public static function table($table_name): Table {
if (Table::getTableInstance($table_name) === null) { if (Table::getTableInstance($table_name) === null) {
if (in_array($table_name, self::$table_list)) if (in_array($table_name, self::$table_list))
return new Table($table_name); return new Table($table_name);
@@ -60,7 +62,7 @@ class DB
* @return bool * @return bool
* @throws DbException * @throws DbException
*/ */
public static function unprepared($line) { public static function unprepared($line): bool {
try { try {
$conn = SqlPoolStorage::$sql_pool->get(); $conn = SqlPoolStorage::$sql_pool->get();
if ($conn === false) { if ($conn === false) {
@@ -84,6 +86,7 @@ class DB
* @throws DbException * @throws DbException
*/ */
public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) { public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) {
if (!is_array($params)) $params = [$params];
Console::debug("MySQL: " . $line . " | " . implode(", ", $params)); Console::debug("MySQL: " . $line . " | " . implode(", ", $params));
try { try {
$conn = SqlPoolStorage::$sql_pool->get(); $conn = SqlPoolStorage::$sql_pool->get();
@@ -133,7 +136,7 @@ class DB
} }
} }
public static function isTableExists($table) { public static function isTableExists($table): bool {
return in_array($table, self::$table_list); return in_array($table, self::$table_list);
} }
} }

View File

@@ -32,7 +32,7 @@ class SelectBody
/** /**
* @throws DbException * @throws DbException
*/ */
public function count() { public function count(): int {
$this->select_thing = ["count(*)"]; $this->select_thing = ["count(*)"];
$str = $this->queryPrepare(); $str = $this->queryPrepare();
$this->result = DB::rawQuery($str[0], $str[1]); $this->result = DB::rawQuery($str[0], $str[1]);
@@ -81,7 +81,7 @@ class SelectBody
public function getResult() { return $this->result; } public function getResult() { return $this->result; }
public function equals(SelectBody $body) { public function equals(SelectBody $body): bool {
if ($this->select_thing != $body->getSelectThing()) return false; if ($this->select_thing != $body->getSelectThing()) return false;
elseif ($this->where_thing == $body->getWhereThing()) return false; elseif ($this->where_thing == $body->getWhereThing()) return false;
else return true; else return true;
@@ -95,9 +95,9 @@ class SelectBody
/** /**
* @return array * @return array
*/ */
public function getWhereThing() { return $this->where_thing; } public function getWhereThing(): array { return $this->where_thing; }
private function queryPrepare() { private function queryPrepare(): array {
$msg = "SELECT " . implode(", ", $this->select_thing) . " FROM " . $this->table->getTableName(); $msg = "SELECT " . implode(", ", $this->select_thing) . " FROM " . $this->table->getTableName();
$sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '='); $sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '=');
if ($sql[0] != '') { if ($sql[0] != '') {

View File

@@ -1,4 +1,6 @@
<?php <?php /** @noinspection PhpUnused */
/** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\DB; namespace ZM\DB;
@@ -59,7 +61,7 @@ class Table
if ($msg == "") { if ($msg == "") {
$msg .= $k . " $operator ? "; $msg .= $k . " $operator ? ";
} else { } else {
$msg .= "AND " . $k . " $operator ?"; $msg .= " AND " . $k . " $operator ?";
} }
$param[] = $v; $param[] = $v;
} }

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\DB; namespace ZM\DB;

View File

@@ -0,0 +1,26 @@
<?php
namespace ZM\Entity;
class CQObject
{
public $type;
public $params;
public $start;
public $end;
public function __construct($type = "", $params = [], $start = 0, $end = 0) {
if ($type !== "") {
$this->type = $type;
$this->params = $params;
$this->start = $start;
$this->end = $end;
}
}
public static function fromArray($arr): CQObject {
return new CQObject($arr["type"], $arr["params"] ?? [], $arr["start"], $arr["end"]);
}
}

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Event; namespace ZM\Event;
@@ -9,7 +9,6 @@ use Error;
use Exception; use Exception;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Exception\InterruptException; use ZM\Exception\InterruptException;
use ZM\Exception\ZMException;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock; use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic; use ZM\Store\ZMAtomic;
@@ -32,7 +31,7 @@ class EventDispatcher
/** @var bool */ /** @var bool */
private $log = false; private $log = false;
/** @var int */ /** @var int */
private $eid = 0; private $eid;
/** @var int */ /** @var int */
public $status = self::STATUS_NORMAL; public $status = self::STATUS_NORMAL;
/** @var mixed */ /** @var mixed */
@@ -64,22 +63,18 @@ class EventDispatcher
public function __construct(string $class = '') { public function __construct(string $class = '') {
$this->class = $class; $this->class = $class;
try { $this->eid = ZMAtomic::get("_event_id")->add(1);
$this->eid = ZMAtomic::get("_event_id")->add(1); $list = LightCacheInside::get("wait_api", "event_trace");
$list = LightCacheInside::get("wait_api", "event_trace");
} catch (ZMException $e) {
$list = [];
}
if (isset($list[$class])) $this->log = true; if (isset($list[$class])) $this->log = true;
if ($this->log) Console::verbose("[事件分发{$this->eid}] 开始分发事件: " . $class); if ($this->log) Console::verbose("[事件分发{$this->eid}] 开始分发事件: " . $class);
} }
public function setRuleFunction(callable $rule = null) { public function setRuleFunction(callable $rule = null): EventDispatcher {
$this->rule = $rule; $this->rule = $rule;
return $this; return $this;
} }
public function setReturnFunction(callable $return_func) { public function setReturnFunction(callable $return_func): EventDispatcher {
$this->return_func = $return_func; $this->return_func = $return_func;
return $this; return $this;
} }
@@ -100,6 +95,7 @@ class EventDispatcher
} }
} }
if ($this->status === self::STATUS_RULE_FAILED) $this->status = self::STATUS_NORMAL; if ($this->status === self::STATUS_RULE_FAILED) $this->status = self::STATUS_NORMAL;
//TODO:没有过滤before的false可能会导致一些问题先观望一下
} catch (InterruptException $e) { } catch (InterruptException $e) {
$this->store = $e->return_var; $this->store = $e->return_var;
$this->status = self::STATUS_INTERRUPTED; $this->status = self::STATUS_INTERRUPTED;
@@ -116,6 +112,7 @@ class EventDispatcher
* @return bool * @return bool
* @throws InterruptException * @throws InterruptException
* @throws AnnotationException * @throws AnnotationException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public function dispatchEvent($v, $rule_func = null, ...$params) { public function dispatchEvent($v, $rule_func = null, ...$params) {
$q_c = $v->class; $q_c = $v->class;

View File

@@ -9,7 +9,6 @@ use Exception;
use Swoole\Timer; use Swoole\Timer;
use ZM\Annotation\AnnotationBase; use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser; use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnSave;
use ZM\Annotation\Swoole\OnTick; use ZM\Annotation\Swoole\OnTick;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\Console\Console; use ZM\Console\Console;
@@ -37,6 +36,7 @@ class EventManager
/** /**
* 注册所有计时器给每个进程 * 注册所有计时器给每个进程
* @throws Exception
*/ */
public static function registerTimerTick() { public static function registerTimerTick() {
$dispatcher = new EventDispatcher(OnTick::class); $dispatcher = new EventDispatcher(OnTick::class);

View File

@@ -6,6 +6,7 @@
namespace ZM\Event; namespace ZM\Event;
use Closure;
use Co; use Co;
use Error; use Error;
use Exception; use Exception;
@@ -16,6 +17,7 @@ use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool; use Swoole\Database\PDOPool;
use Swoole\Event; use Swoole\Event;
use Swoole\Process; use Swoole\Process;
use Throwable;
use ZM\Annotation\AnnotationParser; use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnCloseEvent; use ZM\Annotation\Swoole\OnCloseEvent;
@@ -25,6 +27,8 @@ use ZM\Annotation\Swoole\OnPipeMessageEvent;
use ZM\Annotation\Swoole\OnRequestEvent; use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\Annotation\Swoole\OnStart; use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Annotation\Swoole\OnTask;
use ZM\Annotation\Swoole\OnTaskEvent;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console; use ZM\Console\Console;
@@ -46,10 +50,8 @@ use ZM\Store\LightCacheInside;
use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool; use ZM\Store\Redis\ZMRedisPool;
use ZM\Store\WorkerCache; use ZM\Store\WorkerCache;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider; use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil; use ZM\Utils\HttpUtil;
use ZM\Utils\Terminal;
use ZM\Utils\ZMUtil; use ZM\Utils\ZMUtil;
class ServerEventHandler class ServerEventHandler
@@ -58,9 +60,9 @@ class ServerEventHandler
* @SwooleHandler("start") * @SwooleHandler("start")
*/ */
public function onStart() { public function onStart() {
global $terminal_id; //global $terminal_id;
$r = null; $r = null;
if ($terminal_id !== null) { /*if ($terminal_id !== null) {
ZMBuf::$terminal = $r = STDIN; ZMBuf::$terminal = $r = STDIN;
Event::add($r, function () use ($r) { Event::add($r, function () use ($r) {
$fget = fgets($r); $fget = fgets($r);
@@ -77,13 +79,18 @@ class ServerEventHandler
Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"); Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
} }
}); });
} }*/
Process::signal(SIGINT, function () use ($r) { Process::signal(SIGINT, function () use ($r) {
echo "\r"; if (zm_atomic("_int_is_reload")->get() === 1) {
Console::warning("Server interrupted(SIGINT) on Master."); zm_atomic("_int_is_reload")->set(0);
if ((Framework::$server->inotify ?? null) !== null) ZMUtil::reload();
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); } else {
ZMUtil::stop(); echo "\r";
Console::warning("Server interrupted(SIGINT) on Master.");
if ((Framework::$server->inotify ?? null) !== null)
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
ZMUtil::stop();
}
}); });
if (Framework::$argv["daemon"]) { if (Framework::$argv["daemon"]) {
$daemon_data = json_encode([ $daemon_data = json_encode([
@@ -120,6 +127,7 @@ class ServerEventHandler
* @SwooleHandler("WorkerStop") * @SwooleHandler("WorkerStop")
* @param $server * @param $server
* @param $worker_id * @param $worker_id
* @throws Exception
*/ */
public function onWorkerStop(Server $server, $worker_id) { public function onWorkerStop(Server $server, $worker_id) {
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) { if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
@@ -153,7 +161,7 @@ class ServerEventHandler
else server()->shutdown(); else server()->shutdown();
}); });
Console::info("Worker #{$server->worker_id} 启动中"); Console::verbose("Worker #{$server->worker_id} 启动中");
Framework::$server = $server; Framework::$server = $server;
//ZMBuf::resetCache(); //清空变量缓存 //ZMBuf::resetCache(); //清空变量缓存
//ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行 //ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
@@ -227,7 +235,7 @@ class ServerEventHandler
$this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等 $this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
//echo json_encode(debug_backtrace(), 128|256); //echo json_encode(debug_backtrace(), 128|256);
Console::success("Worker #" . $worker_id . " 已启动");
EventManager::registerTimerTick(); //启动计时器 EventManager::registerTimerTick(); //启动计时器
//ZMBuf::unsetCache("wait_start"); //ZMBuf::unsetCache("wait_start");
set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); set_coroutine_params(["server" => $server, "worker_id" => $worker_id]);
@@ -238,6 +246,7 @@ class ServerEventHandler
$dispatcher->dispatchEvents($server, $worker_id); $dispatcher->dispatchEvents($server, $worker_id);
if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) Console::debug("@OnStart 执行完毕"); if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) Console::debug("@OnStart 执行完毕");
else Console::warning("@OnStart 执行异常!"); else Console::warning("@OnStart 执行异常!");
Console::success("Worker #" . $worker_id . " 已启动");
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Worker加载出错停止服务"); Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
@@ -254,7 +263,7 @@ class ServerEventHandler
try { try {
Framework::$server = $server; Framework::$server = $server;
$this->loadAnnotations(); $this->loadAnnotations();
Console::debug("TaskWorker #" . $server->worker_id . " 已启动"); Console::success("TaskWorker #" . $server->worker_id . " 已启动");
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Worker加载出错停止服务"); Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
@@ -310,7 +319,7 @@ class ServerEventHandler
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg); Console::error("Uncaught " . get_class($e) . " when calling \"message\": " . $error_msg);
Console::trace(); Console::trace();
} }
@@ -410,9 +419,16 @@ class ServerEventHandler
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
unset(Context::$context[Co::getCid()]); unset(Context::$context[Co::getCid()]);
$type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? ""); $type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? "");
$access_token = explode(" ", $request->header["authorization"] ?? $request->get["token"] ?? "")[1] ?? ""; $access_token = explode(" ", $request->header["authorization"] ?? "")[1] ?? $request->get["token"] ?? "";
if (($a = ZMConfig::get("global", "access_token")) != "") { $token = ZMConfig::get("global", "access_token");
if ($access_token !== $a) { if ($token instanceof Closure) {
if (!$token($access_token)) {
$server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token);
return;
}
} elseif (is_string($token)) {
if ($access_token !== $token && $token !== "") {
$server->close($request->fd); $server->close($request->fd);
Console::warning("Unauthorized access_token: " . $access_token); Console::warning("Unauthorized access_token: " . $access_token);
return; return;
@@ -453,7 +469,7 @@ class ServerEventHandler
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"open\": " . $error_msg); Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace(); Console::trace();
} }
//EventHandler::callSwooleEvent("open", $server, $request); //EventHandler::callSwooleEvent("open", $server, $request);
@@ -500,7 +516,7 @@ class ServerEventHandler
Console::trace(); Console::trace();
} catch (Error $e) { } catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Uncaught Error " . get_class($e) . " when calling \"close\": " . $error_msg); Console::error("Uncaught " . get_class($e) . " when calling \"close\": " . $error_msg);
Console::trace(); Console::trace();
} }
ManagerGM::popConnect($fd); ManagerGM::popConnect($fd);
@@ -582,20 +598,53 @@ class ServerEventHandler
* @SwooleHandler("task") * @SwooleHandler("task")
* @param Server|null $server * @param Server|null $server
* @param Server\Task $task * @param Server\Task $task
* @return mixed
* @noinspection PhpUnusedParameterInspection * @noinspection PhpUnusedParameterInspection
*/ */
public function onTask(?Server $server, Server\Task $task) { public function onTask(?Server $server, Server\Task $task) {
$data = $task->data; if (isset($task->data["task"])) {
switch ($data["action"]) { $dispatcher = new EventDispatcher(OnTask::class);
case "runMethod": $dispatcher->setRuleFunction(function ($v) use ($task) {
$c = $data["class"]; /** @var OnTask $v */
$ss = new $c(); return $v->task_name == $task->data["task"];
$method = $data["method"]; });
$ps = $data["params"]; $dispatcher->setReturnFunction(function ($return) {
$task->finish($ss->$method(...$ps)); EventDispatcher::interrupt($return);
});
$params = $task->data["params"];
try {
$dispatcher->dispatchEvents(...$params);
} catch (Throwable $e) {
$finish["throw"] = $e;
}
if ($dispatcher->status === EventDispatcher::STATUS_EXCEPTION) {
$finish["result"] = null;
$finish["retcode"] = -1;
} else {
$finish["result"] = $dispatcher->store;
$finish["retcode"] = 0;
}
if (zm_atomic("server_is_stopped")->get() === 1) {
return;
}
$task->finish($finish);
} else {
try {
$dispatcher = new EventDispatcher(OnTaskEvent::class);
$dispatcher->setRuleFunction(function ($v) {
/** @var OnTaskEvent $v */
return eval("return " . $v->getRule() . ";");
});
$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::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::trace();
}
} }
return null;
} }
/** /**
@@ -628,7 +677,8 @@ class ServerEventHandler
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true); $composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($dir as $v) { 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 (is_dir($path . "/" . $v) && isset($composer["autoload"]["psr-4"][$v . "\\"]) && !in_array($composer["autoload"]["psr-4"][$v . "\\"], $composer["extra"]["exclude_annotate"] ?? [])) {
Console::verbose("Add " . $v . " to register path"); if (\server()->worker_id == 0)
Console::verbose("Add " . $v . " to register path");
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v); $parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v);
} }
} }

View File

@@ -9,6 +9,7 @@ use Exception;
use ZM\Annotation\Swoole\OnSetup; use ZM\Annotation\Swoole\OnSetup;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
use ZM\Console\TermColor;
use ZM\Event\ServerEventHandler; use ZM\Event\ServerEventHandler;
use ZM\Store\LightCache; use ZM\Store\LightCache;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
@@ -44,14 +45,15 @@ class Framework
self::$argv = $args; self::$argv = $args;
//定义常量
include_once "global_defines.php";
ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config');
ZMConfig::setEnv($args["env"] ?? ""); ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) { if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n"); die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
} }
//定义常量
include_once "global_defines.php";
ZMAtomic::init(); ZMAtomic::init();
try { try {
$sw = ZMConfig::get("global"); $sw = ZMConfig::get("global");
@@ -73,8 +75,6 @@ class Framework
die($e->getMessage()); die($e->getMessage());
} }
try { try {
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
$this->server_set = ZMConfig::get("global", "swoole");
Console::init( Console::init(
ZMConfig::get("global", "info_level") ?? 2, ZMConfig::get("global", "info_level") ?? 2,
self::$server, self::$server,
@@ -85,37 +85,45 @@ class Framework
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai"; $timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
date_default_timezone_set($timezone); date_default_timezone_set($timezone);
$this->server_set = ZMConfig::get("global", "swoole");
$this->parseCliArgs(self::$argv); $this->parseCliArgs(self::$argv);
$out = [ // 打印初始信息
"host" => ZMConfig::get("global", "host"), $out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
"port" => ZMConfig::get("global", "port"), if (!isset(ZMConfig::get("global", "swoole")["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
"log_level" => Console::getLevel(), else $out["worker"] = ZMConfig::get("global", "swoole")["worker_num"];
"version" => ZM_VERSION, $out["environment"] = $args["env"] === null ? "default" : $args["env"];
"config" => $args["env"] === null ? 'global.php' : $args["env"] $out["log_level"] = Console::getLevel();
]; $out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION; if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) { if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
$out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"]; $out["task_worker"] = ZMConfig::get("global", "swoole")["task_worker_num"];
} }
if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) { if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
$out["worker_num"] = $num; $conf = ZMConfig::get("global", "sql_config");
$out["mysql_pool"] = $conf["sql_database"] . "@" . $conf["sql_host"] . ":" . $conf["sql_port"];
} }
if (ZMConfig::get("global", "redis_config")["host"] !== "") {
$conf = ZMConfig::get("global", "redis_config");
$out["redis_pool"] = $conf["host"] . ":" . $conf["port"];
}
if (ZMConfig::get("global", "static_file_server")["status"] !== false) {
$out["static_file_server"] = "enabled";
}
if (self::$argv["show-php-ver"] !== false) {
$out["php_version"] = PHP_VERSION;
$out["swoole_version"] = SWOOLE_VERSION;
}
$out["working_dir"] = DataProvider::getWorkingDir(); $out["working_dir"] = DataProvider::getWorkingDir();
Console::printProps($out, $tty_width); self::printProps($out, $tty_width, $args["log-theme"] === null);
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
self::$server->set($this->server_set); self::$server->set($this->server_set);
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) { Console::setServer(self::$server);
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt"); self::printMotd($tty_width);
} else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
}
$motd = explode("\n", $motd);
foreach ($motd as $k => $v) {
$motd[$k] = substr($v, 0, $tty_width);
}
$motd = implode("\n", $motd);
echo $motd;
global $asd; global $asd;
$asd = get_included_files(); $asd = get_included_files();
// 注册 Swoole Server 的事件 // 注册 Swoole Server 的事件
@@ -167,12 +175,29 @@ class Framework
} catch (Exception $e) { } catch (Exception $e) {
Console::error("Framework初始化出现错误请检查"); Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage()); Console::error($e->getMessage());
Console::debug($e);
die; die;
} }
} }
private static function printMotd($tty_width) {
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
} else {
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
}
$motd = explode("\n", $motd);
foreach ($motd as $k => $v) {
$motd[$k] = substr($v, 0, $tty_width);
}
$motd = implode("\n", $motd);
echo $motd;
}
public function start() { public function start() {
self::$server->start(); self::$server->start();
zm_atomic("server_is_stopped")->set(1);
Console::setLevel(0);
} }
/** /**
@@ -223,16 +248,7 @@ class Framework
private function parseCliArgs($args) { private function parseCliArgs($args) {
$coroutine_mode = true; $coroutine_mode = true;
global $terminal_id; global $terminal_id;
$terminal_id = call_user_func(function () { $terminal_id = uuidgen();
try {
$data = random_bytes(16);
} catch (Exception $e) {
return "";
}
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)));
});
foreach ($args as $x => $y) { foreach ($args as $x => $y) {
switch ($x) { switch ($x) {
case 'disable-coroutine': case 'disable-coroutine':
@@ -280,6 +296,7 @@ class Framework
Console::$theme = $y; Console::$theme = $y;
} }
break; break;
case 'show-php-ver':
default: default:
//Console::info("Calculating ".$x); //Console::info("Calculating ".$x);
//dump($y); //dump($y);
@@ -289,7 +306,93 @@ class Framework
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL); if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
} }
public static function getTtyWidth() { private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
$tmp_line = $k . ": " . $v;
//Console::info("写入[".$tmp_line."]");
if (strlen($tmp_line) >= $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
$title_strlen = strlen($k . ": ");
$content_len = $line_width[$current_line] - $title_strlen;
$line_data[$current_line] = " " . $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= substr($v, 0, $content_len);
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
$rest = substr($v, $content_len);
++$current_line; // 带标题的第一行满了,折到第二行
do {
if ($colorful) $line_data[$current_line] = TermColor::color8(32);
$line_data[$current_line] .= " " . substr($rest, 0, $max_border - 2);
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
$rest = substr($rest, $max_border - 2);
++$current_line;
} while ($rest > $max_border - 2); // 循环,直到放完
} else { // 不需要折行
//Console::info("不需要折行");
$line_data[$current_line] = " " . $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= $v;
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
if ($max_border >= 57) {
if (strlen($tmp_line) >= intval(($max_border - 2) / 2)) { // 不需要折行,直接输出一个转下一行
//Console::info("不需要折行,直接输出一个转下一行");
++$current_line;
} else { // 输出很小,写到前面并分片
//Console::info("输出很小,写到前面并分片");
$space = intval($max_border / 2) - 2 - strlen($tmp_line);
$line_data[$current_line] .= str_pad("", $space);
$line_data[$current_line] .= "| "; // 添加分片
$line_width[$current_line] -= (strlen($tmp_line) + 3 + $space);
}
} else {
++$current_line;
}
}
}
public static function printProps($out, $tty_width, $colorful = true) {
$max_border = $tty_width < 65 ? $tty_width : 65;
if (LOAD_MODE == 0) echo Console::setColor("* Framework started with source mode.\n", $colorful ? "yellow" : "");
echo str_pad("", $max_border, "=") . PHP_EOL;
$current_line = 0;
$line_width = [];
$line_data = [];
foreach ($out as $k => $v) {
start:
if (!isset($line_width[$current_line])) {
$line_width[$current_line] = $max_border - 2;
}
//Console::info("行宽[$current_line]".$line_width[$current_line]);
if ($max_border >= 57) { // 很宽的时候,一行能放两个短行
if ($line_width[$current_line] == ($max_border - 2)) { //空行
self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border);
} else { // 不是空行,已经有东西了
$tmp_line = $k . ": " . $v;
//Console::info("[$current_line]即将插入后面的东西[".$tmp_line."]");
if (strlen($tmp_line) > $line_width[$current_line]) { // 地方不够,另起一行
$line_data[$current_line] = str_replace("| ", "", $line_data[$current_line]);
++$current_line;
goto start;
} else { // 地方够,直接写到后面并另起一行
$line_data[$current_line] .= $k . ": ";
if ($colorful) $line_data[$current_line] .= TermColor::color8(32);
$line_data[$current_line] .= $v;
if ($colorful) $line_data[$current_line] .= TermColor::RESET;
++$current_line;
}
}
} else { // 不够宽,直接写单行
self::writeNoDouble($k, $v, $line_data, $line_width, $current_line, $colorful, $max_border);
}
}
foreach ($line_data as $v) {
echo $v . PHP_EOL;
}
echo str_pad("", $max_border, "=") . PHP_EOL;
}
public static function getTtyWidth(): string {
return explode(" ", trim(exec("stty size")))[1]; return explode(" ", trim(exec("stty size")))[1];
} }
} }

View File

@@ -1,4 +1,6 @@
<?php <?php /** @noinspection PhpUnused */
/** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\Http; namespace ZM\Http;

View File

@@ -9,6 +9,7 @@ use Symfony\Component\Routing\RouteCollection;
use ZM\Annotation\Http\Controller; use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Http\RequestMapping;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Store\LightCacheInside;
class RouteManager class RouteManager
{ {
@@ -34,4 +35,26 @@ class RouteManager
self::$routes->add(md5($route_name), $route); 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

@@ -55,7 +55,7 @@ class QQBot
* @return EventDispatcher * @return EventDispatcher
* @throws Exception * @throws Exception
*/ */
public function dispatchBeforeEvents($data) { public function dispatchBeforeEvents($data): EventDispatcher {
$before = new EventDispatcher(CQBefore::class); $before = new EventDispatcher(CQBefore::class);
$before->setRuleFunction(function ($v) use ($data) { $before->setRuleFunction(function ($v) use ($data) {
return $v->cq_event == $data["post_type"]; return $v->cq_event == $data["post_type"];
@@ -70,6 +70,7 @@ class QQBot
/** /**
* @param $data * @param $data
* @throws InterruptException * @throws InterruptException
* @throws Exception
*/ */
private function dispatchEvents($data) { private function dispatchEvents($data) {
//Console::warning("最xia数据包".json_encode($data)); //Console::warning("最xia数据包".json_encode($data));

View File

@@ -27,6 +27,7 @@ class LightCache
* @param $config * @param $config
* @return bool|mixed * @return bool|mixed
* @throws Exception * @throws Exception
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function init($config) { public static function init($config) {
self::$config = $config; self::$config = $config;
@@ -87,6 +88,7 @@ class LightCache
* @param int $expire * @param int $expire
* @return mixed * @return mixed
* @throws ZMException * @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function set(string $key, $value, int $expire = -1) { public static function set(string $key, $value, int $expire = -1) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
@@ -120,6 +122,7 @@ class LightCache
* @param $value * @param $value
* @return bool|mixed * @return bool|mixed
* @throws ZMException * @throws ZMException
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function update(string $key, $value) { public static function update(string $key, $value) {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache."); if (self::$kv_table === null) throw new ZMException("not initialized LightCache.");
@@ -154,7 +157,7 @@ class LightCache
* @return bool * @return bool
* @throws Exception * @throws Exception
*/ */
public static function isset(string $key) { public static function isset(string $key): bool {
return self::get($key) !== null; return self::get($key) !== null;
} }
@@ -162,7 +165,7 @@ class LightCache
return self::$kv_table->del($key); return self::$kv_table->del($key);
} }
public static function getAll() { public static function getAll(): array {
$r = []; $r = [];
$del = []; $del = [];
foreach (self::$kv_table as $k => $v) { foreach (self::$kv_table as $k => $v) {
@@ -178,8 +181,11 @@ class LightCache
return $r; return $r;
} }
/**
* @param false $only_worker
* @throws Exception
*/
public static function savePersistence($only_worker = false) { public static function savePersistence($only_worker = false) {
// 下面将OnSave激活一下 // 下面将OnSave激活一下
if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) { if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) {
$dispatcher = new EventDispatcher(OnSave::class); $dispatcher = new EventDispatcher(OnSave::class);

View File

@@ -13,30 +13,24 @@ class LightCacheInside
/** @var Table[]|null */ /** @var Table[]|null */
private static $kv_table = []; private static $kv_table = [];
public static $last_error = ''; public static function init(): bool {
try {
public static function init() { self::createTable("wait_api", 3, 65536);
self::$kv_table["wait_api"] = new Table(3, 0); self::createTable("connect", 3, 64); //用于存单机器人模式下的机器人fd的
self::$kv_table["wait_api"]->column("value", Table::TYPE_STRING, 65536); self::createTable("static_route", 64, 256);//用于存储
self::$kv_table["connect"] = new Table(8, 0); } catch (ZMException $e) {
self::$kv_table["connect"]->column("value", Table::TYPE_STRING, 256);
$result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create();
if ($result === false) {
self::$last_error = '系统内存不足,申请失败';
return false; return false;
} else { } //用于存协程等待的状态内容的
return true; //self::createTable("worker_start", 2, 1024);//用于存启动服务器时的状态的
} return true;
} }
/** /**
* @param string $table * @param string $table
* @param string $key * @param string $key
* @return mixed|null * @return mixed|null
* @throws ZMException
*/ */
public static function get(string $table, string $key) { public static function get(string $table, string $key) {
if (!isset(self::$kv_table[$table])) throw new ZMException("not initialized LightCache");
$r = self::$kv_table[$table]->get($key); $r = self::$kv_table[$table]->get($key);
return $r === false ? null : json_decode($r["value"], true); return $r === false ? null : json_decode($r["value"], true);
} }
@@ -46,10 +40,8 @@ class LightCacheInside
* @param string $key * @param string $key
* @param string|array|int $value * @param string|array|int $value
* @return mixed * @return mixed
* @throws ZMException
*/ */
public static function set(string $table, string $key, $value) { public static function set(string $table, string $key, $value): bool {
if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
try { try {
return self::$kv_table[$table]->set($key, [ return self::$kv_table[$table]->set($key, [
"value" => json_encode($value, 256) "value" => json_encode($value, 256)
@@ -62,4 +54,17 @@ class LightCacheInside
public static function unset(string $table, string $key) { public static function unset(string $table, string $key) {
return self::$kv_table[$table]->del($key); return self::$kv_table[$table]->del($key);
} }
/**
* @param $name
* @param $size
* @param $str_size
* @throws ZMException
*/
private static function createTable($name, $size, $str_size) {
self::$kv_table[$name] = new Table($size, 0);
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
$r = self::$kv_table[$name]->create();
if ($r === false) throw new ZMException("内存不足,创建静态表失败!");
}
} }

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Store\Lock; namespace ZM\Store\Lock;
@@ -29,7 +29,7 @@ class SpinLock
} }
} }
public static function tryLock(string $key) { public static function tryLock(string $key): bool {
if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) {
return false; return false;
} }

View File

@@ -1,4 +1,6 @@
<?php /** @noinspection PhpComposerExtensionStubsInspection */ <?php /** @noinspection PhpUnused */
/** @noinspection PhpComposerExtensionStubsInspection */
namespace ZM\Store\Redis; namespace ZM\Store\Redis;
@@ -36,7 +38,7 @@ class ZMRedis
/** /**
* @return Redis * @return Redis
*/ */
public function get() { public function get(): Redis {
return $this->conn; return $this->conn;
} }

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\Store; namespace ZM\Store;

View File

@@ -16,7 +16,7 @@ class ZMAtomic
* @param $name * @param $name
* @return Atomic|null * @return Atomic|null
*/ */
public static function get($name) { public static function get($name): ?Atomic {
return self::$atomics[$name] ?? null; return self::$atomics[$name] ?? null;
} }
@@ -28,8 +28,10 @@ class ZMAtomic
self::$atomics[$k] = new Atomic($v); self::$atomics[$k] = new Atomic($v);
} }
self::$atomics["stop_signal"] = new Atomic(0); self::$atomics["stop_signal"] = new Atomic(0);
self::$atomics["_int_is_reload"] = new Atomic(0);
self::$atomics["wait_msg_id"] = new Atomic(0); self::$atomics["wait_msg_id"] = new Atomic(0);
self::$atomics["_event_id"] = new Atomic(0); self::$atomics["_event_id"] = new Atomic(0);
self::$atomics["server_is_stopped"] = new Atomic(0);
for ($i = 0; $i < 10; ++$i) { for ($i = 0; $i < 10; ++$i) {
self::$atomics["_tmp_" . $i] = new Atomic(0); self::$atomics["_tmp_" . $i] = new Atomic(0);
} }

View File

@@ -5,7 +5,6 @@ namespace ZM\Utils;
use Co; use Co;
use Exception;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock; use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic; use ZM\Store\ZMAtomic;
@@ -17,7 +16,7 @@ class CoMessage
* @param array $compare * @param array $compare
* @param int $timeout * @param int $timeout
* @return mixed * @return mixed
* @throws Exception * @noinspection PhpMissingReturnTypeInspection
*/ */
public static function yieldByWS(array $hang, array $compare, $timeout = 600) { public static function yieldByWS(array $hang, array $compare, $timeout = 600) {
$cid = Co::getuid(); $cid = Co::getuid();
@@ -40,7 +39,7 @@ class CoMessage
Co::suspend(); Co::suspend();
SpinLock::lock("wait_api"); SpinLock::lock("wait_api");
$sess = LightCacheInside::get("wait_api", "wait_api"); $sess = LightCacheInside::get("wait_api", "wait_api");
$result = $sess[$api_id]["result"]; $result = $sess[$api_id]["result"] ?? null;
unset($sess[$api_id]); unset($sess[$api_id]);
LightCacheInside::set("wait_api", "wait_api", $sess); LightCacheInside::set("wait_api", "wait_api", $sess);
SpinLock::unlock("wait_api"); SpinLock::unlock("wait_api");
@@ -49,7 +48,7 @@ class CoMessage
return $result; return $result;
} }
public static function resumeByWS() { public static function resumeByWS(): bool {
$dat = ctx()->getData(); $dat = ctx()->getData();
$last = null; $last = null;
SpinLock::lock("wait_api"); SpinLock::lock("wait_api");

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils;
@@ -11,7 +11,7 @@ class DataProvider
{ {
public static $buffer_list = []; public static $buffer_list = [];
public static function getResourceFolder() { public static function getResourceFolder(): string {
return self::getWorkingDir() . '/resources/'; return self::getWorkingDir() . '/resources/';
} }

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpMissingReturnTypeInspection */
namespace ZM\Utils; namespace ZM\Utils;
@@ -17,6 +17,7 @@ use ZM\Http\RouteManager;
class HttpUtil class HttpUtil
{ {
/** @noinspection PhpMissingReturnTypeInspection */
public static function parseUri($request, $response, $uri, &$node, &$params) { public static function parseUri($request, $response, $uri, &$node, &$params) {
$context = new RequestContext(); $context = new RequestContext();
$context->setMethod($request->server['request_method']); $context->setMethod($request->server['request_method']);

View File

@@ -0,0 +1,79 @@
<?php /** @noinspection PhpUnused */
namespace ZM\Utils;
use ZM\API\CQ;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Requests\ZMRequest;
class MessageUtil
{
/**
* 下载消息中 CQ 码的所有图片,通过 url
* @param $msg
* @param null $path
* @return array|false
*/
public static function downloadCQImage($msg, $path = null) {
$path = $path ?? DataProvider::getDataFolder() . "images/";
if (!is_dir($path)) mkdir($path);
$path = realpath($path);
if ($path === false) {
Console::warning("指定的路径错误不存在!");
return false;
}
$files = [];
$cq = CQ::getAllCQ($msg, true);
foreach ($cq as $v) {
if ($v->type == "image") {
$result = ZMRequest::downloadFile($v->params["url"], $path . "/" . $v->params["file"]);
if ($result === false) {
Console::warning("图片 " . $v->params["url"] . " 下载失败!");
return false;
}
$files[] = $path . "/" . $v->params["file"];
}
}
return $files;
}
/**
* 检查消息中是否含有图片 CQ 码
* @param $msg
* @return bool
*/
public static function containsImage($msg): bool {
$cq = CQ::getAllCQ($msg, true);
foreach ($cq as $v) {
if ($v->type == "image") {
return true;
}
}
return false;
}
/**
* 通过本地地址返回图片的 CQ 码
* type == 0 : 返回图片的 base64 CQ 码
* type == 1 : 返回图片的 file://路径 CQ 码(路径必须为绝对路径)
* type == 2 : 返回图片的 http://xxx CQ 码(默认为 /images/ 路径就是文件对应所在的目录)
* @param $file
* @param int $type
* @return string
*/
public static function getImageCQFromLocal($file, $type = 0): string {
switch ($type) {
case 0:
return CQ::image("base64://" . base64_encode(file_get_contents($file)));
case 1:
return CQ::image("file://" . $file);
case 2:
$info = pathinfo($file);
return CQ::image(ZMConfig::get("global", "http_reverse_link") . "/images/" . $info["basename"]);
}
return "";
}
}

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils;

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils;
@@ -15,6 +15,7 @@ trait SingletonTrait
/** /**
* @return self * @return self
* @noinspection PhpMissingReturnTypeInspection
*/ */
public static function getInstance() { public static function getInstance() {
if (null === self::$instance) { if (null === self::$instance) {

View File

@@ -0,0 +1,26 @@
<?php /** @noinspection PhpUnused */
namespace ZM\Utils;
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) {
if (!isset(server()->setting["task_worker_num"])) {
Console::warning("未开启 TaskWorker 进程,请先修改 global 配置文件启用!");
return false;
}
$r = server()->taskwait(["task" => $task_name, "params" => $params], $timeout);
return $r === false ? false : $r["result"];
}
}

View File

@@ -1,4 +1,4 @@
<?php <?php /** @noinspection PhpUnused */
namespace ZM\Utils; namespace ZM\Utils;
@@ -16,6 +16,8 @@ class Terminal
* @param string $cmd * @param string $cmd
* @param $resource * @param $resource
* @return bool * @return bool
* @noinspection PhpMissingReturnTypeInspection
* @noinspection PhpUnused
*/ */
public static function executeCommand(string $cmd, $resource) { public static function executeCommand(string $cmd, $resource) {
$it = explodeMsg($cmd); $it = explodeMsg($cmd);

View File

@@ -11,13 +11,19 @@ use Swoole\Timer;
use ZM\Console\Console; use ZM\Console\Console;
use ZM\Store\LightCache; use ZM\Store\LightCache;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic; use ZM\Store\ZMAtomic;
use ZM\Store\ZMBuf; use ZM\Store\ZMBuf;
class ZMUtil class ZMUtil
{ {
/**
* @throws Exception
*/
public static function stop() { public static function stop() {
if (SpinLock::tryLock("_stop_signal") === false) return;
Console::warning(Console::setColor("Stopping server...", "red")); Console::warning(Console::setColor("Stopping server...", "red"));
if (Console::getLevel() >= 4) Console::trace();
LightCache::savePersistence(); LightCache::savePersistence();
if (ZMBuf::$terminal !== null) if (ZMBuf::$terminal !== null)
Event::del(ZMBuf::$terminal); Event::del(ZMBuf::$terminal);
@@ -30,7 +36,17 @@ class ZMUtil
server()->stop(); server()->stop();
} }
/**
* @param int $delay
* @throws Exception
*/
public static function reload($delay = 800) { public static function reload($delay = 800) {
if (server()->worker_id !== -1) {
Console::info(server()->worker_id);
zm_atomic("_int_is_reload")->set(1);
system("kill -INT " . intval(server()->master_pid));
return;
}
Console::info(Console::setColor("Reloading server...", "gold")); Console::info(Console::setColor("Reloading server...", "gold"));
usleep($delay * 1000); usleep($delay * 1000);
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) { foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {

View File

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

View File

@@ -1,6 +1,8 @@
<?php #plain <?php /** @noinspection PhpUnused */ #plain
use Swoole\Atomic;
use Swoole\Coroutine; use Swoole\Coroutine;
use Swoole\WebSocket\Server;
use ZM\API\ZMRobot; use ZM\API\ZMRobot;
use ZM\Config\ZMConfig; use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM; use ZM\ConnectionManager\ManagerGM;
@@ -11,6 +13,7 @@ use ZM\Exception\RobotNotFoundException;
use ZM\Exception\ZMException; use ZM\Exception\ZMException;
use ZM\Framework; use ZM\Framework;
use ZM\Store\LightCacheInside; use ZM\Store\LightCacheInside;
use ZM\Store\ZMAtomic;
use ZM\Store\ZMBuf; use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider; use ZM\Utils\DataProvider;
use Swoole\Coroutine\System; use Swoole\Coroutine\System;
@@ -52,7 +55,7 @@ function getClassPath($class_name) {
* @param bool $ban_comma * @param bool $ban_comma
* @return array * @return array
*/ */
function explodeMsg($msg, $ban_comma = false) { function explodeMsg($msg, $ban_comma = false): array {
$msg = str_replace(" ", "\n", trim($msg)); $msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) { if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg); //$msg = str_replace("", "\n", $msg);
@@ -79,7 +82,7 @@ function unicode_decode($str) {
* @param $indoor_name * @param $indoor_name
* @return array * @return array
*/ */
function getAllClasses($dir, $indoor_name) { function getAllClasses($dir, $indoor_name): array {
if (!is_dir($dir)) return []; if (!is_dir($dir)) return [];
$list = scandir($dir); $list = scandir($dir);
$classes = []; $classes = [];
@@ -104,7 +107,7 @@ function getAllClasses($dir, $indoor_name) {
return $classes; return $classes;
} }
function matchPattern($pattern, $context) { function matchPattern($pattern, $context): bool {
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "") if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true; return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1)) if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
@@ -116,7 +119,7 @@ function matchPattern($pattern, $context) {
return false; return false;
} }
function split_explode($del, $str, $divide_en = false) { function split_explode($del, $str, $divide_en = false): array {
$str = explode($del, $str); $str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) { for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if ( if (
@@ -178,19 +181,19 @@ function matchArgs($pattern, $context) {
} else return false; } else return false;
} }
function connectIsQQ() { function connectIsQQ(): bool {
return ctx()->getConnection()->getName() == 'qq'; return ctx()->getConnection()->getName() == 'qq';
} }
function connectIsDefault() { function connectIsDefault(): bool {
return ctx()->getConnection()->getName() == 'default'; return ctx()->getConnection()->getName() == 'default';
} }
function connectIs($type) { function connectIs($type): bool {
return ctx()->getConnection()->getName() == $type; return ctx()->getConnection()->getName() == $type;
} }
function getAnnotations() { function getAnnotations(): array {
$s = debug_backtrace()[1]; $s = debug_backtrace()[1];
//echo json_encode($s, 128|256); //echo json_encode($s, 128|256);
$list = []; $list = [];
@@ -218,14 +221,14 @@ function set_coroutine_params($array) {
/** /**
* @return ContextInterface|null * @return ContextInterface|null
*/ */
function context() { function context(): ?ContextInterface {
return ctx(); return ctx();
} }
/** /**
* @return ContextInterface|null * @return ContextInterface|null
*/ */
function ctx() { function ctx(): ?ContextInterface {
$cid = Co::getCid(); $cid = Co::getCid();
$c_class = ZMConfig::get("global", "context_class"); $c_class = ZMConfig::get("global", "context_class");
if (isset(Context::$context[$cid])) { if (isset(Context::$context[$cid])) {
@@ -242,11 +245,11 @@ function ctx() {
function zm_debug($msg) { Console::debug($msg); } function zm_debug($msg) { Console::debug($msg); }
function onebot_target_id_name($message_type) { function onebot_target_id_name($message_type): string {
return ($message_type == "group" ? "group_id" : "user_id"); return ($message_type == "group" ? "group_id" : "user_id");
} }
function zm_sleep($s = 1) { function zm_sleep($s = 1): bool {
if (Coroutine::getCid() != -1) System::sleep($s); if (Coroutine::getCid() != -1) System::sleep($s);
else usleep($s * 1000 * 1000); else usleep($s * 1000 * 1000);
return true; return true;
@@ -261,27 +264,41 @@ function zm_yield() { Co::yield(); }
function zm_resume(int $cid) { Co::resume($cid); } function zm_resume(int $cid) { Co::resume($cid); }
function zm_timer_after($ms, callable $callable) { function zm_timer_after($ms, callable $callable) {
go(function () use ($ms, $callable) { Swoole\Timer::after($ms, function () use ($callable) {
Swoole\Timer::after($ms, $callable); call_with_catch($callable);
}); });
} }
function call_with_catch($callable) {
try {
$callable();
} 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::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::trace();
}
}
function zm_timer_tick($ms, callable $callable) { function zm_timer_tick($ms, callable $callable) {
if (zm_cid() === -1) { if (zm_cid() === -1) {
return go(function () use ($ms, $callable) { return go(function () use ($ms, $callable) {
Console::debug("Adding extra timer tick of " . $ms . " ms"); Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable); Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
}); });
} else { } else {
return Swoole\Timer::tick($ms, $callable); return Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
} }
} }
function zm_data_hash($v) { function zm_data_hash($v): string {
return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"])); return md5($v["user_id"] . "^" . $v["self_id"] . "^" . $v["message_type"] . "^" . ($v[$v["message_type"] . "_id"] ?? $v["user_id"]));
} }
function server() { function server(): ?Server {
return Framework::$server; return Framework::$server;
} }
@@ -290,7 +307,7 @@ function server() {
* @throws RobotNotFoundException * @throws RobotNotFoundException
* @throws ZMException * @throws ZMException
*/ */
function bot() { function bot(): ZMRobot {
if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) { if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) {
return ZMRobot::getRandom(); return ZMRobot::getRandom();
} elseif ($conn != -1) { } elseif ($conn != -1) {
@@ -315,6 +332,25 @@ function getAllFdByConnectType(string $type = 'default'): array {
return $fds; return $fds;
} }
function zm_atomic($name) { function zm_atomic($name): ?Atomic {
return \ZM\Store\ZMAtomic::get($name); return ZMAtomic::get($name);
} }
function uuidgen($uppercase = false): string {
try {
$data = random_bytes(16);
} catch (Exception $e) {
return "";
}
$data[6] = chr(ord($data[6]) & 0x0f | 0x40);
$data[8] = chr(ord($data[8]) & 0x3f | 0x80);
return $uppercase ? strtoupper(vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4))) :
vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
function working_dir() {
if (LOAD_MODE == 0) return WORKING_DIR;
elseif (LOAD_MODE == 1) return LOAD_MODE_COMPOSER_PATH;
elseif (LOAD_MODE == 2) return realpath('.');
return null;
}

View File

@@ -1,256 +0,0 @@
<?php
namespace ZMTest\Mock;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use ZM\API\ZMRobot;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Context\ContextInterface;
use ZM\Http\Response;
class Context implements ContextInterface
{
/**
* Context constructor.
* @param $cid
*/
public function __construct($cid) { }
/**
* @return Server
*/
public function getServer() {
// TODO: Implement getServer() method.
}
/**
* @return Frame
*/
public function getFrame() {
// TODO: Implement getFrame() method.
}
/**
* @return mixed
*/
public function getData() {
// TODO: Implement getData() method.
}
/**
* @param $data
* @return mixed
*/
public function setData($data) {
// TODO: Implement setData() method.
}
/**
* @return ConnectionObject
*/
public function getConnection() {
// TODO: Implement getConnection() method.
}
/**
* @return int|null
*/
public function getFd() {
// TODO: Implement getFd() method.
}
/**
* @return int
*/
public function getCid() {
// TODO: Implement getCid() method.
}
/**
* @return Response
*/
public function getResponse() {
// TODO: Implement getResponse() method.
}
/**
* @return Request
*/
public function getRequest() {
// TODO: Implement getRequest() method.
}
/**
* @return ZMRobot
*/
public function getRobot() {
// TODO: Implement getRobot() method.
}
/**
* @return mixed
*/
public function getUserId() {
// TODO: Implement getUserId() method.
}
/**
* @return mixed
*/
public function getGroupId() {
// TODO: Implement getGroupId() method.
}
/**
* @return mixed
*/
public function getDiscussId() {
// TODO: Implement getDiscussId() method.
}
/**
* @return string
*/
public function getMessageType() {
// TODO: Implement getMessageType() method.
}
/**
* @return mixed
*/
public function getRobotId() {
// TODO: Implement getRobotId() method.
}
/**
* @return mixed
*/
public function getMessage() {
// TODO: Implement getMessage() method.
}
/**
* @param $msg
* @return mixed
*/
public function setMessage($msg) {
// TODO: Implement setMessage() method.
}
/**
* @param $id
* @return mixed
*/
public function setUserId($id) {
// TODO: Implement setUserId() method.
}
/**
* @param $id
* @return mixed
*/
public function setGroupId($id) {
// TODO: Implement setGroupId() method.
}
/**
* @param $id
* @return mixed
*/
public function setDiscussId($id) {
// TODO: Implement setDiscussId() method.
}
/**
* @param $type
* @return mixed
*/
public function setMessageType($type) {
// TODO: Implement setMessageType() method.
}
/**
* @return mixed
*/
public function getCQResponse() {
// TODO: Implement getCQResponse() method.
}
/**
* @param $msg
* @param bool $yield
* @return mixed
*/
public function reply($msg, $yield = false) {
echo $msg.PHP_EOL;
// TODO: Implement reply() method.
}
/**
* @param $msg
* @param bool $yield
* @return mixed
*/
public function finalReply($msg, $yield = false) {
// TODO: Implement finalReply() method.
}
/**
* @param string $prompt
* @param int $timeout
* @param string $timeout_prompt
* @return mixed
*/
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
// TODO: Implement waitMessage() method.
}
/**
* @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed
*/
public function getArgs(&$arg, $mode, $prompt_msg) {
$r = $arg;
array_shift($r);
return array_shift($r);
// TODO: Implement getArgs() method.
}
/**
* @param $key
* @param $value
* @return mixed
*/
public function setCache($key, $value) {
// TODO: Implement setCache() method.
}
/**
* @param $key
* @return mixed
*/
public function getCache($key) {
// TODO: Implement getCache() method.
}
/**
* @return mixed
*/
public function cloneFromParent() {
// TODO: Implement cloneFromParent() method.
}
/**
* @return mixed
*/
public function copy() {
// TODO: Implement copy() method.
}
}

View File

@@ -1,111 +0,0 @@
<?php
/** @noinspection PhpFullyQualifiedNameUsageInspection */
/** @noinspection PhpComposerExtensionStubsInspection */
global $config;
/** bind host */
$config['host'] = '0.0.0.0';
/** bind port */
$config['port'] = 20001;
/** 框架开到公网或外部的HTTP访问链接通过 DataProvider::getFrameworkLink() 获取 */
$config['http_reverse_link'] = "http://127.0.0.1:" . $config['port'];
/** 框架是否启动debug模式 */
$config['debug_mode'] = false;
/** 存放框架内文件数据的目录 */
$config['zm_data'] = realpath(__DIR__ . "/../") . '/zm_data/';
/** 存放崩溃和运行日志的目录 */
$config['crash_dir'] = $config['zm_data'] . 'crash/';
/** 对应swoole的server->set参数 */
$config['swoole'] = [
'log_file' => $config['crash_dir'] . 'swoole_error.log',
'worker_num' => 8,
'dispatch_mode' => 2,
'max_coroutine' => 30000,
//'task_worker_num' => 4,
//'task_enable_coroutine' => true
];
/** 轻量字符串缓存,默认开启 */
$config['light_cache'] = [
"status" => true,
"size" => 2048, //最多允许储存的条数需要2的倍数
"max_strlen" => 4096, //单行字符串最大长度需要2的倍数
"hash_conflict_proportion" => 0.6 //Hash冲突率越大越好但是需要的内存更多
];
/** MySQL数据库连接信息host留空则启动时不创建sql连接池 */
$config['sql_config'] = [
'sql_host' => '',
'sql_port' => 3306,
'sql_username' => 'name',
'sql_database' => 'db_name',
'sql_password' => '',
'sql_enable_cache' => true,
'sql_reset_cache' => '0300',
'sql_options' => [
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false
],
'sql_no_exception' => false,
'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6
];
/** CQHTTP连接约定的token */
$config["access_token"] = "";
/** HTTP服务器固定请求头的返回 */
$config['http_header'] = [
'X-Powered-By' => 'zhamao-framework',
'Content-Type' => 'text/html; charset=utf-8'
];
/** HTTP服务器在指定状态码下回复的页面默认 */
$config['http_default_code_page'] = [
'404' => '404.html'
];
/** zhamao-framework在框架启动时初始化的atomic们 */
$config['init_atomics'] = [
//'custom_atomic_name' => 0, //自定义添加的Atomic
];
/** 终端日志显示等级0-4 */
$config["info_level"] = 2;
/** 自动保存计时器的缓存保存时间(秒) */
$config['auto_save_interval'] = 900;
/** 上下文接口类 implemented from ContextInterface */
$config['context_class'] = \ZMTest\Mock\Context::class;
/** 静态文件访问 */
$config['static_file_server'] = [
'status' => false,
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
'document_index' => [
'index.html'
]
];
/** 注册 Swoole Server 事件注解的类列表 */
$config['server_event_handler_class'] = [
\ZM\Event\ServerEventHandler::class,
];
/** 注册自定义指令的类 */
$config['command_register_class'] = [
//\Custom\Command\CustomCommand::class
];
/** 服务器启用的外部第三方和内部插件 */
$config['modules'] = [
'onebot' => true, // QQ机器人事件解析器如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭
];
return $config;

View File

@@ -40,10 +40,7 @@ class EventDispatcherTest extends TestCase
$dispatcher->setRuleFunction(function ($v) { return $v->match == "你好"; }); $dispatcher->setRuleFunction(function ($v) { return $v->match == "你好"; });
//$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe"); //$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe");
ob_start(); ob_start();
try { $dispatcher->dispatchEvents();
$dispatcher->dispatchEvents();
} catch (AnnotationException $e) {
}
$r = ob_get_clean(); $r = ob_get_clean();
echo $r; echo $r;
$this->assertStringContainsString("你好啊", $r); $this->assertStringContainsString("你好啊", $r);