Compare commits

...

71 Commits
1.0.0 ... 1.5.2

Author SHA1 Message Date
whale
c0ea068d04 update to 1.5.2 version
add ZM_VERSION const
2020-06-08 23:52:55 +08:00
whale
9ba58ff90f remove ModBase to default Module 2020-06-05 19:54:20 +08:00
whale
1a1cf0ad30 update to 1.5.1 version 2020-06-05 19:31:43 +08:00
whale
1de93b9dc1 update to 1.5.1 version
fix a warning bug
2020-06-05 19:31:18 +08:00
Whale
99e44eea3d Merge pull request #12 from 854854321/patch-1
Update ZMRequest.php
2020-06-05 19:25:20 +08:00
Whale
e67958a8d1 Update ZMRequest.php 2020-06-05 19:24:56 +08:00
Whale
23b3dc34e2 Merge pull request #11 from 854854321/master
Update README.md
2020-06-05 18:51:52 +08:00
775672d515 Update ZMRequest.php
增加了http请求 传输 数据体
2020-06-05 18:31:55 +08:00
40e17fab62 Update README.md 2020-06-05 16:02:13 +08:00
whale
59fde3d075 update to 1.5 version 2020-06-05 13:36:30 +08:00
whale
a8183757be update to 1.4.1 version
fix uncaught PDOException
2020-05-31 14:22:39 +08:00
whale
8ae5844649 fix a bug 2020-05-23 17:40:08 +08:00
whale
acc96b78db fix a bug 2020-05-23 17:29:09 +08:00
whale
802f975825 update to version 1.4 2020-05-23 17:23:29 +08:00
whale
76ee308b91 add some exception log feature 2020-05-10 23:45:45 +08:00
whale
d9eca5d7b1 fix finalReply not working 2020-05-10 18:31:44 +08:00
whale
5144bc2094 update LICENSE of composer 2020-05-10 18:27:59 +08:00
whale
f7418de868 update to 1.3.2 version 2020-05-10 18:26:48 +08:00
whale
2bcbdcd3ca support Middleware of TimerTick 2020-05-10 18:25:51 +08:00
whale
013c78dc77 fix OnTick context bug 2020-05-10 14:40:18 +08:00
whale
886816e3d5 update to 1.3.1 version
fix DataProvider::setJsonData bug
add Root document of default request
⚠️ change MySQL driver to PDO, mysqlnd required
improve exception catcher
2020-05-10 14:11:32 +08:00
whale
81db9c6ccb update Dockerfile to fit mysqlnd 2020-05-10 00:52:40 +08:00
whale
ec88c56137 update README.md 2020-05-08 18:45:40 +08:00
whale
acb4bdf9b4 update to 1.3.0 version totally. 2020-05-08 16:37:38 +08:00
whale
a1b013ee53 update to 1.3.0 version. 2020-05-06 17:26:50 +08:00
whale
086c65af26 fix some DataProvider bug. 2020-05-06 17:25:09 +08:00
whale
9b3a2e5296 fix some DataProvider bug. 2020-05-06 17:19:59 +08:00
whale
181f6430a4 update CQ.php 2020-05-06 15:01:32 +08:00
whale
d5c192108e update Dockerfile 2020-05-03 16:38:27 +08:00
whale
0c28dda808 update Dockerfile 2020-05-03 12:03:56 +08:00
whale
b61eeccdb8 fix composer requirement 2020-05-03 11:41:05 +08:00
whale
19d618f167 update README.md 2020-05-02 23:36:54 +08:00
whale
17ce0c20ae update SECURITY.md 2020-05-02 23:28:11 +08:00
whale
82a1f86bbd update to 1.2.1
add phar build script
2020-05-02 23:27:26 +08:00
whale
97e579b8a1 update README.md 2020-04-29 18:11:21 +08:00
whale
3f286c057b fix ZMRobot function name and deprecate CQAPI 2020-04-29 17:56:44 +08:00
whale
a94746b9fb update README.md 2020-04-29 15:58:24 +08:00
whale
f726be554a update SECURITY.md 2020-04-29 15:56:05 +08:00
whale
ac54459ec8 update README.md 2020-04-29 15:53:50 +08:00
whale
b4cef11a55 add MOTD 2020-04-29 15:38:57 +08:00
whale
169a751e0f update to 1.2 version
Generate systemd script
Default info_level set to 2
Modify & add some comment for Example module
Brand new Console
Add daemon command argument
Add #OnTick annotation
Add ZMRobot API class
2020-04-29 15:29:56 +08:00
whale
e1983d6dd8 update some argument 2020-04-29 11:56:28 +08:00
whale
52f01446b5 add systemd generator. 2020-04-29 11:52:08 +08:00
whale
fa699ac5e0 fix composer dependency version bugs. 2020-04-28 11:19:39 +08:00
Whale
0e952c39f5 Update README.md 2020-04-26 18:15:49 +08:00
Whale
16b885da54 Create SECURITY.md 2020-04-26 18:12:06 +08:00
whale
7e35596f9b update README.md 2020-04-26 17:39:45 +08:00
whale
3c41d17802 fix a bug 2020-04-26 17:25:41 +08:00
whale
4ba5e3fc57 add static.html 2020-04-26 17:16:49 +08:00
whale
920680ee3d add static file server and fix root mapping bug 2020-04-26 17:15:27 +08:00
whale
c632977ca1 update value comment 2020-04-26 16:04:02 +08:00
whale
94834c6625 update for class Annotation of Middleware 2020-04-26 15:56:06 +08:00
whale
e3ffeec782 update for IDE support 2020-04-26 15:08:09 +08:00
whale
438a204751 Merge remote-tracking branch 'origin/master' 2020-04-26 15:01:31 +08:00
whale
b0331f6346 fix some stupid bug 2020-04-26 15:01:18 +08:00
Whale
0cbb81788e Update README.md 2020-04-19 10:19:48 +08:00
Whale
7173da9ad2 Update README.md 2020-04-18 19:39:54 +08:00
Whale
302e2c60da Update README.md 2020-04-18 18:09:37 +08:00
whale
87e637f759 fix some bug and add HTTP image upload 2020-04-17 16:51:33 +08:00
whale
1d2aaf3c99 Normalization of Context class 2020-04-15 10:55:10 +08:00
whale
676527205f update README.md 2020-04-15 09:56:27 +08:00
whale
b560246efb add context() fetch data mode
enable coroutine override
2020-04-14 23:46:42 +08:00
whale
c1f720a0b3 update README.md 2020-03-29 16:31:10 +08:00
whale
9a28126765 add Middleware and release version 1.1.0 2020-03-29 16:29:02 +08:00
whale
c2fcdf9668 add Middleware 2020-03-25 22:41:47 +08:00
whale
6861d27629 add Middleware 2020-03-25 18:35:16 +08:00
whale
50a64d2d0b fix websocket close event 2020-03-20 14:50:12 +08:00
whale
80ec6ad030 update README.md 2020-03-19 18:34:43 +08:00
whale
a8ee9ee9c2 update README.md 2020-03-19 18:29:33 +08:00
whale
67a62415a0 update README.md 2020-03-19 18:27:55 +08:00
whale
4187bab913 add global_function and ZMRequest class 2020-03-19 17:43:27 +08:00
78 changed files with 3429 additions and 579 deletions

View File

@@ -2,4 +2,4 @@
if [ ! -d "/app/zhamao-framework/bin" ]; then
cp -r /app/zhamao-framework-bak/* /app/zhamao-framework/
fi
php /app/zhamao-framework/bin/start framework --disable-console-input
php /app/zhamao-framework/bin/start

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@
/vendor/
zm.json
/zm_data/
composer.lock
composer.lock
/resources/server.phar

View File

@@ -1,15 +1,29 @@
FROM phpswoole/swoole:4.4.15-php7.3
FROM ubuntu:18.04
WORKDIR /app/
RUN echo "Asia/Shanghai" > /etc/timezone
#RUN dpkg-configure -f noninteractive tzdata
ENV LANG C.UTF_8
ENV LC_ALL C.UTF-8
ENV LANGUAGE C.UTF-8
RUN apt-get update && apt-get install -y software-properties-common tzdata
RUN dpkg-reconfigure -f noninteractive tzdata
VOLUME ["/app/zhamao-framework/"]
RUN add-apt-repository ppa:ondrej/php && \
apt-get update && \
apt-get install php php-dev php-mbstring gcc make openssl \
php-mbstring php-json php-curl php-mysql -y && \
apt-get install wget composer -y && \
wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \
tar -zxvf v4.5.0.tar.gz && \
cd swoole-src-4.5.0/ && \
phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \
(echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}'))
ADD . /app/zhamao-framework
ADD . /app/zhamao-framework-bak
#RUN cd /app/zhamao-framework && composer update && composer clearcache
#RUN mv zhamao-framework-master zhamao-framework
WORKDIR /app/zhamao-framework
EXPOSE 20001
CMD ["bash", "/app/zhamao-framework-bak/.entry.sh"]
CMD ["/bin/bash", "-i", "/app/zhamao-framework-bak/.entry.sh"]

View File

@@ -2,14 +2,14 @@
[![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]()
[![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
[![版本](https://img.shields.io/badge/version-2020.3.2-green.svg)]()
[![版本](https://img.shields.io/badge/version-1.5-green.svg)]()
[![this counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/this.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=this)
[![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
[![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
一个异步、多平台兼容的 **聊天机器人** 框架
协程高性能的 **QQ 机器人 + Web 服务器** 开发框架(炸毛框架)
<img src="./resources/images/logo.png" height = "200" alt="炸毛框架" align=center/>
<img src="https://avatars0.githubusercontent.com/u/48620312" height = "200" alt="炸毛框架" align=center/>
## 简介
zhamao-framework 是一个基于 酷Q 的 PHP Swoole 的机器人框架,它会对 QQ 机器人收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。
@@ -18,43 +18,72 @@ zhamao-framework 是一个基于 酷Q 的 PHP Swoole 的机器人框架,它会
除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。
## 开始
先安装环境,环境安装见下方文档。
1. `composer create-project zhamao/framework-starter` 从模板新建基础文档结构进行使用
2. 你也可以直接到 **Release** 中下载最新的 phar 包,放入文件夹后 `php server.phar` 快速启动框架
3. 还可以使用 Dockerfile 构建 Docker 容器
## 文档
本项目文档正在努力编写中。
Pages托管[https://framework.zhamao.xin/](https://framework.zhamao.xin/)
Pages[https://framework.zhamao.xin/](https://framework.zhamao.xin/)
如果上面的访问较慢,可以访问国内服务器:[https://framework2.zhamao.xin/](https://framework2.zhamao.xin/)
国内服务器[https://framework2.zhamao.xin/](https://framework2.zhamao.xin/)
## 特点
- 支持多账号
- 灵活的注解事件绑定机制
- 支持下断点调试Psysh
- 易用的上下文,模块内随处可用
- 采用模块化编写,功能之间高内聚低耦合
- 常驻内存,全局缓存变量随处使用
- 自带 MySQL 查询器、数据库连接池等数据库连接方案
- 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口
- 静态文件服务器
- 支持 phar 一键打包
## 炸毛特色模块
| 模块名称 | 说明 | 模块地址 |
| ------------------ | -------------------------------- | ------------------------------------------------------------ |
| 微信公众号兼容模块 | 为框架提供微信公众号订阅号兼容层 | [zhamao-wechat-patch](https://github.com/zhamao-robot/zhamao-wechat-patch) |
| 通用模块 | 图片上传和下载模块 | [zhamao-general-tools](https://github.com/zhamao-robot/zhamao-general-tools) |
## 计划开发内容
- [X] WebSocket测试脚本客户端
- [X] Session 和中间层管理模块
- [X] 常驻服务脚本
- [X] 一些常用的通用 API 例如经济(用户积分、亲密度等)的模块
- [ ] 图灵机器人/腾讯AI 聊天模块
- [ ] 分词模块(可能会放弃计划,因为目前好用的分词都是其他语言的)
- [ ] HTTP 过滤器、Auth 模块、完整的 MVC 兼容(可能会放弃计划,因为框架主打机器人开发)
- [ ] Redis 连接池或开箱即用的相应功能内置
- [X] 1.3 版本使用上下文代替
- [X] 更好的 Logger稳定和漂亮的控制台输出
- [ ] 日志服务
- [X] 框架支持 Phar 打包(可能会比较靠后支持)
- [ ] 完整的单元测试(如果有需求则尽快开发)
- [X] 静态文件服务器
## 从 cqbot-swoole 升级
目前新的框架采用了全新的注解机制,所以旧版的框架上写的模块到新框架需要重新编写。当然为了减少工作量,新的框架也最大限度地保留了旧版框架编写的风格,一般情况下根据新版框架的文档仅需修改少量地方即可完成重写。
旧版框架并入了 `old` 分支,如果想继续使用旧版框架请移步分支。升级过程中如果遇到问题可以找作者。
## 贡献
## 贡献和捐赠
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护耗费精力较大所以非常欢迎对框架的贡献。
本项目为作者闲暇时间开发,如果觉得好用,不妨进行捐助~你的捐助会让我更加有动力完善插件,感谢你的支持!
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。
### 支付宝
![支付宝二维码](/resources/images/alipay_img.jpg)
## 关于
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人3276124472是作者写的一个高性能机器人曾获全国计算机设计大赛一等奖。
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ627577391或提交 Issue 进行疑难解答。
本项目在更内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
本项目在更内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。

13
SECURITY.md Normal file
View File

@@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| 1.2.x | :white_check_mark: |
| 1.1.x | :x: |
| 1.0.x | :x: |
## Reporting a Vulnerability
If you find a bug which is safety related, you should post a new issue named **Security Issue**, and I will check it as soon as possible.

60
bin/phar-build Executable file
View File

@@ -0,0 +1,60 @@
#!/usr/bin/env php
<?php /** @since 1.2.1 */
global $version;
echo "version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../composer.json"), true)["version"]) . PHP_EOL;
switch ($argv[1] ?? '') {
case '--normal':
case '':
build();
break;
case '--help':
case '-h':
default:
echo "\nzhamao-framework Phar builder.\n";
echo "\nUsage: " . $argv[0] . " [OPTION]";
echo "\n\n -h, --help\t\tShow this help menu";
echo "\n --with-wechat-patch\tReplace ModBase with wechat patch version and build your own phar package";
echo "\n --normal\t\tBuild your own phar package as normal options\n\n";
break;
}
function build($with_wechat_patch = false) {
if (ini_get('phar.readonly') == 1) {
die("You need to set \"phar.readonly\" to \"Off\"!\nSee: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing\n");
}
$filename = "server.phar";
@unlink(__DIR__ . '/../resources/' . $filename);
$phar = new Phar(__DIR__ . '/../resources/' . $filename);
$phar->startBuffering();
$src = realpath(__DIR__ . '/../');
$hello = file_get_contents($src . '/src/Module/Example/Hello.php');
$middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php');
unlink($src . '/src/Module/Example/Hello.php');
unlink($src . '/src/Module/Middleware/TimerMiddleware.php');
if ($with_wechat_patch) {
global $wechat_patch;
$wechat = base64_decode($wechat_patch);
} else {
$wechat = false;
}
if ($wechat !== false) {
echo "Using wechat patch.\n";
$modbase = file_get_contents($src . '/src/ZM/ModBase.php');
unlink($src . '/src/ZM/ModBase.php');
}
$phar->buildFromDirectory($src);
$phar->addFromString('tmp/Hello.php.bak', $hello);
$phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware);
if ($wechat !== false) {
$phar->addFromString('src/ZM/ModBase.php', $wechat);
file_put_contents($src . '/src/ZM/ModBase.php', $modbase);
}
//$phar->compressFiles(Phar::GZ);
$phar->setStub($phar->createDefaultStub('phar-starter.php'));
$phar->stopBuffering();
file_put_contents($src . '/src/Module/Example/Hello.php', $hello);
file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware);
echo "Successfully built. Location: " . $src . "/resources/$filename\n";
}

View File

@@ -10,6 +10,14 @@ require __DIR__ . '/../src/Scheduler/Scheduler.php';
Swoole\Coroutine::set([
'max_coroutine' => 30000,
]);
global $vendor_mode;
$vendor_mode = false;
if (mb_strpos(__DIR__, getcwd()) !== false && substr(str_replace(getcwd(), "", __DIR__), 0, 8) == "/vendor/") {
define("LOAD_MODE", 1); //composer项目模式
define("LOAD_MODE_COMPOSER_PATH", getcwd());
} else {
define("LOAD_MODE", 0); //正常模式
}
date_default_timezone_set("Asia/Shanghai");
@@ -24,21 +32,38 @@ switch ($argv[1] ?? '') {
}
});
break;
case 'phar-build':
array_shift($argv);
require_once 'phar-build';
break;
case 'systemd':
array_shift($argv);
require_once 'systemd';
break;
case '':
case 'framework':
case 'server':
if(!is_dir(__DIR__.'/../vendor/')){
if (!is_dir(__DIR__ . '/../vendor/') && LOAD_MODE == 0) {
echo "Warning: you have not update composer!\n";
exec("composer update", $out, $var);
if($var != 0) {
if ($var != 0) {
echo "You need to run \"composer update\" at root of zhamao-framework!\n";
die;
}
}
$loader = new FrameworkLoader($argv);
break;
case '--help':
case '-h':
echo "\nUsage: " . $argv[0] . " [OPTION]\n";
echo "\nzhamao-framework start script, provides several startup arguments.";
echo "\n\n -h, --help\t\tShow this help menu";
echo "\n framework, server\tstart main framework, this is default option";
echo "\n phar-build\t\tbuild a new phar archive";
echo "\n systemd\t\tgenerate a new systemd \".service\" file to use\n\n";
break;
default:
echo "Unknown option \"{$argv[1]}\"!\n";
echo "Unknown option \"{$argv[1]}\"!\n\"--help\" for more information\n";
break;
}

31
bin/systemd Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env php
<?php /** @since 1.2 */
switch ($argv[1] ?? '') {
case '--generate':
case '':
generate($argv);
break;
case '--help':
case '-h':
default:
echo "\nUsage: " . $argv[0] . " [OPTION]\n";
echo "\nzhamao-framework systemd generator.";
echo "\n\n -h, --help\t\tShow this help menu";
echo "\n --generate\tGenerate a systemd service file\n\n";
break;
}
function generate($argv) {
$s = "[Unit]\nDescription=zhamao-framework Daemon\nAfter=rc-local.service\n\n[Service]\nType=simple";
$s .= "\nUser=" . exec("whoami");
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd();
if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd'))
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server --disable-console-input";
else
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s);
echo "File successfully generated. Path: " . getcwd() . "/resources/zhamao.service\n";
}

View File

@@ -1,3 +0,0 @@
#!/bin/bash
composer update

View File

@@ -1,9 +1,9 @@
{
"name": "zhamao/framework",
"description": "high-performance intelligent assistant",
"description": "High performance QQ robot and web server development framework",
"minimum-stability": "stable",
"license": "proprietary",
"version": "1.0",
"license": "Apache-2.0",
"version": "1.5.2",
"authors": [
{
"name": "whale",
@@ -14,19 +14,20 @@
"email": "hugo_swift@yahoo.com"
}
],
"prefer-stable": true,
"bin": [
"bin/start"
],
"require": {
"php": ">=7.2",
"swoole/ide-helper": "@dev",
"ext-mbstring": "*",
"swlib/saber": "^1.0",
"doctrine/annotations": "^1.8",
"doctrine/annotations": "<1.10.2",
"ext-json": "*",
"ext-posix": "*",
"ext-ctype": "*"
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
"ext-ctype": "*",
"ext-pdo": "*",
"psy/psysh": "@stable"
}
}

141
config/file_header.json Normal file
View File

@@ -0,0 +1,141 @@
{
"ai": "application/postscript",
"aif": "audio/x-aiff",
"aifc": "audio/x-aiff",
"aiff": "audio/x-aiff",
"asc": "text/plain",
"au": "audio/basic",
"avi": "video/x-msvideo",
"bcpio": "application/x-bcpio",
"bin": "application/octet-stream",
"bmp": "image/bmp",
"cdf": "application/x-netcdf",
"class": "application/octet-stream",
"cpio": "application/x-cpio",
"cpt": "application/mac-compactpro",
"csh": "application/x-csh",
"css": "text/css",
"dcr": "application/x-director",
"dir": "application/x-director",
"djv": "image/vnd.djvu",
"djvu": "image/vnd.djvu",
"dll": "application/octet-stream",
"dms": "application/octet-stream",
"doc": "application/msword",
"dvi": "application/x-dvi",
"dxr": "application/x-director",
"eps": "application/postscript",
"etx": "text/x-setext",
"exe": "application/octet-stream",
"ez": "application/andrew-inset",
"gif": "image/gif",
"gtar": "application/x-gtar",
"hdf": "application/x-hdf",
"hqx": "application/mac-binhex40",
"htm": "text/html",
"html": "text/html",
"ice": "x-conference/x-cooltalk",
"ief": "image/ief",
"iges": "model/iges",
"igs": "model/iges",
"jpe": "image/jpeg",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"js": "application/x-javascript",
"kar": "audio/midi",
"latex": "application/x-latex",
"lha": "application/octet-stream",
"lzh": "application/octet-stream",
"m3u": "audio/x-mpegurl",
"man": "application/x-troff-man",
"me": "application/x-troff-me",
"mesh": "model/mesh",
"mid": "audio/midi",
"midi": "audio/midi",
"mif": "application/vnd.mif",
"mov": "video/quicktime",
"movie": "video/x-sgi-movie",
"mp2": "audio/mpeg",
"mp3": "audio/mpeg",
"mpe": "video/mpeg",
"mpeg": "video/mpeg",
"mpg": "video/mpeg",
"mpga": "audio/mpeg",
"ms": "application/x-troff-ms",
"msh": "model/mesh",
"mxu": "video/vnd.mpegurl",
"nc": "application/x-netcdf",
"oda": "application/oda",
"pbm": "image/x-portable-bitmap",
"pdb": "chemical/x-pdb",
"pdf": "application/pdf",
"pgm": "image/x-portable-graymap",
"pgn": "application/x-chess-pgn",
"png": "image/png",
"pnm": "image/x-portable-anymap",
"ppm": "image/x-portable-pixmap",
"ppt": "application/vnd.ms-powerpoint",
"ps": "application/postscript",
"qt": "video/quicktime",
"ra": "audio/x-realaudio",
"ram": "audio/x-pn-realaudio",
"ras": "image/x-cmu-raster",
"rgb": "image/x-rgb",
"rm": "audio/x-pn-realaudio",
"roff": "application/x-troff",
"rpm": "audio/x-pn-realaudio-plugin",
"rtf": "text/rtf",
"rtx": "text/richtext",
"sgm": "text/sgml",
"sgml": "text/sgml",
"sh": "application/x-sh",
"shar": "application/x-shar",
"silo": "model/mesh",
"sit": "application/x-stuffit",
"skd": "application/x-koan",
"skm": "application/x-koan",
"skp": "application/x-koan",
"skt": "application/x-koan",
"smi": "application/smil",
"smil": "application/smil",
"snd": "audio/basic",
"so": "application/octet-stream",
"spl": "application/x-futuresplash",
"src": "application/x-wais-source",
"sv4cpio": "application/x-sv4cpio",
"sv4crc": "application/x-sv4crc",
"swf": "application/x-shockwave-flash",
"t": "application/x-troff",
"tar": "application/x-tar",
"tcl": "application/x-tcl",
"tex": "application/x-tex",
"texi": "application/x-texinfo",
"texinfo": "application/x-texinfo",
"tif": "image/tiff",
"tiff": "image/tiff",
"tr": "application/x-troff",
"tsv": "text/tab-separated-values",
"txt": "text/plain",
"ustar": "application/x-ustar",
"vcd": "application/x-cdlink",
"vrml": "model/vrml",
"wav": "audio/x-wav",
"wbmp": "image/vnd.wap.wbmp",
"wbxml": "application/vnd.wap.wbxml",
"wml": "text/vnd.wap.wml",
"wmlc": "application/vnd.wap.wmlc",
"wmls": "text/vnd.wap.wmlscript",
"wmlsc": "application/vnd.wap.wmlscriptc",
"wrl": "model/vrml",
"xbm": "image/x-xbitmap",
"xht": "application/xhtml+xml",
"xhtml": "application/xhtml+xml",
"xls": "application/vnd.ms-excel",
"xml": "text/xml",
"xpm": "image/x-xpixmap",
"xsl": "text/xml",
"xwd": "image/x-xwindowdump",
"xyz": "chemical/x-xyz",
"zip": "application/zip",
"": "application/octet-stream"
}

View File

@@ -10,8 +10,11 @@ $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'] = WORKING_DIR.'/zm_data/';
$config['zm_data'] = realpath(__DIR__ . "/../").'/zm_data/';
/** 存放各个模块配置文件的目录 */
$config['config_dir'] = $config['zm_data'].'config/';
@@ -58,10 +61,22 @@ $config['init_atomics'] = [
'out_count' => 0, //消息发送调用send_*_msg的统计数量
'reload_time' => 0, //调用reload功能统计数量
'wait_msg_id' => 0, //协程挂起id自增
'info_level' => 0, //终端显示的log等级
'info_level' => 2, //终端显示的log等级
];
/** 自动保存的缓存保存时间(秒) */
$config['auto_save_interval'] = 900;
return $config;
/** 上下文接口类 implemented from ContextInterface */
$config['context_class'] = \ZM\Context\Context::class;
/** 静态文件访问 */
$config['static_file_server'] = [
'status' => false,
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
'document_index' => [
'index.html'
]
];
return $config;

6
config/motd.txt Normal file
View File

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

18
docker_mixed/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM richardchien/cqhttp:latest
RUN apt-get update && apt-get install -y software-properties-common tzdata
RUN dpkg-reconfigure -f noninteractive tzdata
RUN add-apt-repository ppa:ondrej/php && \
apt-get update && \
apt-get install php php-dev php-mbstring gcc make openssl \
php-mbstring php-json php-curl php-mysql -y && \
apt-get install wget composer -y && \
wget https://github.com/swoole/swoole-src/archive/v4.5.0.tar.gz && \
tar -zxvf v4.5.0.tar.gz && \
cd swoole-src-4.5.0/ && \
phpize && ./configure --enable-openssl --enable-mysqlnd && make -j2 && make install && \
(echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}'))
ADD start.sh /home/user/start.sh
RUN chown user:user /home/user/start.sh && chmod +x /home/user/start.sh
ADD https://github.com/zhamao-robot/zhamao-framework/archive/master.zip /home/user/master.zip
RUN chown user:user /home/user/master.zip && chmod 777 /home/user/master.zip
VOLUME ["/home/user/coolq","/home/user/zhamao-framework"]

6
docker_mixed/start.sh Normal file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
unzip master.zip
mv zhamao-framework-master/* zhamao-framework/
cd zhamao-framework
php bin/start

85
phar-starter.php Normal file
View File

@@ -0,0 +1,85 @@
<?php
global $is_phar;
use Framework\FrameworkLoader;
$is_phar = true;
if (substr(__DIR__, 0, 7) != 'phar://') {
die("You can not run this script directly!\n");
}
testEnvironment();
spl_autoload_register(function ($class) {
//echo $class."\n";
$exp = str_replace("\\", '/', $class);
$exp = __DIR__ . '/src/' . $exp . '.php';
if (is_file($exp)) {
require_once $exp;
}
});
loadPhp(__DIR__ . '/src');
Swoole\Coroutine::set([
'max_coroutine' => 30000,
]);
date_default_timezone_set("Asia/Shanghai");
define('WORKING_DIR', __DIR__);
define('FRAMEWORK_DIR', __DIR__);
define('LOAD_MODE', 2);
$s = new FrameworkLoader($argv);
function loadPhp($dir) {
$dirs = scandir($dir);
foreach ($dirs as $v) {
$path = $dir . '/' . $v;
if (is_dir($path)) {
loadPhp($path);
} else {
if (pathinfo($dir . '/' . $v)['extension'] == 'php') {
if(pathinfo($dir . '/' . $v)['basename'] == 'terminal_listener.php') continue;
//echo 'loading '.$path.PHP_EOL;
require_once $path;
}
}
}
}
function testEnvironment() {
$current_dir = realpath('.');
@mkdir($current_dir . '/config/');
if (!is_file($current_dir . '/config/global.php')) {
echo "Exporting default global config...\n";
$global = file_get_contents(__DIR__ . '/config/global.php');
$global = str_replace("WORKING_DIR", 'realpath(__DIR__ . "/../")', $global);
file_put_contents($current_dir . '/config/global.php', $global);
}
if (!is_file($current_dir . '/config/file_header.json')) {
echo "Exporting default file_header config...\n";
$global = file_get_contents(__DIR__ . '/config/file_header.json');
file_put_contents($current_dir . '/config/file_header.json', $global);
}
if (!is_dir($current_dir . '/resources')) mkdir($current_dir . '/resources');
if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src');
if (!is_dir($current_dir . '/src')) mkdir($current_dir . '/src');
if (!is_dir($current_dir . '/src/Module')) {
mkdir($current_dir . '/src/Module');
mkdir($current_dir . '/src/Module/Example');
file_put_contents($current_dir . '/src/Module/Example/Hello.php', file_get_contents(__DIR__ . '/tmp/Hello.php.bak'));
mkdir($current_dir . '/src/Module/Middleware');
file_put_contents($current_dir . '/src/Module/Middleware/TimerMiddleware.php', file_get_contents(__DIR__ . '/tmp/TimerMiddleware.php.bak'));
}
if (!is_dir($current_dir . '/src/Custom')) {
mkdir($current_dir . '/src/Custom');
mkdir($current_dir . '/src/Custom/Annotation');
mkdir($current_dir . '/src/Custom/Connection');
file_put_contents($current_dir . '/src/Custom/global_function.php', "<?php\n\n//这里写你的全局方法");
}
}

View File

@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This is example</title>
</head>
<body>
<h1>Hello zhamao!</h1>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@@ -0,0 +1,7 @@
<?php
//这里写你的全局函数
function phptest(){
echo "Nothing.\n";
}

View File

@@ -8,14 +8,20 @@
namespace Framework;
use co;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\WSConnection;
use ZM\Utils\ZMUtil;
use Exception;
class Console
{
static function setColor($string, $color = "")
{
/**
* @var false|resource
*/
public static $console_proc = null;
public static $pipes = [];
static function setColor($string, $color = "") {
switch ($color) {
case "red":
return "\x1b[38;5;203m" . $string . "\x1b[m";
@@ -25,6 +31,7 @@ class Console
return "\x1b[38;5;227m" . $string . "\x1b[m";
case "blue":
return "\033[34m" . $string . "\033[0m";
case "pink": // I really don't know what stupid color it is.
case "lightpurple":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightblue":
@@ -33,8 +40,6 @@ class Console
return "\x1b[38;5;214m" . $string . "\x1b[m";
case "gray":
return "\x1b[38;5;59m" . $string . "\x1b[m";
case "pink":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightlightblue":
return "\x1b[38;5;63m" . $string . "\x1b[m";
default:
@@ -42,9 +47,8 @@ class Console
}
}
static function error($obj, $head = null)
{
if ($head === null) $head = date("[H:i:s ") . "ERROR] ";
static function error($obj, $head = null) {
if ($head === null) $head = date("[H:i:s] ") . "[E] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
@@ -58,90 +62,80 @@ class Console
echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n");
}
static function warning($obj, $head = null)
{
if ($head === null) $head = date("[H:i:s") . " WARN] ";
static function warning($obj, $head = null) {
if ($head === null) $head = date("[H:i:s]") . " [W] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
if (ZMBuf::$atomics["info_level"]->get() >= 1) {
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "blue" : "yellow") . "\n");
}
echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n");
}
static function info($obj, $head = null)
{
if ($head === null) $head = date("[H:i:s ") . "INFO] ";
static function info($obj, $head = null) {
if ($head === null) $head = date("[H:i:s] ") . "[I] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
if (ZMBuf::$atomics["info_level"]->get() >= 2) {
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, in_array("--white-term", FrameworkLoader::$argv) ? "black" : "lightblue") . "\n");
}
echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n");
}
static function log($obj, $color = "")
{
static function success($obj, $head = null) {
if ($head === null) $head = date("[H:i:s] ") . "[S] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (ZMBuf::$atomics["info_level"]->get() >= 2) {
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "green") . "\n");
}
}
static function verbose($obj, $head = null) {
if ($head === null) $head = date("[H:i:s] ") . "[V] ";
if (ZMBuf::$atomics["info_level"]->get() >= 3) {
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "blue") . "\n");
}
}
static function debug($msg) {
if (ZMBuf::$atomics["info_level"]->get() >= 4) Console::log(date("[H:i:s] ") . "[D] " . $msg, 'gray');
}
static function log($obj, $color = "") {
if (!is_string($obj)) var_dump($obj);
else echo(self::setColor($obj, $color) . "\n");
}
static function msg($obj, $self_id = "")
{
if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) {
if (!isset($obj["post_type"])) {
switch ($obj["action"]) {
case "send_private_msg":
$msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue");
$msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue");
$msg .= Console::setColor(" > ", "gray");
$msg .= $obj["params"]["message"];
Console::log($msg);
break;
case "send_group_msg":
//TODO: 写新的控制台消息API消息处理
Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_discuss_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_msg":
$obj["action"] = "send_" . $obj["message_type"] . "_msg";
self::msg($obj);
break;
case "send_wechat_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
default:
break;
}
} else {
if ($obj["post_type"] == "message") {
switch ($obj["message_type"]) {
case "group":
//
//TODO: 写新的控制台消息event处理
case "private":
case "discuss":
case "wechat":
}
}
}
}
}
static function stackTrace()
{
static function stackTrace() {
$log = "Stack trace:\n";
$trace = debug_backtrace();
//array_shift($trace);
@@ -165,17 +159,56 @@ class Console
echo $log;
}
static function listenConsole()
{
if (in_array('--disable-console-input', FrameworkLoader::$argv)) {
static function listenConsole() {
if (in_array('--disable-console-input', FrameworkLoader::$argv) || in_array('--debug-mode', FrameworkLoader::$argv)) {
self::info("ConsoleCommand disabled.");
return;
}
go(function () {
while (true) {
$cmd = trim(co::fread(STDIN));
if (self::executeCommand($cmd) === false) break;
global $terminal_id;
global $port;
$port = ZMBuf::globals("port");
$vss = new SwooleEventAt();
$vss->type = "open";
$vss->level = 256;
$vss->rule = "connectType:terminal";
$terminal_id = call_user_func(function () {
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)));
});
$vss->callback = function(?WSConnection $conn) use ($terminal_id){
$req = ctx()->getRequest();
if($conn->getType() != "terminal") return false;
if(($req->header["x-terminal-id"] ?? "") != $terminal_id || ($req->header["x-pid"] ?? "") != posix_getpid()) {
$conn->close();
return false;
}
return false;
};
ZMBuf::$events[SwooleEventAt::class][] = $vss;
$vss2 = new SwooleEventAt();
$vss2->type = "message";
$vss2->rule = "connectType:terminal";
$vss2->callback = function(?WSConnection $conn){
if($conn->getType() != "terminal") return false;
$cmd = ctx()->getFrame()->data;
self::executeCommand($cmd);
return false;
};
ZMBuf::$events[SwooleEventAt::class][] = $vss2;
go(function () {
global $terminal_id, $port;
$descriptorspec = array(
0 => STDIN,
1 => STDOUT,
2 => STDERR
);
self::$console_proc = proc_open('php -r \'$terminal_id = "'.$terminal_id.'";$port = '.$port.';require "'.__DIR__.'/terminal_listener.php";\'', $descriptorspec, $pipes);
});
}
@@ -183,10 +216,18 @@ class Console
* @param string $cmd
* @return bool
*/
private static function executeCommand(string $cmd)
{
private static function executeCommand(string $cmd) {
$it = explodeMsg($cmd);
switch ($it[0] ?? '') {
case 'logtest':
Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)");
Console::error("This is error msg. (0)");
Console::warning("This is warning msg. (1)");
Console::info("This is info msg. (2)");
Console::success("This is success msg. (2)");
Console::verbose("This is verbose msg. (3)");
Console::debug("This is debug msg. (4)");
return true;
case 'call':
$class_name = $it[1];
$function_name = $it[2];
@@ -203,6 +244,9 @@ class Console
case 'echo':
Console::info($it[1]);
return true;
case 'color':
Console::log($it[2], $it[1]);
return true;
case 'stop':
ZMUtil::stop();
return false;
@@ -210,17 +254,22 @@ class Console
case 'r':
ZMUtil::reload();
return false;
case 'save':
$origin = ZMBuf::$atomics["info_level"]->get();
//ZMBuf::$atomics["info_level"]->set(3);
DataProvider::saveBuffer();
//ZMBuf::$atomics["info_level"]->set($origin);
return true;
case '':
return true;
default:
Console::info("Command not found: " . $it[0]);
Console::info("Command not found: " . $cmd);
return true;
}
}
public static function withSleep(string $string, int $int)
{
public static function withSleep(string $string, int $int) {
self::info($string);
sleep($int);
}
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Framework;
use ZM\Annotation\Swoole\OnSave;
class DataProvider
{
public static $buffer_list = [];
public static function getResourceFolder() {
return self::getWorkingDir() . '/resources/';
}
public static function getWorkingDir() {
if(LOAD_MODE == 0) return WORKING_DIR;
elseif (LOAD_MODE == 1) return LOAD_MODE_COMPOSER_PATH;
elseif (LOAD_MODE == 2) return realpath('.');
return null;
}
public static function getDataConfig() {
return CONFIG_DIR;
}
public static function addSaveBuffer($buf_name, $sub_folder = null) {
$name = ($sub_folder ?? "") . "/" . $buf_name . ".json";
self::$buffer_list[$buf_name] = $name;
Console::debug("Added " . $buf_name . " at $sub_folder");
ZMBuf::set($buf_name, self::getJsonData($name));
}
public static function saveBuffer() {
$head = Console::setColor(date("[H:i:s] ") . "[V] Saving buffer......", "blue");
if (ZMBuf::$atomics["info_level"]->get() >= 3)
echo $head;
foreach (self::$buffer_list as $k => $v) {
Console::debug("Saving " . $k . " to " . $v);
self::setJsonData($v, ZMBuf::get($k));
}
foreach (ZMBuf::$events[OnSave::class] ?? [] as $v) {
$c = $v->class;
$method = $v->method;
$class = new $c();
Console::debug("Calling @OnSave: $c -> $method");
$class->$method();
}
if (ZMBuf::$atomics["info_level"]->get() >= 3)
echo Console::setColor("saved", "blue") . PHP_EOL;
}
public static function getFrameworkLink() {
return ZMBuf::globals("http_reverse_link");
}
public static function getJsonData(string $string) {
if (!file_exists(self::getDataConfig() . $string)) return [];
return json_decode(file_get_contents(self::getDataConfig() . $string), true);
}
public static function setJsonData($filename, array $args) {
$pathinfo = pathinfo($filename);
if (!is_dir(self::getDataConfig() . $pathinfo["dirname"])) {
Console::debug("Making Directory: " . self::getDataConfig() . $pathinfo["dirname"]);
mkdir(self::getDataConfig() . $pathinfo["dirname"]);
}
$r = file_put_contents(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING));
if ($r === false) {
Console::warning("无法保存文件: " . $filename);
}
}
public static function getDataFolder() {
return ZM_DATA;
}
}

View File

@@ -3,6 +3,11 @@
namespace Framework;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use Swoole\Http\Request;
use Swoole\Runtime;
use Swoole\WebSocket\Frame;
use ZM\Event\EventHandler;
use Exception;
use Swoole\WebSocket\Server;
@@ -34,13 +39,23 @@ class FrameworkLoader
public function __construct($args = []) {
if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!");
self::$instance = $this;
self::$argv = $args;
chdir(__DIR__ . '/../..');
define('WORKING_DIR', getcwd());
$this->requireGlobalFunctions();
if (LOAD_MODE == 0) define("WORKING_DIR", getcwd());
elseif(LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
$this->registerAutoloader('classLoader');
self::$settings = new GlobalConfig();
if (self::$settings->get("debug_mode") === true) {
$args[] = "--debug-mode";
$args[] = "--disable-console-input";
}
self::$argv = $args;
if (!in_array("--debug-mode", self::$argv)) {
Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
}
self::$settings = new GlobalConfig();
ZMBuf::$globals = self::$settings;
if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file");
$this->defineProperties();
@@ -49,18 +64,53 @@ class FrameworkLoader
$this->selfCheck();
try {
$this->server = new Server(self::$settings->get("host"), self::$settings->get("port"));
if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
$this->server->set(self::$settings->get("swoole"));
$settings = self::$settings->get("swoole");
if (in_array("--daemon", $args)) {
$settings["daemonize"] = 1;
Console::log("已启用守护进程,输出重定向到 " . $settings["log_file"]);
self::$argv[] = "--disable-console-input";
}
$this->server->set($settings);
$this->server->on("WorkerStart", [$this, "onWorkerStart"]);
$this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); });
$this->server->on("message", function ($server, Frame $frame) {
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd);
EventHandler::callSwooleEvent("message", $server, $frame);
});
$this->server->on("request", function ($request, $response) {
$response = new Response($response);
Console::debug("Receiving Http request event, cid=" . Co::getCid());
EventHandler::callSwooleEvent("request", $request, $response);
});
$this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); });
$this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); });
$this->server->on("open", function ($server, Request $request) {
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
EventHandler::callSwooleEvent("open", $server, $request);
});
$this->server->on("close", function ($server, $fd) {
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
EventHandler::callSwooleEvent("close", $server, $fd);
});
ZMBuf::initAtomic();
Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port"));
if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
if (in_array("--log-error", $args)) ZMBuf::$atomics["info_level"]->set(0);
if (in_array("--log-warning", $args)) ZMBuf::$atomics["info_level"]->set(1);
if (in_array("--log-info", $args)) ZMBuf::$atomics["info_level"]->set(2);
if (in_array("--log-verbose", $args)) ZMBuf::$atomics["info_level"]->set(3);
if (in_array("--log-debug", $args)) ZMBuf::$atomics["info_level"]->set(4);
Console::log(
"host: " . self::$settings->get("host") .
", port: " . self::$settings->get("port") .
", log_level: " . ZMBuf::$atomics["info_level"]->get() .
", version: " . ZM_VERSION .
"\nworking_dir: " . DataProvider::getWorkingDir()
);
global $motd;
if (!file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
echo $motd;
} else {
echo file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
}
if (in_array("--debug-mode", self::$argv))
Console::warning("You are in debug mode, do not use in production!");
$this->server->start();
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
@@ -70,7 +120,7 @@ class FrameworkLoader
}
private function requireGlobalFunctions() {
require __DIR__ . '/global_functions.php';
require_once __DIR__ . '/global_functions.php';
}
private function registerAutoloader(string $string) {
@@ -80,6 +130,7 @@ class FrameworkLoader
private function defineProperties() {
define("ZM_START_TIME", microtime(true));
define("ZM_DATA", self::$settings->get("zm_data"));
define("ZM_VERSION", json_decode(file_get_contents(__DIR__."/../../composer.json"), true)["version"] ?? "unknown");
define("CONFIG_DIR", self::$settings->get("config_dir"));
define("CRASH_DIR", self::$settings->get("crash_dir"));
@mkdir(ZM_DATA);
@@ -89,12 +140,15 @@ class FrameworkLoader
define("ZM_MATCH_FIRST", 1);
define("ZM_MATCH_NUMBER", 2);
define("ZM_MATCH_SECOND", 3);
define("ZM_BREAKPOINT", 'if(in_array("--debug-mode", \Framework\FrameworkLoader::$argv)) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));');
}
private function selfCheck() {
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
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 (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
if (!extension_loaded("ctype")) die("Can not find ctype extension.\n");
if (!function_exists("mb_substr")) die("Can not find mbstring 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");
@@ -103,9 +157,26 @@ class FrameworkLoader
return true;
}
/**
* @param \Swoole\Server $server
* @param $worker_id
* @throws AnnotationException
*/
public function onWorkerStart(\Swoole\Server $server, $worker_id) {
self::$instance = $this;
self::$run_time = microtime(true);
EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
}
}
}
global $motd;
$motd = <<<EOL
______
|__ / |__ __ _ _ __ ___ __ _ ___
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
/ /_| | | | (_| | | | | | | (_| | (_) |
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
EOL;

View File

@@ -19,7 +19,7 @@ class GlobalConfig
public function __construct() {
/** @noinspection PhpIncludeInspection */
include_once WORKING_DIR.'/config/global.php';
include_once DataProvider::getWorkingDir() . '/config/global.php';
global $config;
$this->success = true;
$this->config = $config;
@@ -30,4 +30,8 @@ class GlobalConfig
if ($r === null) return null;
return $r;
}
}
public function getAll() {
return $this->config;
}
}

View File

@@ -1,10 +0,0 @@
<?php
namespace Framework;
class InfoLevel
{
const INFO = 0;
}

View File

@@ -1,18 +0,0 @@
<?php
namespace Framework;
use Swoole\Coroutine\System;
class Logger
{
private static function getTimeFormat($type = "I") {
return "[" . date("Y-m-d H:i:s") . "]\t[" . $type . "]\t";
}
public static function writeSwooleLog($log) {
System::writeFile(CRASH_DIR . "swoole_error.log", "\n" . self::getTimeFormat() . $log, FILE_APPEND);
}
}

View File

@@ -9,10 +9,9 @@
namespace Framework;
use Swoole\Atomic;
use Swoole\Database\PDOPool;
use swoole_atomic;
use ZM\connection\WSConnection;
use ZM\Utils\Scheduler;
use ZM\Utils\SQLPool;
class ZMBuf
{
@@ -22,11 +21,10 @@ class ZMBuf
/** @var WSConnection[] */
static $connect = [];//储存连接实例的数组
//Scheduler计划任务连接实例只可以在单worker_num时使用
/** @var Scheduler|null */
static $scheduler = null;
static $scheduler = null; //This is stupid warning...
//Swoole SQL连接池多进程下每个进程一个连接池
/** @var SQLPool */
/** @var PDOPool */
static $sql_pool = null;//保存sql连接池的类
//只读的数据可以在多worker_num下使用
@@ -46,13 +44,14 @@ class ZMBuf
// Atomic可跨进程读写的原子计数任何地方均可使用
/** @var null|swoole_atomic */
static $info_level = null;//保存log等级的原子计数
/** @var swoole_atomic $reload_time */
public static $events = [];
/** @var Atomic[] */
public static $atomics;
public static $req_mapping = [];
public static $config = [];
public static $context = [];
public static $instance = [];
public static $context_class = [];
static function get($name, $default = null) {
return self::$cache[$name] ?? $default;
@@ -100,13 +99,14 @@ class ZMBuf
}
static function config($config_name) {
return self::$config ?? null;
return self::$config[$config_name] ?? null;
}
public static function resetCache() {
self::$cache = [];
self::$connect = [];
self::$time_nlp = null;
self::$instance = [];
}
/**
@@ -117,4 +117,4 @@ class ZMBuf
self::$atomics[$k] = new Atomic($v);
}
}
}
}

View File

@@ -1,5 +1,13 @@
<?php
use Framework\Console;
use Framework\DataProvider;
use Framework\ZMBuf;
use Swoole\Coroutine\System;
use ZM\Context\ContextInterface;
use ZM\Utils\ZMUtil;
function classLoader($p) {
$filepath = getClassPath($p);
if ($filepath === null)
@@ -8,18 +16,23 @@ function classLoader($p) {
try {
require_once $filepath;
} catch (Exception $e) {
echo "Error when finding class: ".$p.PHP_EOL;
echo "Error when finding class: " . $p . PHP_EOL;
die;
}
}
function getClassPath($class_name) {
$dir = str_replace("\\", "/", $class_name);
$dir = WORKING_DIR . "/src/" . $dir . ".php";
//echo "@@@".$dir.PHP_EOL;
$dir = str_replace("\\", "/", $dir);
if (file_exists($dir)) return $dir;
else return null;
$dir2 = WORKING_DIR . "/src/" . $dir . ".php";
//echo "@@@".$dir2.PHP_EOL;
$dir2 = str_replace("\\", "/", $dir2);
if (file_exists($dir2)) return $dir2;
else {
$dir = DataProvider::getWorkingDir() . "/src/" . $dir . ".php";
//echo "###".$dir.PHP_EOL;
if (file_exists($dir)) return $dir;
else return null;
}
}
/**
@@ -56,6 +69,7 @@ function unicode_decode($str) {
* @return array
*/
function getAllClasses($dir, $indoor_name) {
if(!is_dir($dir)) return [];
$list = scandir($dir);
$classes = [];
unset($list[0], $list[1]);
@@ -143,4 +157,70 @@ function matchArgs($pattern, $context) {
}
return $result;
} else return false;
}
}
function set_coroutine_params($array) {
$cid = Co::getCid();
if ($cid == -1) die("Cannot set coroutine params at none coroutine mode.");
if (isset(ZMBuf::$context[$cid])) ZMBuf::$context[$cid] = array_merge(ZMBuf::$context[$cid], $array);
else ZMBuf::$context[$cid] = $array;
foreach (ZMBuf::$context as $c => $v) {
if (!Co::exists($c)) unset(ZMBuf::$context[$c], ZMBuf::$context_class[$c]);
}
}
/**
* @return ContextInterface|null
*/
function context() {
return ctx();
}
/**
* @return ContextInterface|null
*/
function ctx() {
$cid = Co::getCid();
$c_class = ZMBuf::globals("context_class");
if (isset(ZMBuf::$context[$cid])) {
return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
} else {
Console::debug("未找到当前协程的上下文($cid),正在找父进程的上下文");
while (($pcid = Co::getPcid($cid)) !== -1) {
$cid = $pcid;
if (isset(ZMBuf::$context[$cid])) return ZMBuf::$context_class[$cid] ?? (ZMBuf::$context_class[$cid] = new $c_class($cid));
}
return null;
}
}
function debug($msg) { Console::debug($msg); }
function zm_sleep($s = 1) { Co::sleep($s); }
function zm_exec($cmd): array { return System::exec($cmd); }
function zm_cid() { return Co::getCid(); }
function zm_yield() { Co::yield(); }
function zm_resume(int $cid) { Co::resume($cid); }
function zm_timer_after($ms, callable $callable) {
go(function () use ($ms, $callable) {
ZMUtil::checkWait();
Swoole\Timer::after($ms, $callable);
});
}
function zm_timer_tick($ms, callable $callable) {
go(function () use ($ms, $callable) {
ZMUtil::checkWait();
Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable);
});
}

View File

@@ -0,0 +1,31 @@
<?php
use Swoole\Coroutine\Http\Client;
Co\run(function (){
global $terminal_id, $port;
$client = new Client("127.0.0.1", $port);
$client->set(['websocket_mask' => true]);
$client->setHeaders(["x-terminal-id" => $terminal_id, 'x-pid' => posix_getppid()]);
$ret = $client->upgrade("/?type=terminal");
if ($ret) {
while (true) {
$line = fgets(STDIN);
if ($line !== false) {
$r = $client->push(trim($line));
if (trim($line) == "reload" || trim($line) == "r" || trim($line) == "stop") {
break;
}
if($r === false) {
echo "Unable to connect framework terminal, connection closed.\n";
break;
}
} else {
break;
}
}
} else {
echo "Unable to connect framework terminal. port: $port\n";
}
});

View File

@@ -6,52 +6,86 @@ namespace Module\Example;
use Framework\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\CQConnection;
use ZM\ModBase;
use ZM\Utils\ZMUtil;
/**
* Class Hello
* @package Module\Example
* @Controller("/view")
* @since 1.0
*/
class Hello extends ModBase
class Hello
{
/**
* 在机器人连接后向终端输出信息
* @SwooleEventAt("open",rule="connectType:qq")
* @param $conn
*/
public function onConnect(CQConnection $conn){
Console::info("机器人 ".$conn->getQQ()." 已连接!");
public function onConnect(CQConnection $conn) {
Console::info("机器人 " . $conn->getQQ() . " 已连接!");
}
/**
* 向机器人发送"你好",即可回复这句话
* @CQCommand("你好")
*/
public function hello(){
public function hello() {
return "你好啊,我是由炸毛框架构建的机器人!";
}
/**
* @RequestMapping("/test/{ping}")
* @CQCommand(".reload")
*/
public function ping($param){
return "You input id is: ".$param["ping"];
public function reload() {
context()->reply("reloading...");
ZMUtil::reload();
}
/**
* @RequestMapping("/test/pong")
* @CQCommand("随机数")
* @CQCommand(regexMatch="*从*到*的随机数")
* @param $arg
*/
public function pong(){
$this->response->end("ping");
public function randNum($arg) {
// 获取第一个数字类型的参数
$num1 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第一个数字");
// 获取第二个数字类型的参数
$num2 = context()->getArgs($arg, ZM_MATCH_NUMBER, "请输入第二个数字");
$a = min(intval($num1), intval($num2));
$b = max(intval($num1), intval($num2));
// 回复用户结果
context()->reply("随机数是:".mt_rand($a, $b));
}
/**
* 中间件测试的一个示例函数
* @RequestMapping("/httpTimer")
* @Middleware("timer")
*/
public function timer() {
eval(ZM_BREAKPOINT);
return "This page is used as testing TimerMiddleware! Do not use it in production.";
}
/**
* 默认示例页面
* @RequestMapping("/index")
* @RequestMapping("/")
*/
public function index() {
return "Hello Zhamao!";
}
/**
* 框架会默认关闭未知的WebSocket链接因为这个绑定的事件你可以根据你自己的需求进行修改
* @SwooleEventAt(type="open",rule="connectType:unknown")
*/
public function closeUnknownConn(){
public function closeUnknownConn() {
Console::info("Unknown connection , I will close it.");
$this->connection->close();
context()->getConnection()->close();
}
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Module\Middleware;
use Framework\Console;
use ZM\Annotation\Http\After;
use ZM\Annotation\Http\Before;
use ZM\Annotation\Http\MiddlewareClass;
use ZM\Http\MiddlewareInterface;
/**
* Class AuthMiddleware
* 示例中间件:用于统计路由函数运行时间用的
* @package Module\Middleware
* @MiddlewareClass()
*/
class TimerMiddleware implements MiddlewareInterface
{
private $starttime;
/**
* @Before()
* @return bool
*/
public function onBefore() {
$this->starttime = microtime(true);
return true;
}
/**
* @After()
*/
public function onAfter() {
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
}
public function getName() { return "timer"; }
}

View File

@@ -18,7 +18,7 @@ class CQ
if (is_numeric($qq) || $qq === "all") {
return "[CQ:at,qq=" . $qq . "]";
}
Console::error("传入的QQ号码($qq)错误!");
Console::warning("传入的QQ号码($qq)错误!");
return " ";
}
@@ -31,7 +31,7 @@ class CQ
if (is_numeric($id)) {
return "[CQ:face,id=" . $id . "]";
}
Console::error("传入的face id($id)错误!");
Console::warning("传入的face id($id)错误!");
return " ";
}
@@ -44,7 +44,7 @@ class CQ
if (is_numeric($id)) {
return "[CQ:emoji,id=" . $id . "]";
}
Console::error("传入的emoji id($id)错误!");
Console::warning("传入的emoji id($id)错误!");
return " ";
}
@@ -66,7 +66,7 @@ class CQ
if (is_numeric($id)) {
return "[CQ:sface,id=" . $id . "]";
}
Console::error("传入的sface id($id)错误!");
Console::warning("传入的sface id($id)错误!");
return " ";
}
@@ -151,7 +151,7 @@ class CQ
return "[CQ:music,type=$type,id=$id_or_url]";
case "custom":
if ($title === null || $audio === null) {
Console::error("传入CQ码实例的标题和音频链接不能为空");
Console::warning("传入CQ码实例的标题和音频链接不能为空");
return " ";
}
if ($content === null) $c = "";
@@ -160,7 +160,7 @@ class CQ
else $i = ",image=" . $image;
return "[CQ:music,type=custom,url=" . $id_or_url . ",audio=" . $audio . ",title=" . $title . $c . $i . "]";
default:
Console::error("传入的music type($type)错误!");
Console::warning("传入的music type($type)错误!");
return " ";
}
}
@@ -211,10 +211,31 @@ class CQ
return $msg;
}
public static function encode($str) {
return self::escape($str);
}
public static function removeCQ($msg) {
while (($cq = ZMUtil::getCQ($msg)) !== null) {
while (($cq = self::getCQ($msg)) !== null) {
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
}
return $msg;
}
}
public static function getCQ($msg) {
if (($start = mb_strpos($msg, '[')) === false) return null;
if (($end = mb_strpos($msg, ']')) === false) return null;
$msg = mb_substr($msg, $start + 1, $end - $start - 1);
if (mb_substr($msg, 0, 3) != "CQ:") return null;
$msg = mb_substr($msg, 3);
$msg2 = explode(",", $msg);
$type = array_shift($msg2);
$array = [];
foreach ($msg2 as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$array[$sk] = implode("=", $ss);
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
}
}

View File

@@ -9,7 +9,8 @@ use Framework\Console;
use Framework\ZMBuf;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\WSConnection;
use ZM\Event\EventHandler;
use ZM\Utils\ZMRobot;
/**
* @method static send_private_msg($self_id, $params, $function = null)
@@ -68,19 +69,26 @@ use ZM\Connection\WSConnection;
*/
class CQAPI
{
public static function quick_reply(WSConnection $conn, $data, $msg, $yield = null) {
public static function quick_reply(CQConnection $conn, $data, $msg, $yield = null) {
switch ($data["message_type"]) {
case "group":
return self::send_group_msg($conn, ["group_id" => $data["group_id"], "message" => $msg], $yield);
return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg);
case "private":
return self::send_private_msg($conn, ["user_id" => $data["user_id"], "message" => $msg], $yield);
return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg);
case "discuss":
return self::send_discuss_msg($conn, ["discuss_id" => $data["discuss_id"], "message" => $msg], $yield);
return (new ZMRobot($conn))->setCallback($yield)->sendDiscussMsg($data["discuss_id"], $msg);
}
return null;
}
/**
* @param $name
* @param $arg
* @return bool
* @deprecated
*/
public static function __callStatic($name, $arg) {
trigger_error("This dynamic CQAPI calling method will be removed after 2.0 version.", E_USER_DEPRECATED);
$all = self::getSupportedAPIs();
$find = null;
if (in_array($name, $all)) $find = $name;
@@ -93,12 +101,12 @@ class CQAPI
}
}
if ($find === null) {
Console::error("Unknown API " . $name);
Console::warning("Unknown API " . $name);
return false;
}
$reply = ["action" => $find];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
Console::warning("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
@@ -107,7 +115,7 @@ class CQAPI
if (!($arg[0] instanceof CQConnection)) {
$robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]);
if ($robot == []) {
Console::error("发送错误,机器人连接不存在!");
Console::warning("发送错误,机器人连接不存在!");
return false;
}
$arg[0] = $robot[0];
@@ -190,16 +198,16 @@ class CQAPI
}
/**
* @param WSConnection $connection
* @param CQConnection $connection
* @param $reply
* @param |null $function
* @return bool
* @return bool|array
*/
private static function processAPI($connection, $reply, $function = null) {
public static function processAPI($connection, $reply, $function = null) {
$api_id = ZMBuf::$atomics["wait_msg_id"]->get();
$reply["echo"] = $api_id;
ZMBuf::$atomics["wait_msg_id"]->add(1);
EventHandler::callCQAPISend($reply, $connection);
if (is_callable($function)) {
ZMBuf::appendKey("sent_api", $api_id, [
"data" => $reply,
@@ -222,7 +230,7 @@ class CQAPI
]);
}
if ($connection->push(json_encode($reply))) {
Console::msg($reply, $connection->getQQ());
//Console::msg($reply, $connection->getQQ());
ZMBuf::$atomics["out_count"]->add(1);
if ($function === true) {
Co::suspend();
@@ -232,9 +240,10 @@ class CQAPI
}
return true;
} else {
Console::warning("CQAPI send failed, websocket push error.");
$response = [
"status" => "failed",
"retcode" => 999,
"retcode" => -1000,
"data" => null,
"self_id" => $connection->getQQ()
];
@@ -242,8 +251,8 @@ class CQAPI
if (($s["func"] ?? null) !== null)
call_user_func($s["func"], $response, $reply);
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
if ($function === true) return null;
if ($function === true) return $response;
return false;
}
}
}
}

View File

@@ -3,26 +3,36 @@
namespace ZM\Annotation;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\AnnotationReader;
use Framework\ZMBuf;
use Doctrine\Common\Annotations\{AnnotationException, AnnotationReader};
use Co;
use Framework\{Console, ZMBuf};
use Error;
use Exception;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\CQ\{CQAfter, CQBefore, CQCommand, CQMessage, CQMetaEvent, CQNotice, CQRequest};
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\CQ\{CQAfter,
CQAPIResponse,
CQAPISend,
CQBefore,
CQCommand,
CQMessage,
CQMetaEvent,
CQNotice,
CQRequest
};
use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping};
use Swoole\Timer;
use ZM\Annotation\Interfaces\CustomAnnotation;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed;
use ZM\Annotation\Module\InitBuffer;
use ZM\Annotation\Module\SaveBuffer;
use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Annotation\Module\{Closed, InitBuffer, LoadBuffer, SaveBuffer};
use ZM\Annotation\Swoole\{OnSave, OnStart, OnTick, SwooleEventAfter, SwooleEventAt};
use ZM\Annotation\Interfaces\Rule;
use ZM\Connection\WSConnection;
use ZM\Utils\DataProvider;
use ZM\Event\EventHandler;
use ZM\Http\MiddlewareInterface;
use Framework\DataProvider;
use ZM\Utils\ZMUtil;
class AnnotationParser
{
@@ -31,10 +41,9 @@ class AnnotationParser
* @throws ReflectionException
* @throws AnnotationException
*/
public static function registerMods()
{
public static function registerMods() {
self::loadAnnotationClasses();
$all_class = getAllClasses(WORKING_DIR . "/src/Module/", "Module");
$all_class = getAllClasses(DataProvider::getWorkingDir() . "/src/Module/", "Module");
ZMBuf::$req_mapping[0] = [
'id' => 0,
'pid' => -1,
@@ -42,22 +51,61 @@ class AnnotationParser
];
$reader = new AnnotationReader();
foreach ($all_class as $v) {
Console::debug("正在检索 " . $v);
$reflection_class = new ReflectionClass($v);
$class_prefix = '';
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
$class_annotations = $reader->getClassAnnotations($reflection_class);
$middleware_addon = null;
foreach ($class_annotations as $vs) {
if ($vs instanceof Closed) {
continue 2;
} elseif ($vs instanceof Controller) {
Console::debug("找到 Controller 中间件: " . $vs->class);
$class_prefix = $vs->prefix;
} elseif ($vs instanceof SaveBuffer) {
Console::debug("注册自动保存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")");
DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder);
} elseif ($vs instanceof LoadBuffer) {
Console::debug("注册到内存的缓存变量: " . $vs->buf_name . " (Dir:" . $vs->sub_folder . ")");
ZMBuf::set($vs->buf_name, DataProvider::getJsonData(($vs->sub_folder ?? "") . "/" . $vs->buf_name . ".json"));
} elseif ($vs instanceof InitBuffer) {
ZMBuf::set($vs->buf_name, []);
} elseif ($vs instanceof MiddlewareClass) {
Console::verbose("正在注册中间件 " . $reflection_class->getName());
$result = [
"class" => "\\" . $reflection_class->getName()
];
foreach ($methods as $vss) {
if ($vss->getName() == "getName") {
/** @var MiddlewareInterface $tmp */
$tmp = new $v();
$result["name"] = $tmp->getName();
continue;
}
$method_annotations = $reader->getMethodAnnotations($vss);
foreach ($method_annotations as $vsss) {
if ($vss instanceof Rule) $vss = self::registerRuleEvent($vsss, $vss, $reflection_class);
else $vss = self::registerMethod($vsss, $vss, $reflection_class);
//echo get_class($vsss) . PHP_EOL;
if ($vsss instanceof Before) $result["before"] = $vsss->method;
if ($vsss instanceof After) $result["after"] = $vsss->method;
if ($vsss instanceof HandleException) {
$result["exceptions"][$vsss->class_name] = $vsss->method;
}
}
}
ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result;
continue 2;
} elseif ($vs instanceof Middleware) {
$middleware_addon = $vs;
}
}
foreach ($methods as $vs) {
if ($middleware_addon !== null) {
Console::debug("Added middleware " . $middleware_addon->middleware . " to $v -> " . $vs->getName());
ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $middleware_addon->middleware;
}
$method_annotations = $reader->getMethodAnnotations($vs);
foreach ($method_annotations as $vss) {
if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class);
@@ -70,13 +118,17 @@ class AnnotationParser
elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss;
elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss;
elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss;
elseif ($vss instanceof RequestMapping) self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix);
elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss;
elseif ($vss instanceof RequestMapping) {
self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix);
} elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss;
elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss;
elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss;
elseif ($vss instanceof OnStart) {
ZMBuf::$events[OnStart::class][]=$vss;
}
elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss;
elseif ($vss instanceof OnSave) ZMBuf::$events[OnSave::class][] = $vss;
elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method][] = $vss->middleware;
elseif ($vss instanceof OnTick) self::addTimerTick($vss);
elseif ($vss instanceof CQAPISend) ZMBuf::$events[CQAPISend::class][] = $vss;
elseif ($vss instanceof CQAPIResponse) ZMBuf::$events[CQAPIResponse::class][$vss->retcode] = [$vss->class, $vss->method];
}
}
}
@@ -84,7 +136,7 @@ class AnnotationParser
ZMBuf::$req_mapping = $tree[0];
//给支持level的排个序
foreach (ZMBuf::$events as $class_name => $v) {
if ((new $class_name()) instanceof Level) {
if (is_a($class_name, Level::class, true)) {
for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) {
for ($j = 0; $j < count(ZMBuf::$events[$class_name]) - $i - 1; ++$j) {
$l1 = ZMBuf::$events[$class_name][$j]->level;
@@ -98,10 +150,14 @@ class AnnotationParser
}
}
}
Console::debug("解析注解完毕!");
if (ZMBuf::isset("timer_count")) {
Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!");
ZMBuf::unsetCache("timer_count");
}
}
private static function getRuleCallback($rule_str)
{
public static function getRuleCallback($rule_str) {
$func = null;
$rule = $rule_str;
if ($rule != "") {
@@ -111,7 +167,8 @@ class AnnotationParser
//Swoole 事件时走此switch
switch ($asp_name) {
case "connectType": //websocket连接类型
$func = function (WSConnection $connection) use ($rest) {
$func = function (?WSConnection $connection) use ($rest) {
if($connection === null) return false;
return $connection->getType() == $rest ? true : false;
};
break;
@@ -179,23 +236,20 @@ class AnnotationParser
return $func;
}
private static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class)
{
public static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
$vss->callback = self::getRuleCallback($vss->getRule());
$vss->method = $method->getName();
$vss->class = $class->getName();
return $vss;
}
private static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class)
{
public static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
$vss->method = $method->getName();
$vss->class = $class->getName();
return $vss;
}
private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix)
{
private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) {
$array = ZMBuf::$req_mapping;
$uid = count($array);
$prefix_exp = explode("/", $prefix);
@@ -211,9 +265,9 @@ class AnnotationParser
}
}
if ($prefix_exp == [] && $route_exp == []) {
$array[$uid - 1]['method'] = $method->getName();
$array[$uid - 1]['class'] = $class->getName();
$array[$uid - 1]['request_method'] = $vss->request_method;
$array[0]['method'] = $method->getName();
$array[0]['class'] = $class->getName();
$array[0]['request_method'] = $vss->request_method;
ZMBuf::$req_mapping = $array;
return;
}
@@ -263,22 +317,20 @@ class AnnotationParser
ZMBuf::$req_mapping = $array;
}
private static function loadAnnotationClasses()
{
private static function loadAnnotationClasses() {
$class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
$class = getAllClasses(WORKING_DIR . "/src/Custom/Annotation/", "Custom\\Annotation");
$class = getAllClasses(DataProvider::getWorkingDir() . "/src/Custom/Annotation/", "Custom\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
$s = DataProvider::getWorkingDir() . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
}
public static function genTree($items)
{
public static function genTree($items) {
$tree = array();
foreach ($items as $item)
if (isset($items[$item['pid']]))
@@ -287,4 +339,45 @@ class AnnotationParser
$tree[] = &$items[$item['id']];
return $tree;
}
}
private static function addTimerTick(?OnTick $vss) {
ZMBuf::set("timer_count", ZMBuf::get("timer_count", 0) + 1);
$class = ZMUtil::getModInstance($vss->class);
$method = $vss->method;
$ms = $vss->tick_ms;
$cid = go(function () use ($class, $method, $ms) {
Co::suspend();
$plain_class = get_class($class);
if (!isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) {
Console::debug("Added timer: " . $plain_class . " -> " . $method);
Timer::tick($ms, function () use ($class, $method) {
set_coroutine_params([]);
try {
$class->$method();
} catch (Exception $e) {
Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
} catch (Error $e) {
Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage());
echo Console::setColor($e->getTraceAsString(), "gray");
Console::error("Please check your code!");
}
});
} else {
Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $method);
Timer::tick($ms, function () use ($class, $method) {
set_coroutine_params([]);
try {
EventHandler::callWithMiddleware($class, $method, [], []);
} catch (Exception $e) {
Console::error("Uncaught error from TimerTick: " . $e->getMessage() . " at " . $e->getFile() . "({$e->getLine()})");
} catch (Error $e) {
Console::error("Uncaught fatal error from TimerTick: " . $e->getMessage());
echo Console::setColor($e->getTraceAsString(), "gray");
Console::error("Please check your code!");
}
});
}
});
ZMBuf::append("paused_tick", $cid);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class CQAPIResponse
* @package ZM\Annotation\CQ
* @Annotation
* @Target("METHOD")
*/
class CQAPIResponse extends AnnotationBase
{
/**
* @var int
* @Required()
*/
public $retcode;
}

View File

@@ -0,0 +1,43 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQAPISend
* @package ZM\Annotation\CQ
* @Annotation
* @Target("METHOD")
*/
class CQAPISend extends AnnotationBase implements Level
{
/**
* @var string
*/
public $action = "";
/**
* @var bool
*/
public $with_result = false;
public $level = 20;
/**
* @return mixed
*/
public function getLevel() {
return $this->level;
}
/**
* @param mixed $level
*/
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -5,6 +5,7 @@ namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQAfter
@@ -12,11 +13,27 @@ use ZM\Annotation\AnnotationBase;
* @Target("METHOD")
* @package ZM\Annotation\CQ
*/
class CQAfter extends AnnotationBase
class CQAfter extends AnnotationBase implements Level
{
/**
* @var string
* @Required()
*/
public $cq_event;
}
public $level = 20;
/**
* @return mixed
*/
public function getLevel() {
return $this->level;
}
/**
* @param mixed $level
*/
public function setLevel($level) {
$this->level = $level;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class After
* @package ZM\Annotation\Http
* @Annotation
* @Target("METHOD")
*/
class After extends AnnotationBase
{
}

View File

@@ -0,0 +1,19 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Before
* @package ZM\Annotation\Http
* @Annotation
* @Target("METHOD")
*/
class Before extends AnnotationBase
{
}

View File

@@ -0,0 +1,23 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Target;
use Exception;
use ZM\Annotation\AnnotationBase;
/**
* Class HandleException
* @package ZM\Annotation\Http
* @Annotation
* @Target("METHOD")
*/
class HandleException extends AnnotationBase
{
/**
* @var string
*/
public $class_name = Exception::class;
}

View File

@@ -0,0 +1,24 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Middleware
* @package ZM\Annotation\Http
* @Annotation
* @Target("ALL")
*/
class Middleware extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $middleware;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class MiddlewareClass
* @package ZM\Annotation\Http
* @Annotation
* @Target("CLASS")
*/
class MiddlewareClass extends AnnotationBase
{
}

View File

@@ -0,0 +1,24 @@
<?php
namespace ZM\Annotation\Module;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* Class LoadBuffer
* @package ZM\Annotation\Module
* @Annotation
* @Target("CLASS")
*/
class LoadBuffer
{
/**
* @var string
* @Required()
*/
public $buf_name;
/** @var string $sub_folder */
public $sub_folder = null;
}

View File

@@ -17,9 +17,10 @@ class SaveBuffer
{
/**
* @var string
*@Required()
* @Required()
*/
public $buf_name;
/** @var string $sub_folder */
public $sub_folder = null;
}
}

View File

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

View File

@@ -0,0 +1,25 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class OnTick
* @package ZM\Annotation\Swoole
* @Annotation
* @Target("METHOD")
* @since 1.2
*/
class OnTick extends AnnotationBase
{
/**
* @var int
* @Required()
*/
public $tick_ms;
}

View File

@@ -20,4 +20,4 @@ class CQConnection extends WSConnection
public function getType() {
return "qq";
}
}
}

View File

@@ -5,13 +5,14 @@ namespace ZM\Connection;
use Framework\ZMBuf;
use Framework\DataProvider;
class ConnectionManager
{
/**
* 通过server的fd获取WSConnection实例化对象
* @param int $fd
* @return WSConnection
* @return WSConnection|CQConnection|ProxyConnection
*/
public static function get(int $fd) {
foreach (ZMBuf::$connect as $v) {
@@ -23,7 +24,7 @@ class ConnectionManager
/**
* @param string $type
* @param array $option
* @return WSConnection[]
* @return WSConnection[]|CQConnection[]
*/
public static function getByType(string $type, $option = []) {
$conn = [];
@@ -46,6 +47,8 @@ class ConnectionManager
return WCConnection::class;
case "proxy":
return ProxyConnection::class;
case "terminal":
return TerminalConnection::class;
default:
foreach (ZMBuf::$custom_connection_class as $v) {
/** @var WSConnection $r */
@@ -67,7 +70,7 @@ class ConnectionManager
}
public static function registerCustomClass() {
$classes = getAllClasses(WORKING_DIR . "/src/Custom/Connection/", "Custom\\Connection");
$classes = getAllClasses(DataProvider::getWorkingDir(). "/src/Custom/Connection/", "Custom\\Connection");
ZMBuf::$custom_connection_class = $classes;
}
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace ZM\Connection;
class TerminalConnection extends WSConnection
{
public function getType() {
return "terminal";
}
}

View File

@@ -5,7 +5,6 @@ namespace ZM\Connection;
use Framework\Console;
use Framework\Logger;
use swoole_websocket_server;
abstract class WSConnection
@@ -43,10 +42,10 @@ abstract class WSConnection
}
if ($this->server->push($this->fd, $data) === false) {
$data = unicode_decode($data);
if ($push_error_record) Logger::writeSwooleLog("API push failed. Data: " . $data);
Console::error("websocket数据未成功推送长度" . strlen($data));
if ($push_error_record) Console::warning("API push failed. Data: " . $data);
Console::warning("websocket数据未成功推送长度" . strlen($data));
return false;
}
return true;
}
}
}

206
src/ZM/Context/Context.php Normal file
View File

@@ -0,0 +1,206 @@
<?php
namespace ZM\Context;
use Co;
use Framework\ZMBuf;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use swoole_server;
use ZM\API\CQAPI;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\WSConnection;
use ZM\Exception\InvalidArgumentException;
use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
use ZM\Utils\ZMRobot;
class Context implements ContextInterface
{
private $cid;
public function __construct($cid) { $this->cid = $cid; }
/**
* @return swoole_server|null
*/
public function getServer() { return ZMBuf::$context[$this->cid]["server"] ?? null; }
/**
* @return Frame|null
*/
public function getFrame() { return ZMBuf::$context[$this->cid]["frame"] ?? null; }
public function getFd() { return ZMBuf::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; }
/**
* @return array|null
*/
public function getData() { return ZMBuf::$context[$this->cid]["data"] ?? null; }
public function setData($data) { ZMBuf::$context[$this->cid]["data"] = $data; }
/**
* @return Request|null
*/
public function getRequest() { return ZMBuf::$context[$this->cid]["request"] ?? null; }
/**
* @return Response|null
*/
public function getResponse() { return ZMBuf::$context[$this->cid]["response"] ?? null; }
/** @return WSConnection */
public function getConnection() { return ConnectionManager::get($this->getFd()); }
/**
* @return int|null
*/
public function getCid() { return $this->cid; }
/**
* @return ZMRobot|null
*/
public function getRobot() {
$conn = ConnectionManager::get($this->getFrame()->fd);
return $conn instanceof CQConnection ? new ZMRobot($conn) : null;
}
public function getMessage() { return ZMBuf::$context[$this->cid]["data"]["message"] ?? null; }
public function setMessage($msg) { ZMBuf::$context[$this->cid]["data"]["message"] = $msg; }
public function getUserId() { return $this->getData()["user_id"] ?? null; }
public function setUserId($id) { ZMBuf::$context[$this->cid]["data"]["user_id"] = $id; }
public function getGroupId() { return $this->getData()["group_id"] ?? null; }
public function setGroupId($id) { ZMBuf::$context[$this->cid]["data"]["group_id"] = $id; }
public function getDiscussId() { return $this->getData()["discuss_id"] ?? null; }
public function setDiscussId($id) { ZMBuf::$context[$this->cid]["data"]["discuss_id"] = $id; }
public function getMessageType() { return $this->getData()["message_type"] ?? null; }
public function setMessageType($type) { ZMBuf::$context[$this->cid]["data"]["message_type"] = $type; }
public function getRobotId() { return $this->getData()["self_id"] ?? null; }
public function getCache($key) { return ZMBuf::$context[$this->cid]["cache"][$key] ?? null; }
public function setCache($key, $value) { ZMBuf::$context[$this->cid]["cache"][$key] = $value; }
public function getCQResponse() { return ZMBuf::$context[$this->cid]["cq_response"] ?? null; }
/**
* only can used by cq->message event function
* @param $msg
* @param bool $yield
* @return mixed
*/
public function reply($msg, $yield = false) {
switch ($this->getData()["message_type"]) {
case "group":
case "private":
case "discuss":
$this->setCache("has_reply", true);
return CQAPI::quick_reply(ConnectionManager::get($this->getFrame()->fd), $this->getData(), $msg, $yield);
}
return false;
}
public function finalReply($msg, $yield = false) {
ZMBuf::$context[$this->cid]["cache"]["block_continue"] = true;
if ($msg == "") return true;
return $this->reply($msg, $yield);
}
/**
* @param string $prompt
* @param int $timeout
* @param string $timeout_prompt
* @return string
* @throws InvalidArgumentException
* @throws WaitTimeoutException
*/
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
if ($prompt != "") $this->reply($prompt);
if (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"]))
throw new InvalidArgumentException("协程等待参数缺失");
$cid = Co::getuid();
$api_id = ZMBuf::$atomics["wait_msg_id"]->get();
ZMBuf::$atomics["wait_msg_id"]->add(1);
$hang = [
"coroutine" => $cid,
"user_id" => $this->getData()["user_id"],
"message" => $this->getData()["message"],
"self_id" => $this->getData()["self_id"],
"message_type" => $this->getData()["message_type"],
"result" => null
];
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
$hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"];
}
ZMBuf::appendKey("wait_api", $api_id, $hang);
$id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) {
$r = ZMBuf::get("wait_api")[$api_id] ?? null;
if ($r !== null) {
Co::resume($r["coroutine"]);
}
});
Co::suspend();
$sess = ZMBuf::get("wait_api")[$api_id];
ZMBuf::unsetByValue("wait_api", $api_id);
$result = $sess["result"];
if (isset($id)) swoole_timer_clear($id);
if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
return $result;
}
/**
* @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed|string
* @throws InvalidArgumentException
* @throws WaitTimeoutException
*/
public function getArgs(&$arg, $mode, $prompt_msg) {
switch ($mode) {
case ZM_MATCH_ALL:
$p = $arg;
array_shift($p);
return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p));
case ZM_MATCH_NUMBER:
foreach ($arg as $k => $v) {
if (is_numeric($v)) {
array_splice($arg, $k, 1);
return $v;
}
}
return $this->waitMessage($prompt_msg);
case ZM_MATCH_FIRST:
if (isset($arg[1])) {
$a = $arg[1];
array_splice($arg, 1, 1);
return $a;
} else {
return $this->waitMessage($prompt_msg);
}
}
throw new InvalidArgumentException();
}
public function cloneFromParent() {
set_coroutine_params(ZMBuf::$context[Co::getPcid()] ?? ZMBuf::$context[$this->cid]);
return context();
}
public function copy() { return ZMBuf::$context[$this->cid]; }
}

View File

@@ -0,0 +1,118 @@
<?php
namespace ZM\Context;
use Swoole\Http\Request;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use ZM\Connection\WSConnection;
use ZM\Http\Response;
use ZM\Utils\ZMRobot;
interface ContextInterface
{
public function __construct($cid);
/** @return Server */
public function getServer();
/** @return Frame */
public function getFrame();
/** @return mixed */
public function getData();
public function setData($data);
/** @return WSConnection */
public function getConnection();
/** @return int|null */
public function getFd();
/** @return int */
public function getCid();
/** @return Response */
public function getResponse();
/** @return Request */
public function getRequest();
/** @return ZMRobot */
public function getRobot();
/** @return mixed */
public function getUserId();
/** @return mixed */
public function getGroupId();
/** @return mixed */
public function getDiscussId();
/** @return string */
public function getMessageType();
/** @return mixed */
public function getRobotId();
/** @return mixed */
public function getMessage();
public function setMessage($msg);
public function setUserId($id);
public function setGroupId($id);
public function setDiscussId($id);
public function setMessageType($type);
public function getCQResponse();
/**
* @param $msg
* @param bool $yield
* @return mixed
*/
public function reply($msg, $yield = false);
/**
* @param $msg
* @param bool $yield
* @return mixed
*/
public function finalReply($msg, $yield = false);
/**
* @param string $prompt
* @param int $timeout
* @param string $timeout_prompt
* @return mixed
*/
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "");
/**
* @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed
*/
public function getArgs(&$arg, $mode, $prompt_msg);
public function setCache($key, $value);
/**
* @param $key
* @return mixed
*/
public function getCache($key);
public function cloneFromParent();
public function copy();
}

View File

@@ -4,17 +4,25 @@
namespace ZM\DB;
use Exception;
use framework\Console;
use framework\ZMBuf;
use PDOException;
use PDOStatement;
use Swoole\Coroutine;
use Swoole\Coroutine\MySQL\Statement;
use Swoole\Database\PDOStatementProxy;
use ZM\Exception\DbException;
class DB
{
private static $table_list = [];
/**
* @throws DbException
* @throws Exception
*/
public static function initTableList() {
if (!extension_loaded("mysqlnd")) throw new Exception("Can not find mysqlnd PHP extension.");
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []);
foreach ($result as $v) {
self::$table_list[] = $v['TABLE_NAME'];
@@ -40,10 +48,19 @@ class DB
return Table::getTableInstance($table_name);
}
/**
* @param $line
* @throws DbException
*/
public static function statement($line) {
self::rawQuery($line, []);
}
/**
* @param $line
* @return bool
* @throws DbException
*/
public static function unprepared($line) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
@@ -51,9 +68,10 @@ class DB
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
ZMBuf::$sql_pool->put(null);
throw new DbException("无法连接SQL" . $line);
}
$result = $conn->query($line) === false ? false : ($conn->errno != 0 ? false : true);
$result = $conn->query($line) === false ? false : true;
ZMBuf::$sql_pool->put($conn);
return $result;
} catch (DBException $e) {
@@ -64,38 +82,48 @@ class DB
"] " . $line . " (Error:" . $e->getMessage() . ")\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
Console::error($e->getMessage());
return false;
Console::warning($e->getMessage());
throw $e;
}
}
public static function rawQuery(string $line, $params) {
/**
* @param string $line
* @param array $params
* @return mixed
* @throws DbException
*/
public static function rawQuery(string $line, $params = []) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
}
Console::debug("MySQL: ".$line);
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
ZMBuf::$sql_pool->put(null);
throw new DbException("无法连接SQL" . $line);
}
$ps = $conn->prepare($line);
if ($ps === false) {
$conn->close();
ZMBuf::$sql_pool->connect_cnt -= 1;
ZMBuf::$sql_pool->put(null);
throw new DbException("SQL语句查询错误" . $line . ",错误信息:" . $conn->error);
} else {
if (!($ps instanceof Statement)) {
throw new DbException("语句查询错误!" . $line);
if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) {
var_dump($ps);
ZMBuf::$sql_pool->put(null);
throw new DbException("语句查询错误!返回的不是 PDOStatement" . $line);
}
if ($params == []) $result = $ps->execute();
elseif (!is_array($params)) {
$result = $ps->execute([$params]);
} else $result = $ps->execute($params);
ZMBuf::$sql_pool->put($conn);
if ($ps->errno != 0) {
throw new DBException("语句[$line]错误!" . $ps->error);
if ($result !== true) {
ZMBuf::$sql_pool->put(null);
throw new DBException("语句[$line]错误!" . $ps->errorInfo()[2]);
//echo json_encode(debug_backtrace(), 128 | 256);
}
ZMBuf::$sql_pool->put($conn);
if (ZMBuf::get("sql_log") === true) {
$log =
"[" . date("Y-m-d H:i:s") .
@@ -103,9 +131,9 @@ class DB
"] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
return $result;
return $ps->fetchAll();
}
} catch (DBException $e) {
} catch (DbException $e) {
if (ZMBuf::get("sql_log") === true) {
$log =
"[" . date("Y-m-d H:i:s") .
@@ -113,8 +141,32 @@ class DB
"] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
Console::error($e->getMessage());
return false;
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
return self::rawQuery($line, $params);
}
Console::warning($e->getMessage());
throw $e;
} catch (PDOException $e) {
if (ZMBuf::get("sql_log") === true) {
$log =
"[" . date("Y-m-d H:i:s") .
" " . round(microtime(true) - $starttime, 4) .
"] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
return self::rawQuery($line, $params);
}
Console::warning($e->getMessage());
throw new DbException($e->getMessage(), $e->getCode(), $e);
}
}
}
public static function isTableExists($table) {
return in_array($table, self::$table_list);
}
}

View File

@@ -4,6 +4,8 @@
namespace ZM\DB;
use ZM\Exception\DbException;
class DeleteBody
{
use WhereBody;
@@ -21,8 +23,12 @@ class DeleteBody
$this->table = $table;
}
/**
* @return mixed
* @throws DbException
*/
public function save() {
list($sql, $param) = $this->getWhereSQL();
return DB::rawQuery("DELETE FROM " . $this->table->getTableName() . " WHERE " . $sql, $param);
}
}
}

View File

@@ -4,6 +4,8 @@
namespace ZM\DB;
use ZM\Exception\DbException;
class InsertBody
{
/**
@@ -22,7 +24,10 @@ class InsertBody
$this->row = $row;
}
/**
* @throws DbException
*/
public function save() {
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, 5, '?')).')', $this->row);
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, count($this->row), '?')).')', $this->row);
}
}
}

View File

@@ -5,6 +5,7 @@ namespace ZM\DB;
use Framework\Console;
use ZM\Exception\DbException;
class SelectBody
{
@@ -23,13 +24,31 @@ class SelectBody
$this->select_thing = $select_thing;
}
/**
* @return null
* @throws DbException
*/
public function get() { return $this->fetchAll(); }
/**
* @throws DbException
*/
public function count() {
$this->select_thing = ["count(*)"];
$str = $this->queryPrepare();
$this->result = DB::rawQuery($str[0], $str[1]);
return intval($this->result[0]["count(*)"]);
}
/**
* @return null
* @throws DbException
*/
public function fetchAll() {
if ($this->table->isCacheEnabled()) {
$rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing));
if (array_key_exists($rr, $this->table->cache)) {
Console::info('SQL query cached: ' . $rr, date("[H:i:s ") . 'DB] ');
Console::debug('SQL query cached: ' . $rr);
return $this->table->cache[$rr]->getResult();
}
}
@@ -40,16 +59,30 @@ class SelectBody
return $this->getResult();
}
/**
* @return mixed|null
* @throws DbException
*/
public function fetchFirst() {
return $this->fetchAll()[0] ?? null;
}
public function value() {
/**
* @param null $key
* @return mixed|null
* @throws DbException
*/
public function value($key = null) {
$r = $this->fetchFirst();
if ($r === null) return null;
return current($r);
if ($key === null)
return current($r);
else return $r[$key] ?? null;
}
/**
* @throws DbException
*/
public function execute() {
$str = $this->queryPrepare();
$this->result = DB::rawQuery($str[0], $str[1]);
@@ -85,4 +118,4 @@ class SelectBody
}
return [$msg, $array ?? []];
}
}
}

View File

@@ -28,6 +28,7 @@ trait WhereBody
$param []=$vs;
}
}
if ($msg == '') $msg = 1;
return [$msg, $param];
}
}
}

View File

@@ -5,6 +5,7 @@ namespace ZM\Event\CQ;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
@@ -12,6 +13,7 @@ use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQMessage;
use ZM\Connection\WSConnection;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
use ZM\ModBase;
@@ -31,37 +33,63 @@ class MessageEvent
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["message"] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_MESSAGE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
$obj_list = ZMBuf::$events[CQBefore::class]["message"] ?? [];
foreach ($obj_list as $v) {
if($v->level < 200) break;
EventHandler::callWithMiddleware(
$v->class,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if (!$r) context()->setCache("block_continue", true);
}
);
if (context()->getCache("block_continue") === true) return false;
}
foreach (ZMBuf::get("wait_api", []) as $k => $v) {
if($this->data["user_id"] == $v["user_id"] &&
$this->data["self_id"] == $v["self_id"] &&
$this->data["message_type"] == $v["message_type"] &&
($this->data[$this->data["message_type"]."_id"] ?? $this->data["user_id"]) ==
($v[$v["message_type"]."_id"] ?? $v["user_id"])){
$v["result"] = $this->data["message"];
if (context()->getData()["user_id"] == $v["user_id"] &&
context()->getData()["self_id"] == $v["self_id"] &&
context()->getData()["message_type"] == $v["message_type"] &&
(context()->getData()[context()->getData()["message_type"] . "_id"] ?? context()->getData()["user_id"]) ==
($v[$v["message_type"] . "_id"] ?? $v["user_id"])) {
$v["result"] = context()->getData()["message"];
ZMBuf::appendKey("wait_api", $k, $v);
Co::resume($v["coroutine"]);
return false;
}
}
foreach (ZMBuf::$events[CQBefore::class]["message"] ?? [] as $v) {
if($v->level >= 200) continue;
$c = $v->class;
if (ctx()->getCache("level") != 0) continue;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if (!$r) context()->setCache("block_continue", true);
}
);
if (context()->getCache("block_continue") === true) return false;
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
$word = split_explode(" ", str_replace("\r", "", $this->data["message"]));
$word = split_explode(" ", str_replace("\r", "", context()->getMessage()));
if (count(explode("\n", $word[0])) >= 2) {
$enter = explode("\n", $this->data["message"]);
$enter = explode("\n", context()->getMessage());
$first = split_explode(" ", array_shift($enter));
$word = array_merge($first, $enter);
foreach ($word as $k => $v) {
@@ -75,20 +103,26 @@ class MessageEvent
if ($v->match == "" && $v->regexMatch == "") continue;
else {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_MESSAGE);
$class_construct = [
"data" => context()->getData(),
"connection" => context()->getConnection()
];
if (!isset($obj[$c])) {
$obj[$c] = new $c($class_construct);
}
if ($word[0] != "" && $v->match == $word[0]) {
$r = call_user_func([$obj[$c], $v->method], $word);
if (is_string($r)) $obj[$c]->reply($r);
$this->function_call = true;
Console::debug("Calling $c -> {$v->method}");
$this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) {
if (is_string($r)) context()->reply($r);
return true;
});
return;
} elseif (($args = matchArgs($v->regexMatch, $this->data["message"])) !== false) {
$r = call_user_func([$obj[$c], $v->method], $args);
if (is_string($r)) $obj[$c]->reply($r);
$this->function_call = true;
} elseif ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, context()->getMessage())) !== false) {
Console::debug("Calling $c -> {$v->method}");
$this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) {
if (is_string($r)) context()->reply($r);
return true;
});
return;
}
}
@@ -96,41 +130,48 @@ class MessageEvent
foreach (ZMBuf::$events[CQMessage::class] ?? [] as $v) {
/** @var CQMessage $v */
if (
($v->message == '' || ($v->message != '' && $v->message == $this->data["message"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $this->data["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) &&
($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == ($this->data["discuss_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $this->data["message_type"])) &&
($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == $this->data["raw_message"]))) {
($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) &&
($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]))) {
$c = $v->class;
Console::debug("Calling CQMessage: $c -> {$v->method}");
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"data" => context()->getData(),
"connection" => $this->connection
], ModHandleType::CQ_MESSAGE);
$r = call_user_func([$obj[$c], $v->method], $this->data["message"]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
EventHandler::callWithMiddleware($obj[$c], $v->method, [], [context()->getData()["message"]], function ($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
/**
* 在调用完事件后执行的
* @throws AnnotationException
*/
public function onAfter() {
context()->setCache("block_continue", null);
foreach (ZMBuf::$events[CQAfter::class]["message"] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_MESSAGE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if (!$r) context()->setCache("block_continue", true);
}
);
if (context()->getCache("block_continue") === true) return false;
}
return true;
}
@@ -138,4 +179,4 @@ class MessageEvent
public function hasReply() {
return $this->function_call;
}
}
}

View File

@@ -4,11 +4,12 @@
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
@@ -26,21 +27,30 @@ class MetaEvent
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["meta_event"] ?? [] as $v) {
$c = $v->class;
/** @var CQMetaEvent $v */
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_META_EVENT);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
/** @var ModBase[] $obj */
@@ -56,13 +66,14 @@ class MetaEvent
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_META_EVENT);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
}
}

View File

@@ -4,11 +4,13 @@
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQNotice;
use ZM\Connection\CQConnection;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
@@ -26,20 +28,30 @@ class NoticeEvent
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["notice"] ?? [] as $v) {
$c = $v->class;
/** @var CQNotice $v */
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_NOTICE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
/** @var ModBase[] $obj */
@@ -57,9 +69,10 @@ class NoticeEvent
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_NOTICE);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
@@ -67,16 +80,24 @@ class NoticeEvent
}
}
/**
* @return bool
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class]["notice"] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_NOTICE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
}
}

View File

@@ -4,11 +4,13 @@
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQRequest;
use ZM\Connection\CQConnection;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
@@ -26,21 +28,30 @@ class RequestEvent
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["request"] ?? [] as $v) {
$c = $v->class;
/** @var CQRequest $v */
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_REQUEST);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
/** @var ModBase[] $obj */
@@ -58,9 +69,10 @@ class RequestEvent
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_REQUEST);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} catch (WaitTimeoutException $e) {
@@ -68,16 +80,24 @@ class RequestEvent
}
}
/**
* @return bool
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class]["request"] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"connection" => $this->connection
], ModHandleType::CQ_REQUEST);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
}
}

View File

@@ -5,56 +5,136 @@ namespace ZM\Event;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use Error;
use Exception;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent};
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\WebSocket\Frame;
use ZM\Annotation\CQ\CQAPIResponse;
use ZM\Annotation\CQ\CQAPISend;
use ZM\Annotation\Http\MiddlewareClass;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Http\MiddlewareInterface;
use ZM\Http\Response;
use Framework\DataProvider;
use ZM\Utils\ZMUtil;
class EventHandler
{
/**
* @param $event_name
* @param $param0
* @param null $param1
* @throws AnnotationException
*/
public static function callSwooleEvent($event_name, $param0, $param1 = null) {
//$starttime = microtime(true);
unset(ZMBuf::$context[Co::getCid()]);
$event_name = strtolower($event_name);
switch ($event_name) {
case "workerstart":
try {
(new WorkerStartEvent($param0, $param1))->onActivate()->onAfter();
register_shutdown_function(function () {
$error = error_get_last();
if ($error["type"] != 0) {
Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
}
DataProvider::saveBuffer();
ZMBuf::$server->shutdown();
});
$r = (new WorkerStartEvent($param0, $param1))->onActivate();
Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold");
$r->onAfter();
self::startTick();
} catch (Exception $e) {
Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
ZMUtil::stop();
return;
} catch (Error $e) {
var_export($e);
ZMUtil::stop();
}
break;
case "message":
(new MessageEvent($param0, $param1))->onActivate()->onAfter();
/** @var Frame $param1 */
/** @var Server $param0 */
$conn = ConnectionManager::get($param1->fd);
set_coroutine_params(["server" => $param0, "frame" => $param1, "connection" => $conn]);
try {
(new MessageEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::stackTrace();
}
break;
case "request":
try {
set_coroutine_params(["request" => $param0, "response" => $param1]);
(new RequestEvent($param0, $param1))->onActivate()->onAfter();
} catch (Exception $e) {
/** @var Response $param1 */
$param1->status(500);
Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
" [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
);
if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage());
Console::error("Internal server error (500), caused by uncaught exception.");
Console::log($e->getTraceAsString(), "gray");
} catch (Error $e) {
/** @var Response $param1 */
$param1->status(500);
Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
" [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
);
$doc = "Internal server error<br>";
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
if (ZMBuf::$atomics["info_level"]->get() >= 4) $doc .= $error_msg;
if (!$param1->isEnd()) $param1->end($doc);
Console::error("Internal server error (500): " . $error_msg);
Console::log($e->getTraceAsString(), "gray");
}
break;
case "open":
(new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
/** @var Request $param1 */
set_coroutine_params(["server" => $param0, "request" => $param1, "fd" => $param1->fd]);
try {
(new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::stackTrace();
}
break;
case "close":
(new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
set_coroutine_params(["server" => $param0, "fd" => $param1]);
try {
(new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::stackTrace();
}
break;
}
//Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold"));
}
/**
* @param $event_data
* @param $conn_or_response
* @param int $level
* @return bool
* @throws AnnotationException
*/
public static function callCQEvent($event_data, $conn_or_response, int $level = 0) {
ctx()->setCache("level",$level);
if ($level >= 5) {
Console::warning("Recursive call reached " . $level . " times");
Console::stackTrace();
@@ -87,20 +167,42 @@ class EventHandler
return false;
}
/**
* @param $req
* @throws AnnotationException
*/
public static function callCQResponse($req) {
//Console::info("收到来自API连接的回复".json_encode($req, 128|256));
Console::debug("收到来自API连接的回复".json_encode($req, 128|256));
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
if (isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) {
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
$origin = ZMBuf::get("sent_api")[$req["echo"]];
$self_id = $origin["self_id"];
$response = [
"status" => $status,
"retcode" => $retcode,
"data" => $data,
"self_id" => $self_id
"self_id" => $self_id,
"echo" => $req["echo"]
];
set_coroutine_params(["cq_response" => $response]);
if (isset(ZMBuf::$events[CQAPIResponse::class][$req["retcode"]])) {
list($c, $method) = ZMBuf::$events[CQAPIResponse::class][$req["retcode"]];
$class = new $c(["data" => $origin["data"]]);
call_user_func_array([$class, $method], [$origin["data"], $req]);
}
$origin_ctx = ctx()->copy();
ctx()->setCache("action", $origin["data"]["action"] ?? "unknown");
ctx()->setData($origin["data"]);
foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
if (($v->action == "" || $v->action == ctx()->getCache("action")) && $v->with_result) {
$c = $v->class;
self::callWithMiddleware($c, $v->method, context()->copy(), [ctx()->getCache("action"), $origin["data"]["params"] ?? [], ctx()->getRobotId()]);
if (context()->getCache("block_continue") === true) break;
}
}
set_coroutine_params($origin_ctx);
if (($origin["func"] ?? null) !== null) {
call_user_func($origin["func"], $response, $origin["data"]);
} elseif (($origin["coroutine"] ?? false) !== false) {
@@ -112,4 +214,102 @@ class EventHandler
ZMBuf::unsetByValue("sent_api", $req["echo"]);
}
}
}
public static function callCQAPISend($reply, ?CQConnection $connection) {
$action = $reply["action"] ?? null;
if ($action === null) {
Console::warning("API 激活事件异常!");
return;
}
if (ctx() === null) $content = [];
else $content = ctx()->copy();
go(function () use ($action, $reply, $connection, $content) {
set_coroutine_params($content);
context()->setCache("action", $action);
context()->setCache("reply", $reply);
foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
if (($v->action == "" || $v->action == $action) && !$v->with_result) {
$c = $v->class;
self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getQQ()]);
if (context()->getCache("block_continue") === true) break;
}
}
});
}
/**
* @param $c
* @param $method
* @param array $class_construct
* @param array $func_args
* @param null $after_call
* @return mixed|null
* @throws AnnotationException
* @throws Exception
*/
public static function callWithMiddleware($c, $method, array $class_construct, array $func_args, $after_call = null) {
$return_value = null;
$plain_class = is_object($c) ? get_class($c) : $c;
if (isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) {
$middlewares = ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method];
$before_result = true;
$r = [];
foreach ($middlewares as $k => $middleware) {
if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middleware];
$before = $middleware_obj["class"];
$r[$k] = new $before();
$r[$k]->class = is_object($c) ? get_class($c) : $c;
$r[$k]->method = $method;
if (isset($middleware_obj["before"])) {
$before_result = call_user_func_array([$r[$k], $middleware_obj["before"]], $func_args);
if ($before_result === false) break;
}
}
if ($before_result) {
try {
if (is_object($c)) $class = $c;
elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
else $class = new $c($class_construct);
$result = call_user_func_array([$class, $method], $func_args);
if (is_callable($after_call))
$return_value = call_user_func_array($after_call, [$result]);
} catch (Exception $e) {
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
if (!isset($middleware_obj["exceptions"])) continue;
foreach ($middleware_obj["exceptions"] as $name => $method) {
if ($e instanceof $name) {
$r[$i]->$method($e);
context()->setCache("block_continue", true);
}
}
if (context()->getCache("block_continue") === true) return $return_value;
}
throw $e;
}
}
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
if (isset($middleware_obj["after"], $r[$i]))
call_user_func_array([$r[$i], $middleware_obj["after"]], $func_args);
}
} else {
if (is_object($c)) $class = $c;
elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
else $class = new $c($class_construct);
$result = call_user_func_array([$class, $method], $func_args);
if (is_callable($after_call))
$return_value = call_user_func_array($after_call, [$result]);
}
return $return_value;
}
private static function startTick() {
Console::debug("Starting " . count(ZMBuf::get("paused_tick", [])) . " custom tick function");
foreach (ZMBuf::get("paused_tick", []) as $cid) {
Co::resume($cid);
}
}
}

View File

@@ -39,26 +39,35 @@ class MessageEvent implements SwooleEvent
*/
public function onActivate() {
ZMUtil::checkWait();
$conn = ConnectionManager::get(context()->getFrame()->fd);
try {
if (ConnectionManager::get($this->frame->fd)->getType() == "qq") {
$data = json_decode($this->frame->data, true);
if ($conn->getType() == "qq") {
$data = json_decode(context()->getFrame()->data, true);
if (isset($data["post_type"])) {
EventHandler::callCQEvent($data, ConnectionManager::get($this->frame->fd), 0);
} else
set_coroutine_params(["data" => $data, "connection" => $conn]);
ctx()->setCache("level", 0);
Console::debug("Calling CQ Event from fd=" . $conn->fd);
EventHandler::callCQEvent($data, ConnectionManager::get(context()->getFrame()->fd), 0);
} else{
set_coroutine_params(["connection" => $conn]);
EventHandler::callCQResponse($data);
}
}
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
$conn = ConnectionManager::get($this->frame->fd);
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
call_user_func_array([$class, $v->method], [$conn]);
if ($class->block_continue) break;
EventHandler::callWithMiddleware(
$c,
$v->method,
["server" => $this->server, "frame" => $this->frame, "connection" => $conn],
[$conn]
);
if (context()->getCache("block_continue") === true) break;
}
}
} catch (Exception $e) {
Console::error("出现错误: " . $e->getMessage());
Console::warning("Websocket message event exception: " . (($cs = $e->getMessage()) == "" ? get_class($e) : $cs));
Console::warning("In ". $e->getFile() . " at line ".$e->getLine());
}
return $this;
}
@@ -74,7 +83,7 @@ class MessageEvent implements SwooleEvent
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
if (context()->getCache("block_continue") === true) break;
}
}
return $this;
@@ -91,4 +100,4 @@ class MessageEvent implements SwooleEvent
}
return true;
}
}
}

View File

@@ -5,13 +5,15 @@ namespace ZM\Event\Swoole;
use Closure;
use Exception;
use Framework\Console;
use Framework\ZMBuf;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Event\EventHandler;
use ZM\Http\Response;
use ZM\ModBase;
use ZM\ModHandleType;
use Framework\DataProvider;
use ZM\Utils\ZMUtil;
class RequestEvent implements SwooleEvent
@@ -30,12 +32,17 @@ class RequestEvent implements SwooleEvent
$this->response = $response;
}
/**
* @return $this|SwooleEvent
* @throws Exception
*/
public function onActivate() {
ZMUtil::checkWait();
foreach (ZMBuf::globals("http_header") as $k => $v) {
$this->response->setHeader($k, $v);
}
$uri = $this->request->server["request_uri"];
Console::verbose($this->request->server["remote_addr"]." request ".$uri);
$uri = explode("/", $uri);
$uri = array_diff($uri, ["..", "", "."]);
$node = ZMBuf::$req_mapping;
@@ -55,9 +62,6 @@ class RequestEvent implements SwooleEvent
} elseif ($node["son"][0]["name"] == $r) {
$node = $node["son"][0];
continue;
} else {
$this->responseStatus(404);
return $this;
}
} elseif ($cnt >= 1) {
if (isset($node["param_route"])) {
@@ -76,26 +80,63 @@ class RequestEvent implements SwooleEvent
}
}
}
$this->responseStatus(404);
if (ZMBuf::globals("static_file_server")["status"]) {
$base_dir = ZMBuf::globals("static_file_server")["document_root"];
$base_index = ZMBuf::globals("static_file_server")["document_index"];
$uri = $this->request->server["request_uri"];
$path = realpath($base_dir . urldecode($uri));
if ($path !== false) {
if (is_dir($path)) $path = $path . '/';
$work = realpath(DataProvider::getWorkingDir()) . '/';
if (strpos($path, $work) !== 0) {
$this->responseStatus(403);
return $this;
}
if (is_dir($path)) {
foreach ($base_index as $vp) {
if (is_file($path . $vp)) {
Console::info("[200] " . $uri . " (static)");
$exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown");
$this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
$this->response->end(file_get_contents($path . $vp));
return $this;
}
}
} elseif (is_file($path)) {
Console::info("[200] " . $uri . " (static)");
$exp = strtolower(pathinfo($path)['extension'] ?? "unknown");
$this->response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
$this->response->end(file_get_contents($path));
return $this;
}
}
}
$this->response->status(404);
$this->response->end(ZMUtil::getHttpCodePage(404));
return $this;
}
context()->setCache("params", $params);
if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面
$c_name = $node["class"];
/** @var ModBase $class */
$class = new $c_name(["request" => $this->request, "response" => $this->response, "params" => $params], ModHandleType::SWOOLE_REQUEST);
$r = call_user_func_array([$class, $node["method"]], [$params]);
if (is_string($r) && !$this->response->isEnd()) $this->response->end($r);
if ($class->block_continue) return $this;
if ($this->response->isEnd()) return $this;
EventHandler::callWithMiddleware(
$c_name,
$node["method"],
["request" => $this->request, "response" => &$this->response, "params" => $params],
[$params],
function ($result) {
if (is_string($result) && !$this->response->isEnd()) $this->response->end($result);
if ($this->response->isEnd()) context()->setCache("block_continue", true);
}
);
}
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["request" => $this->request, "response" => $this->response]);
$r = call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
EventHandler::callWithMiddleware($c, $v->method, ["request" => $this->request, "response" => $this->response], []);
if (context()->getCache("block_continue") === true) break;
}
}
@@ -127,7 +168,7 @@ class RequestEvent implements SwooleEvent
}
private function parseSwooleRule($v) {
switch (explode(":",$v->rule)[0]) {
switch (explode(":", $v->rule)[0]) {
case "containsGet":
case "containsPost":
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request);
@@ -141,4 +182,4 @@ class RequestEvent implements SwooleEvent
}
return true;
}
}
}

View File

@@ -4,10 +4,13 @@
namespace ZM\Event\Swoole;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\ConnectionManager;
use ZM\Event\EventHandler;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
@@ -25,15 +28,17 @@ class WSCloseEvent implements SwooleEvent
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onActivate() {
ZMUtil::checkWait();
ConnectionManager::close($this->fd);
set_coroutine_params(["server" => $this->server, "fd" => $this->fd]);
foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE);
call_user_func_array([$class, $v->method], []);
if($class->block_continue) break;
EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
if(context()->getCache("block_continue") === true) break;
}
}
return $this;
@@ -41,15 +46,14 @@ class WSCloseEvent implements SwooleEvent
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE);
call_user_func_array([$class, $v->method], []);
if($class->block_continue) break;
EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
if(context()->getCache("block_continue") === true) break;
}
}
return $this;
@@ -58,4 +62,4 @@ class WSCloseEvent implements SwooleEvent
private function parseSwooleRule($v) {
return true;
}
}
}

View File

@@ -5,6 +5,8 @@ namespace ZM\Event\Swoole;
use Closure;
use Doctrine\Common\Annotations\AnnotationException;
use Framework\Console;
use Framework\ZMBuf;
use Swoole\Http\Request;
use Swoole\WebSocket\Server;
@@ -14,6 +16,7 @@ use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\UnknownConnection;
use ZM\Connection\WSConnection;
use ZM\Event\EventHandler;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
@@ -40,6 +43,7 @@ class WSOpenEvent implements SwooleEvent
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onActivate() {
ZMUtil::checkWait();
@@ -48,19 +52,31 @@ class WSOpenEvent implements SwooleEvent
if ($type_conn == CQConnection::class) {
$qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? "";
$self_token = ZMBuf::globals("access_token") ?? "";
$remote_token = $this->request->get["token"] ?? (isset($header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : "");
if(isset($this->request->header["authorization"])) {
Console::debug($this->request->header["authorization"]);
}
$remote_token = $this->request->get["token"] ?? (isset($this->request->header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : "");
if ($qq != "" && ($self_token == $remote_token)) $this->conn = new CQConnection($this->server, $this->request->fd, $qq);
else $this->conn = new UnknownConnection($this->server, $this->request->fd);
else {
$this->conn = new UnknownConnection($this->server, $this->request->fd);
Console::warning("connection of CQ has invalid QQ or token!");
Console::debug("Remote token: ".$remote_token);
}
} else {
$this->conn = new $type_conn($this->server, $this->request->fd);
}
ZMBuf::$connect[$this->request->fd] = $this->conn;
set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]);
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
$class = new $c(["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN);
call_user_func_array([$class, $v->method], [$this->conn]);
if ($class->block_continue) break;
EventHandler::callWithMiddleware(
$c,
$v->method,
["server" => $this->server, "request" => $this->request, "connection" => $this->conn],
[$this->conn]
);
if (context()->getCache("block_continue") === true) break;
}
}
return $this;
@@ -76,7 +92,7 @@ class WSOpenEvent implements SwooleEvent
/** @var ModBase $class */
$class = new $v["class"](["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN);
call_user_func_array([$class, $v["method"]], [$this->conn]);
if ($class->block_continue) break;
if (context()->getCache("block_continue") === true) break;
}
}
return $this;
@@ -90,4 +106,4 @@ class WSOpenEvent implements SwooleEvent
}
return true;
}
}
}

View File

@@ -6,23 +6,29 @@ namespace ZM\Event\Swoole;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use Exception;
use PDO;
use ReflectionException;
use Swoole\Coroutine;
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Process;
use Swoole\Timer;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Connection\ConnectionManager;
use ZM\Context\ContextInterface;
use ZM\DB\DB;
use Framework\Console;
use Framework\GlobalConfig;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\DataProvider;
use ZM\Utils\SQLPool;
use ZM\Event\EventHandler;
use ZM\Exception\DbException;
use Framework\DataProvider;
use ZM\Utils\ZMUtil;
class WorkerStartEvent implements SwooleEvent
{
@@ -41,9 +47,17 @@ class WorkerStartEvent implements SwooleEvent
* @return WorkerStartEvent
* @throws AnnotationException
* @throws ReflectionException
* @throws DbException
*/
public function onActivate(): WorkerStartEvent {
Console::info("Worker启动中");
ZMBuf::$server = $this->server;
Console::listenConsole(); //这个方法只能在这里调用且如果worker_num不为1的话此功能不可用
Process::signal(SIGINT, function () {
Console::warning("Server interrupted by keyboard.");
ZMUtil::stop(true);
});
ZMBuf::resetCache(); //清空变量缓存
ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
$this->resetConnections();//释放所有与framework的连接
@@ -51,59 +65,83 @@ class WorkerStartEvent implements SwooleEvent
//设置炸毛buf中储存的对象
ZMBuf::$globals = new GlobalConfig();
ZMBuf::$config = [];
$file = scandir(WORKING_DIR . '/config/');
$file = scandir(DataProvider::getWorkingDir() . '/config/');
unset($file[0], $file[1]);
foreach ($file as $k => $v) {
if ($v == "global.php") continue;
$name = explode(".", $v);
if (($prefix = end($name)) == "json") {
ZMBuf::$config[$name[0]] = json_decode(Co::readFile(WORKING_DIR . '/config/' . $v), true);
Console::info("已读取配置文件(json)" . $prefix);
ZMBuf::$config[$name[0]] = json_decode(Co::readFile(DataProvider::getWorkingDir() . '/config/' . $v), true);
Console::info("已读取配置文件:" . $v);
} elseif ($prefix == "php") {
ZMBuf::$config[$name[0]] = include_once WORKING_DIR . '/config/' . $v;
ZMBuf::$config[$name[0]] = include_once DataProvider::getWorkingDir() . '/config/' . $v;
if (is_array(ZMBuf::$config[$name[0]]))
Console::info("已读取配置文件(php)" . $prefix);
Console::info("已读取配置文件:" . $v);
}
}
if (ZMBuf::globals("sql_config")["sql_host"] != "") {
Console::info("新建SQL连接池中");
ZMBuf::$sql_pool = new SQLPool();
ob_start();
phpinfo();
$str = ob_get_clean();
$str = explode("\n", $str);
foreach($str as $k => $v) {
$v = trim($v);
if($v == "") continue;
if(mb_strpos($v, "API Extensions") === false) continue;
if(mb_strpos($v, "pdo_mysql") === false) {
throw new DbException("未安装 mysqlnd php-mysql扩展。");
}
}
$sql = ZMBuf::globals("sql_config");
ZMBuf::$sql_pool = new PDOPool((new PDOConfig())
->withHost($sql["sql_host"])
->withPort($sql["sql_port"])
// ->withUnixSocket('/tmp/mysql.sock')
->withDbName($sql["sql_database"])
->withCharset('utf8mb4')
->withUsername($sql["sql_username"])
->withPassword($sql["sql_password"])
->withOptions([PDO::ATTR_STRINGIFY_FETCHES => false])
);
DB::initTableList();
}
ZMBuf::$server = $this->server;
ZMBuf::$atomics['reload_time']->add(1);
Console::info("监听console输入");
Console::listenConsole(); //这个方法只能在这里调用且如果worker_num不为1的话此功能不可用
$this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等
$this->setAutosaveTimer(ZMBuf::globals("auto_save_interval"));
$this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等
return $this;
}
/**
* @return WorkerStartEvent
* @throws AnnotationException
*/
public function onAfter(): WorkerStartEvent {
foreach (ZMBuf::get("wait_start") as $v) {
Coroutine::resume($v);
}
ZMBuf::unsetCache("wait_start");
foreach(ZMBuf::$events[OnStart::class] ?? [] as $v) {
set_coroutine_params(["server" => $this->server, "worker_id" => $this->worker_id]);
foreach (ZMBuf::$events[OnStart::class] ?? [] as $v) {
$class_name = $v->class;
/** @var ModBase $class */
$class = new $class_name(["server" => $this->server, "worker_id" => $this->worker_id], ModHandleType::SWOOLE_WORKER_START);
call_user_func_array([$class, $v->method], []);
Console::debug("正在调用启动时函数: " . $class_name . " -> " . $v->method);
EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []);
}
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
/** @var AnnotationBase $v */
if (strtolower($v->type) == "workerstart") {
$class_name = $v->class;
/** @var ModBase $class */
$class = new $class_name(["server" => $this->server, "worker_id" => $this->worker_id], ModHandleType::SWOOLE_WORKER_START);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
Console::debug("正在调用启动时函数after: " . $class_name . " -> " . $v->method);
EventHandler::callWithMiddleware($class_name, $v->method, ["server" => $this->server, "worker_id" => $this->worker_id], []);
if (context()->getCache("block_continue") === true) break;
}
}
Console::debug("调用完毕!");
return $this;
}
@@ -111,8 +149,8 @@ class WorkerStartEvent implements SwooleEvent
foreach ($this->server->connections as $v) {
$this->server->close($v);
}
if (ZMBuf::$sql_pool instanceof SqlPool) {
ZMBuf::$sql_pool->destruct();
if (ZMBuf::$sql_pool !== null) {
ZMBuf::$sql_pool->close();
ZMBuf::$sql_pool = null;
}
}
@@ -120,23 +158,31 @@ class WorkerStartEvent implements SwooleEvent
/**
* @throws AnnotationException
* @throws ReflectionException
* @throws Exception
*/
private function loadAllClass() {
//加载phar包
Console::info("加载外部phar包中");
$dir = WORKING_DIR . "/resources/package/";
$dir = DataProvider::getWorkingDir() . "/resources/package/";
if (version_compare(SWOOLE_VERSION, "4.4.0", ">=")) Timer::clearAll();
if (is_dir($dir)) {
$list = scandir($dir);
unset($list[0], $list[1]);
foreach ($list as $v) {
if (is_dir($dir . $v)) continue;
if (pathinfo($dir . $v, 4) == "phar") require_once($dir . $v);
if (pathinfo($dir . $v, 4) == "phar") {
Console::verbose("加载Phar: " . $dir . $v . "");
require_once($dir . $v);
}
}
}
//加载composer类
Console::info("加载composer资源中");
require_once WORKING_DIR . "/vendor/autoload.php";
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
}
if (LOAD_MODE == 2) require_once FRAMEWORK_DIR . "/vendor/autoload.php";
//加载各个模块的注解类,以及反射
Console::info("检索Module中");
@@ -144,12 +190,28 @@ class WorkerStartEvent implements SwooleEvent
//加载Custom目录下的自定义的内部类
ConnectionManager::registerCustomClass();
//加载自定义的全局函数
Console::debug("加载自定义的全局函数中");
if (file_exists(DataProvider::getWorkingDir() . "/src/Custom/global_function.php"))
require_once DataProvider::getWorkingDir() . "/src/Custom/global_function.php";
$this->afterCheck();
}
private function setAutosaveTimer($globals) {
DataProvider::$buffer_list = [];
Timer::tick($globals * 1000, function () {
zm_timer_tick($globals * 1000, function () {
DataProvider::saveBuffer();
});
}
}
/**
* @throws Exception
*/
private function afterCheck() {
$context_class = ZMBuf::globals("context_class");
if (!is_a($context_class, ContextInterface::class, true)) {
throw new Exception("Context class must implemented from ContextInterface!");
}
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace ZM\Exception;
use Exception;
use Throwable;
/**
* Class RobotNotFoundException
* @package ZM\Exception
* @since 1.2
*/
class RobotNotFoundException extends Exception
{
public function __construct($message = "", $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\Http;
interface MiddlewareInterface
{
public function getName();
}

View File

@@ -21,6 +21,7 @@ class Response
*/
private $response;
private $is_end = false;
private $status_code;
public function __construct(\Swoole\Http\Response $response) {
$this->response = $response;
@@ -90,9 +91,14 @@ class Response
* @return mixed
*/
public function status($http_code, $reason = null) {
$this->status_code = $http_code;
return $this->response->status($http_code, $reason);
}
public function getStatusCode() {
return $this->status_code ?? 200;
}
/**
* @param $http_code
* @param $reason
@@ -157,6 +163,11 @@ class Response
public function isEnd() { return $this->is_end; }
public function endWithStatus($status_code = 200, $content = null){
$this->status($status_code);
$this->end($content);
}
/**
* @param $filename
* @param $offset
@@ -226,4 +237,4 @@ class Response
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace ZM\Http;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Utils\ZMUtil;
class StaticFileHandler
{
public function __construct($filename, $path) {
$full_path = realpath($path . "/" . $filename);
$response = ctx()->getResponse();
Console::debug("Full path: ".$full_path);
if ($full_path !== false) {
if (strpos($full_path, $path) !== 0) {
$response->status(403);
$response->end("403 Forbidden");
return true;
} else {
if(is_file($full_path)) {
$exp = strtolower(pathinfo($full_path)['extension'] ?? "unknown");
$response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream");
$response->end(file_get_contents($full_path));
return true;
}
}
}
$response->status(404);
$response->end(ZMUtil::getHttpCodePage(404));
return true;
}
}

View File

@@ -67,7 +67,7 @@ abstract class ModBase
}
public function finalReply($msg, $yield = false) {
$this->block_continue = true;
$this->setBlock();
if ($msg == "") return true;
return $this->reply($msg, $yield);
}
@@ -161,5 +161,5 @@ abstract class ModBase
public function getConnection() { return $this->connection; }
public function setBlock($result = true) { $this->block_continue = $result; }
}
public function setBlock($result = true) { context()->setCache("block_continue", $result); }
}

View File

@@ -1,55 +0,0 @@
<?php
namespace ZM\Utils;
use Co;
use Framework\Console;
use Framework\ZMBuf;
class DataProvider
{
const HEADER_TYPE = '{"ai":"application/postscript","aif":"audio/x-aiff","aifc":"audio/x-aiff","aiff":"audio/x-aiff","asc":"text/plain","au":"audio/basic","avi":"video/x-msvideo","bcpio":"application/x-bcpio","bin":"application/octet-stream","bmp":"image/bmp","cdf":"application/x-netcdf","class":"application/octet-stream","cpio":"application/x-cpio","cpt":"application/mac-compactpro","csh":"application/x-csh","css":"text/css","dcr":"application/x-director","dir":"application/x-director","djv":"image/vnd.djvu","djvu":"image/vnd.djvu","dll":"application/octet-stream","dms":"application/octet-stream","doc":"application/msword","dvi":"application/x-dvi","dxr":"application/x-director","eps":"application/postscript","etx":"text/x-setext","exe":"application/octet-stream","ez":"application/andrew-inset","gif":"image/gif","gtar":"application/x-gtar","hdf":"application/x-hdf","hqx":"application/mac-binhex40","htm":"text/html","html":"text/html","ice":"x-conference/x-cooltalk","ief":"image/ief","iges":"model/iges","igs":"model/iges","jpe":"image/jpeg","jpeg":"image/jpeg","jpg":"image/jpeg","js":"application/x-javascript","kar":"audio/midi","latex":"application/x-latex","lha":"application/octet-stream","lzh":"application/octet-stream","m3u":"audio/x-mpegurl","man":"application/x-troff-man","me":"application/x-troff-me","mesh":"model/mesh","mid":"audio/midi","midi":"audio/midi","mif":"application/vnd.mif","mov":"video/quicktime","movie":"video/x-sgi-movie","mp2":"audio/mpeg","mp3":"audio/mpeg","mpe":"video/mpeg","mpeg":"video/mpeg","mpg":"video/mpeg","mpga":"audio/mpeg","ms":"application/x-troff-ms","msh":"model/mesh","mxu":"video/vnd.mpegurl","nc":"application/x-netcdf","oda":"application/oda","pbm":"image/x-portable-bitmap","pdb":"chemical/x-pdb","pdf":"application/pdf","pgm":"image/x-portable-graymap","pgn":"application/x-chess-pgn","png":"image/png","pnm":"image/x-portable-anymap","ppm":"image/x-portable-pixmap","ppt":"application/vnd.ms-powerpoint","ps":"application/postscript","qt":"video/quicktime","ra":"audio/x-realaudio","ram":"audio/x-pn-realaudio","ras":"image/x-cmu-raster","rgb":"image/x-rgb","rm":"audio/x-pn-realaudio","roff":"application/x-troff","rpm":"audio/x-pn-realaudio-plugin","rtf":"text/rtf","rtx":"text/richtext","sgm":"text/sgml","sgml":"text/sgml","sh":"application/x-sh","shar":"application/x-shar","silo":"model/mesh","sit":"application/x-stuffit","skd":"application/x-koan","skm":"application/x-koan","skp":"application/x-koan","skt":"application/x-koan","smi":"application/smil","smil":"application/smil","snd":"audio/basic","so":"application/octet-stream","spl":"application/x-futuresplash","src":"application/x-wais-source","sv4cpio":"application/x-sv4cpio","sv4crc":"application/x-sv4crc","swf":"application/x-shockwave-flash","t":"application/x-troff","tar":"application/x-tar","tcl":"application/x-tcl","tex":"application/x-tex","texi":"application/x-texinfo","texinfo":"application/x-texinfo","tif":"image/tiff","tiff":"image/tiff","tr":"application/x-troff","tsv":"text/tab-separated-values","txt":"text/plain","ustar":"application/x-ustar","vcd":"application/x-cdlink","vrml":"model/vrml","wav":"audio/x-wav","wbmp":"image/vnd.wap.wbmp","wbxml":"application/vnd.wap.wbxml","wml":"text/vnd.wap.wml","wmlc":"application/vnd.wap.wmlc","wmls":"text/vnd.wap.wmlscript","wmlsc":"application/vnd.wap.wmlscriptc","wrl":"model/vrml","xbm":"image/x-xbitmap","xht":"application/xhtml+xml","xhtml":"application/xhtml+xml","xls":"application/vnd.ms-excel","xml":"text/xml","xpm":"image/x-xpixmap","xsl":"text/xml","xwd":"image/x-xwindowdump","xyz":"chemical/x-xyz","zip":"application/zip"}';
public static $buffer_list = [];
public static function getResourceFolder() {
return WORKING_DIR . '/resources/';
}
public static function getDataConfig(){
return CONFIG_DIR;
}
public static function addSaveBuffer($buf_name, $sub_folder = null) {
$name = ($sub_folder ?? "") . "/" . $buf_name . ".json";
self::$buffer_list[$buf_name] = $name;
ZMBuf::set($buf_name, self::getJsonData($name));
}
public static function saveBuffer() {
$head = Console::setColor(date("[H:i:s ") . "INFO] Saving buffer......", "lightblue");
echo $head;
foreach(self::$buffer_list as $k => $v) {
self::setJsonData($v, ZMBuf::get($k));
}
echo Console::setColor("saved", "lightblue").PHP_EOL;
}
public static function getFrameworkLink(){
return ZMBuf::globals("http_reverse_link");
}
private static function getJsonData(string $string) {
if(!file_exists(self::getDataConfig().$string)) return [];
return json_decode(Co::readFile(self::getDataConfig().$string), true);
}
private static function setJsonData($filename, array $args) {
Co::writeFile(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING));
}
public static function getDataFolder() {
return ZM_DATA;
}
}

View File

@@ -10,9 +10,10 @@ namespace ZM\Utils;
use framework\Console;
use framework\ZMBuf;
use PDO;
use PDOException;
use SplQueue;
use Swoole\Coroutine;
use Swoole\Coroutine\Mysql;
class SQLPool
{
@@ -32,6 +33,26 @@ class SQLPool
"password" => ZMBuf::globals("sql_config")["sql_password"],
"database" => ZMBuf::globals("sql_config")["sql_database"]
];
Console::debug("新建检测 MySQL 连接的计时器");
zm_timer_tick(10000, function () {
//Console::debug("正在检测是否有坏死的MySQL连接当前连接池有 ".count($this->pool) . " 个连接");
if (count($this->pool) > 0) {
/** @var PDO $cnn */
$cnn = $this->pool->pop();
$this->connect_cnt -= 1;
try {
$cnn->getAttribute(PDO::ATTR_SERVER_INFO);
} catch (PDOException $e) {
if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
Console::info("MySQL 长连接丢失,取消连接");
unset($cnn);
return;
}
}
$this->pool->push($cnn);
$this->connect_cnt += 1;
}
});
}
/**
@@ -48,11 +69,11 @@ class SQLPool
/**
* 获取队中的连接,如果不存在则创建新的
* @param bool $no_new_conn
* @return bool|mixed|Mysql
* @return bool|mixed|PDO
*/
public function get($no_new_conn = false) {
if (count($this->pool) == 0 && $this->connect_cnt <= 70) {
if($no_new_conn) return false;
if ($no_new_conn) return false;
$this->connect_cnt += 1;
$r = $this->newConnect();
if ($r !== false) {
@@ -62,11 +83,12 @@ class SQLPool
return false;
}
} elseif (count($this->pool) > 0) {
/** @var PDO $con */
$con = $this->pool->pop();
if ($con->connected !== false) return $con;
return $con;
} elseif ($this->connect_cnt > 70) {
$this->co_list[]=Coroutine::getuid();
Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 ".Coroutine::stats()["coroutine_num"]);
$this->co_list[] = Coroutine::getuid();
Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 " . Coroutine::stats()["coroutine_num"]);
Coroutine::suspend();
return $this->get($no_new_conn);
}
@@ -87,15 +109,14 @@ class SQLPool
private function newConnect() {
//无空闲连接,创建新连接
$mysql = new Mysql();
Console::info("创建SQL连接中当前有" . $this->connect_cnt . "个连接");
$res = $mysql->connect($this->info);
if ($res == false) {
echo $mysql->error . PHP_EOL;
$dsn = "mysql:host=" . $this->info["host"] . ";dbname=" . $this->info["database"] . ";charset=utf8";
try {
$mysql = new PDO($dsn, $this->info["user"], $this->info["password"], array(PDO::ATTR_PERSISTENT => true));
} catch (PDOException $e) {
Console::error("PDO Error: " . $e->getMessage());
return false;
} else {
return $mysql;
}
Console::info("创建SQL连接中当前有" . $this->connect_cnt . "个连接");
return $mysql;
}
}
}

129
src/ZM/Utils/ZMRequest.php Normal file
View File

@@ -0,0 +1,129 @@
<?php
namespace ZM\Utils;
use Framework\Console;
use Swlib\Saber;
use Swoole\Coroutine\Http\Client;
class ZMRequest
{
/**
* 使用Swoole协程客户端发起HTTP GET请求
* @param $url
* @param array $headers
* @param array $set
* @param bool $return_body
* @return bool|string|Client
* @version 1.1
* 返回请求后的body
* 如果请求失败或返回状态不是200则返回 false
*/
public static function get($url, $headers = [], $set = [], $return_body = true) {
$parse = parse_url($url);
if (!isset($parse["host"])) {
Console::warning("ZMRequest: url must contains scheme such as \"http(s)\"");
return false;
}
if(!isset($parse["path"])) $parse["path"] = "/";
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
$cli->setHeaders($headers);
$cli->set($set == [] ? ['timeout' => 15.0] : $set);
$cli->get($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
if ($return_body) {
if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
$a = $cli->body;
$cli->close();
return $a;
} else {
$cli->close();
return $cli;
}
}
/**
* 使用Swoole协程客户端发起HTTP POST请求
* 返回请求后的body
* 如果请求失败或返回状态不是200则返回 false
* @param $url
* @param array $header
* @param $data
* @param array $set
* @param bool $return_body
* @return bool|string|Client
*/
public static function post($url, array $header, $data, $set = [], $return_body = true) {
$parse = parse_url($url);
if (!isset($parse["host"])) {
Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\"");
return false;
}
if(!isset($parse["path"])) $parse["path"] = "/";
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
$cli->set($set == [] ? ['timeout' => 15.0] : $set);
$cli->setHeaders($header);
$cli->post($parse["path"] . (isset($parse["query"]) ? ("?" . $parse["query"]) : ""), $data);
if ($return_body) {
if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
$a = $cli->body;
$cli->close();
return $a;
} else {
$cli->close();
return $cli;
}
}
/**
* @param $url
* @param array $set
* @param array $header
* @return ZMWebSocket
* @since 1.5
*/
public static function websocket($url, $set = ['websocket_mask' => true], $header = []) {
return new ZMWebSocket($url, $set, $header);
}
/**
* @param $option
* @return Saber
*/
public static function session($option) {
return Saber::session($option);
}
public static function request($url, $attribute = [], $return_body = true) {
$parse = parse_url($url);
if (!isset($parse["host"])) {
Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\"");
return false;
}
if(!isset($parse["path"])) $parse["path"] = "/";
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
$cli->set($attribute["set"] ?? ["timeout" => 15.0]);
$cli->setMethod($attribute["method"] ?? "GET");
$cli->setHeaders($attribute["headers"] ?? []);
if(isset($attribute["data"])) $cli->setData($attribute["data"]);
if(isset($attribute["file"])) {
foreach($attribute["file"] as $k => $v) {
$cli->addFile($v["path"], $v["name"], $v["mime_type"] ?? null, $v["filename"] ?? null, $v["offset"] ?? 0, $v["length"] ?? 0);
}
}
$cli->execute($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
if ($return_body) {
if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
$a = $cli->body;
$cli->close();
return $a;
} else {
$cli->close();
return $cli;
}
}
}

412
src/ZM/Utils/ZMRobot.php Normal file
View File

@@ -0,0 +1,412 @@
<?php
namespace ZM\Utils;
use ZM\API\CQAPI;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Exception\RobotNotFoundException;
/**
* Class ZMRobot
* @package ZM\Utils
* @since 1.2
*/
class ZMRobot
{
const API_ASYNC = 1;
const API_NORMAL = 0;
const API_RATE_LIMITED = 2;
private $connection;
private $callback = null;
private $prefix = 0;
/**
* @param $robot_id
* @return ZMRobot
* @throws RobotNotFoundException
*/
public static function get($robot_id) {
$r = ConnectionManager::getByType("qq", ["self_id" => $robot_id]);
if ($r == []) throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!");
return new ZMRobot($r[0]);
}
/**
* @throws RobotNotFoundException
* @return ZMRobot
*/
public static function getRandom() {
$r = ConnectionManager::getByType("qq");
if($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
return new ZMRobot($r[array_rand($r)]);
}
public function __construct(CQConnection $connection) {
$this->connection = $connection;
}
public function setCallback($callback = true) {
$this->callback = $callback;
return $this;
}
public function setPrefix($prefix = self::API_NORMAL) {
$this->prefix = $prefix;
return $this;
}
public function sendPrivateMsg($user_id, $message, $auto_escape = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
public function sendGroupMsg($group_id, $message, $auto_escape = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
public function sendDiscussMsg($discuss_id, $message, $auto_escape = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'discuss_id' => $discuss_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_type' => $message_type,
($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id,
'message' => $message,
'auto_escape' => $auto_escape
]
], $this->callback);
}
public function deleteMsg($message_id) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'message_id' => $message_id
]
], $this->callback);
}
public function sendLike($user_id, $times = 1) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'times' => $times
]
], $this->callback);
}
public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'reject_add_request' => $reject_add_request
]
], $this->callback);
}
public function setGroupBan($group_id, $user_id, $duration = 1800) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'duration' => $duration
]
], $this->callback);
}
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
(is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag,
'duration' => $duration
]
], $this->callback);
}
public function setGroupWholeBan($group_id, $enable = true) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
public function setGroupAdmin($group_id, $user_id, $enable = true) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'enable' => $enable
]
], $this->callback);
}
public function setGroupAnonymous($group_id, $enable = true) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'enable' => $enable
]
], $this->callback);
}
public function setGroupCard($group_id, $user_id, $card = "") {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'card' => $card
]
], $this->callback);
}
public function setGroupLeave($group_id, $is_dismiss = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'is_dismiss' => $is_dismiss
]
], $this->callback);
}
public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'special_title' => $special_title,
'duration' => $duration
]
], $this->callback);
}
public function setDiscussLeave($discuss_id) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'discuss_id' => $discuss_id
]
], $this->callback);
}
public function setFriendAddRequest($flag, $approve = true, $remark = "") {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'approve' => $approve,
'remark' => $remark
]
], $this->callback);
}
public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'flag' => $flag,
'sub_type' => $sub_type,
'approve' => $approve,
'reason' => $reason
]
], $this->callback);
}
public function getLoginInfo() {
return CQAPI::processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
}
public function getStrangerInfo($user_id, $no_cache = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
public function getFriendList() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getGroupList() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getGroupInfo($group_id, $no_cache = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'no_cache' => $no_cache
]
], $this->callback);
}
public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'user_id' => $user_id,
'no_cache' => $no_cache
]
], $this->callback);
}
public function getGroupMemberList($group_id) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id
]
]);
}
public function getCookies($domain = "") {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'domain' => $domain
]
]);
}
public function getCsrfToken() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getCredentials($domain = "") {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'domain' => $domain
]
], $this->callback);
}
public function getRecord($file, $out_format, $full_path = false) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file,
'out_format' => $out_format,
'full_path' => $full_path
]
], $this->callback);
}
public function getImage($file) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'file' => $file
]
], $this->callback);
}
public function canSendImage() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function canSendRecord() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getStatus() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getVersionInfo() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function setRestartPlugin($delay = 0) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'delay' => $delay
]
], $this->callback);
}
public function cleanDataDir($data_dir) {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__),
'params' => [
'data_dir' => $data_dir
]
], $this->callback);
}
public function cleanPluginLog() {
return CQAPI::processAPI($this->connection, [
'action' => $this->getActionName(__FUNCTION__)
], $this->callback);
}
public function getExperimentAPI() {
return new ZMRobotExperiment($this->connection, $this->callback, $this->prefix);
}
private function getActionName(string $method) {
$prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $func_name . $prefix;
}
}

View File

@@ -0,0 +1,104 @@
<?php
namespace ZM\Utils;
use ZM\API\CQAPI;
use ZM\Connection\CQConnection;
/**
* Class ZMRobotExperiment
* @package ZM\Utils
* @since 1.2
*/
class ZMRobotExperiment
{
/**
* @var CQConnection
*/
private $connection;
private $callback = null;
private $prefix = 0;
public function __construct(CQConnection $connection, $callback, $prefix) {
$this->connection = $connection;
$this->callback = $callback;
$this->prefix = $prefix;
}
public function setCallback($callback = true) {
$this->callback = $callback;
return $this;
}
public function setPrefix($prefix = ZMRobot::API_NORMAL) {
$this->prefix = $prefix;
return $this;
}
public function getFriendList($flat = false) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'flat' => $flat
]
], $this->callback);
}
public function getGroupInfo($group_id) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id
]
], $this->callback);
}
public function getVipInfo($user_id) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'user_id' => $user_id
]
], $this->callback);
}
public function getGroupNotice($group_id) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id
]
], $this->callback);
}
public function sendGroupNotice($group_id, $title, $content) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'group_id' => $group_id,
'title' => $title,
'content' => $content
]
], $this->callback);
}
public function setRestart($clean_log = false, $clean_cache = false, $clean_event = false) {
return CQAPI::processAPI($this->connection, [
'action' => '_' . $this->getActionName(__FUNCTION__),
'params' => [
'clean_log' => $clean_log,
'clean_cache' => $clean_cache,
'clean_event' => $clean_event
]
], $this->callback);
}
private function getActionName(string $method) {
$prefix = ($this->prefix == ZMRobot::API_ASYNC ? '_async' : ($this->prefix == ZMRobot::API_RATE_LIMITED ? '_rate_limited' : ''));
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
return $prefix . $func_name;
}
}

View File

@@ -6,7 +6,9 @@ namespace ZM\Utils;
use Co;
use framework\Console;
use Framework\DataProvider;
use Framework\ZMBuf;
use ZM\API\CQ;
class ZMUtil
{
@@ -14,30 +16,34 @@ class ZMUtil
* 检查workerStart是否运行结束
*/
public static function checkWait() {
if(ZMBuf::isset("wait_start")) {
if (ZMBuf::isset("wait_start")) {
ZMBuf::append("wait_start", Co::getCid());
Co::suspend();
}
}
public static function stop() {
public static function stop($without_shutdown = false) {
Console::info(Console::setColor("Stopping server...", "red"));
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
DataProvider::saveBuffer();
ZMBuf::$server->shutdown();
if (!$without_shutdown)
ZMBuf::$server->shutdown();
ZMBuf::$server->stop();
}
public static function getHttpCodePage(int $http_code) {
if(isset(ZMBuf::globals("http_default_code_page")[$http_code])) {
return Co::readFile(DataProvider::getResourceFolder()."html/".ZMBuf::globals("http_default_code_page")[$http_code]);
if (isset(ZMBuf::globals("http_default_code_page")[$http_code])) {
return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMBuf::globals("http_default_code_page")[$http_code]);
} else return null;
}
public static function reload() {
Console::info(Console::setColor("Reloading server...", "gold"));
foreach (ZMBuf::get("wait_api", []) as $k => $v) {
if ($v["result"] === null) Co::resume($v["coroutine"]);
}
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
@@ -49,23 +55,18 @@ class ZMUtil
* 解析CQ码
* @param $msg
* @return array|null
* 0123456
* [CQ:at]
*/
static function getCQ($msg) {
if (($start = mb_strpos($msg, '[')) === false) return null;
if (($end = mb_strpos($msg, ']')) === false) return null;
$msg = mb_substr($msg, $start + 1, $end - $start - 1);
if (mb_substr($msg, 0, 3) != "CQ:") return null;
$msg = mb_substr($msg, 3);
$msg2 = explode(",", $msg);
$type = array_shift($msg2);
$array = [];
foreach ($msg2 as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$array[$sk] = implode("=", $ss);
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
return CQ::getCQ($msg);
}
}
public static function getModInstance($class) {
if (!isset(ZMBuf::$instance[$class])) {
Console::debug("Class instance $class not exist, so I created it.");
ZMBuf::$instance[$class] = new $class();
return ZMBuf::$instance[$class];
} else {
return ZMBuf::$instance[$class];
}
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace ZM\Utils;
use Framework\Console;
use Swoole\Coroutine\Http\Client;
use Swoole\WebSocket\Frame;
/**
* Class ZMWebSocket
* @package ZM\Utils
* @since 1.5
*/
class ZMWebSocket
{
private $parse;
private $client;
public $is_available = false;
private $close_func;
private $message_func;
public function __construct($url, $set = ['websocket_mask' => true], $header = []) {
$this->parse = parse_url($url);
if (!isset($this->parse["host"])) {
Console::warning("ZMRequest: url must contains scheme such as \"ws(s)://\"");
return;
}
if (!isset($this->parse["path"])) $this->parse["path"] = "/";
$port = $this->parse["port"] ?? (($this->parse["scheme"] ?? "ws") == "wss" ? 443 : 80);
$this->client = new Client($this->parse["host"], $port, (($this->parse["scheme"] ?? "ws") == "wss" ? true : false));
$this->client->set($set);
if ($header != []) $this->client->setHeaders($header);
$this->is_available = true;
}
/**
* @return bool
*/
public function upgrade() {
if (!$this->is_available) return false;
$r = $this->client->upgrade($this->parse["path"] . (isset($this->parse["query"]) ? ("?" . $this->parse["query"]) : ""));
if ($r) {
go(function () {
while (true) {
$result = $this->client->recv(60);
if ($result === false) {
if ($this->client->connected === false) {
go(function () {
call_user_func($this->close_func, $this->client);
});
break;
}
} elseif ($result instanceof Frame) {
go(function () use ($result) {
$this->is_available = false;
call_user_func($this->message_func, $result, $this->client);
});
}
}
});
return true;
}
return false;
}
/**
* @param callable $callable
* @return $this
*/
public function onMessage(callable $callable) {
$this->message_func = $callable;
return $this;
}
/**
* @param callable $callable
* @return $this
*/
public function onClose(callable $callable) {
$this->close_func = $callable;
return $this;
}
}
if (!debug_backtrace()) {
go(function () {
require_once __DIR__ . "/../../Framework/Console.php";
$cli = new ZMWebSocket("ws://127.0.0.1:20001/");
if (!$cli->is_available) die("Error!\n");
$cli->onMessage(function (Frame $frame) {
var_dump($frame);
});
$cli->onClose(function () {
echo "Connection closed.\n";
});
if ($cli->upgrade()) {
echo "成功连接!\n";
} else {
echo "连接失败!\n";
}
});
}