mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-04 07:15:36 +08:00
Compare commits
85 Commits
1.2
...
2.0.0-beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fe405d0af | ||
|
|
3b90bf6245 | ||
|
|
deab5fd921 | ||
|
|
29fa9d8662 | ||
|
|
da584e0542 | ||
|
|
f91d24aaaa | ||
|
|
1510e2f0d0 | ||
|
|
82896ee4a1 | ||
|
|
beb1f5f063 | ||
|
|
b6756179f5 | ||
|
|
1adcf76203 | ||
|
|
5b003ab575 | ||
|
|
75f6aa531e | ||
|
|
6e1f4820f8 | ||
|
|
50ce81334b | ||
|
|
0ed0aa089a | ||
|
|
102ba769ec | ||
|
|
e062f484b1 | ||
|
|
3be3e8412a | ||
|
|
ab5abf1c00 | ||
|
|
3aaa72cfb9 | ||
|
|
10f846c214 | ||
|
|
67a42c4be9 | ||
|
|
7e4e58a322 | ||
|
|
5de283d30c | ||
|
|
7513fd1a1d | ||
|
|
c635891e0b | ||
|
|
7b7a2d7010 | ||
|
|
23b1f797ad | ||
|
|
a72e0f705c | ||
|
|
504934f057 | ||
|
|
e976a96d0b | ||
|
|
dc7af105f4 | ||
|
|
fd96543268 | ||
|
|
cebc6fb310 | ||
|
|
c654cf40d5 | ||
|
|
68cc536af3 | ||
|
|
42412fee98 | ||
|
|
500ec4707e | ||
|
|
b09857e3b8 | ||
|
|
7d79126c8f | ||
|
|
a542558503 | ||
|
|
6697591a22 | ||
|
|
aad28f1ec4 | ||
|
|
f1949b1bd0 | ||
|
|
3b8aac5d8f | ||
|
|
5fd45c2542 | ||
|
|
af89c1b1f6 | ||
|
|
3287b96f30 | ||
|
|
00a8683658 | ||
|
|
c0ea068d04 | ||
|
|
9ba58ff90f | ||
|
|
1a1cf0ad30 | ||
|
|
1de93b9dc1 | ||
|
|
99e44eea3d | ||
|
|
e67958a8d1 | ||
|
|
23b3dc34e2 | ||
|
|
775672d515 | ||
|
|
40e17fab62 | ||
|
|
59fde3d075 | ||
|
|
a8183757be | ||
|
|
8ae5844649 | ||
|
|
acc96b78db | ||
|
|
802f975825 | ||
|
|
76ee308b91 | ||
|
|
d9eca5d7b1 | ||
|
|
5144bc2094 | ||
|
|
f7418de868 | ||
|
|
2bcbdcd3ca | ||
|
|
013c78dc77 | ||
|
|
886816e3d5 | ||
|
|
81db9c6ccb | ||
|
|
ec88c56137 | ||
|
|
acb4bdf9b4 | ||
|
|
a1b013ee53 | ||
|
|
086c65af26 | ||
|
|
9b3a2e5296 | ||
|
|
181f6430a4 | ||
|
|
d5c192108e | ||
|
|
0c28dda808 | ||
|
|
b61eeccdb8 | ||
|
|
19d618f167 | ||
|
|
17ce0c20ae | ||
|
|
82a1f86bbd | ||
|
|
97e579b8a1 |
@@ -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
3
.gitignore
vendored
@@ -4,4 +4,5 @@
|
||||
/vendor/
|
||||
zm.json
|
||||
/zm_data/
|
||||
composer.lock
|
||||
composer.lock
|
||||
/resources/server.phar
|
||||
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,15 +1,28 @@
|
||||
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"]
|
||||
|
||||
53
README.md
53
README.md
@@ -1,64 +1,75 @@
|
||||
# zhamao-framework
|
||||
<div align="center">
|
||||
<img src="/resources/images/logo_trans.png" height = "150" alt="炸毛框架"><br>
|
||||
<h2>炸毛框架</h2>
|
||||
炸毛框架 (zhamao-frameowork) 是一个协程高性能的聊天机器人 + Web 服务器开发框架<br><br>
|
||||
|
||||
[]()
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
|
||||
[]()
|
||||
[](https://packagist.org/packages/zhamao/framework)
|
||||
[]()
|
||||
[]()
|
||||
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
|
||||
|
||||
一个异步、多平台兼容的 **聊天机器人** 框架。
|
||||
</div>
|
||||
|
||||
## 开发者注意
|
||||
**此版本目前无法正常运行,还在持续开发中,请勿使用!!!**
|
||||
|
||||
**2.0 版本开发完毕后,会发布测试版和并入 master 分支,敬请关注。**
|
||||
|
||||
[]()
|
||||
|
||||
|
||||
<img src="https://avatars0.githubusercontent.com/u/48620312" height = "200" alt="炸毛框架" align=center/>
|
||||
|
||||
## 简介
|
||||
zhamao-framework 是一个基于 酷Q 的 PHP Swoole 的机器人框架,它会对 QQ 机器人收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。
|
||||
|
||||
框架对接 酷Q 的桥梁是 **CQHTTP** 插件,这里是它的[项目地址](https://github.com/richardchien/coolq-http-api/)。
|
||||
zhamao-framework 是一个 PHP Swoole 的聊天机器人框架,兼容 OneBot 标准,它会对微信公众号等终端收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。
|
||||
|
||||
除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。
|
||||
|
||||
## 开始
|
||||
你可以使用 GitHub 的 `Use This Template` 功能快速将本项目克隆到你的公开或私有仓库,也可以从 `release` 中下载本项目的最新版本到本地私有开发。
|
||||
先安装环境,环境安装见下方文档。
|
||||
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) |
|
||||
|
||||
## 计划开发内容
|
||||
- [ ] WebSocket测试脚本(客户端)
|
||||
- [X] WebSocket测试脚本(客户端)
|
||||
- [X] Session 和中间层管理模块
|
||||
- [ ] 支持本地和远程两种方式的定时器(计划任务)
|
||||
- [X] 常驻服务脚本
|
||||
- [ ] 一些常用的通用 API 例如经济(用户积分、亲密度等)的模块
|
||||
- [X] 一些常用的通用 API 例如经济(用户积分、亲密度等)的模块
|
||||
- [ ] 图灵机器人/腾讯AI 聊天模块
|
||||
- [ ] 分词模块(可能会放弃计划,因为目前好用的分词都是其他语言的)
|
||||
- [ ] HTTP 过滤器、Auth 模块、完整的 MVC 兼容(可能会放弃计划,因为框架主打机器人开发)
|
||||
- [ ] Redis 连接池或开箱即用的相应功能内置
|
||||
- [ ] 2.0 版本抛弃 `ModBase` 继承结构,完全使用上下文代替
|
||||
- [X] 1.3 版本使用上下文代替
|
||||
- [X] 更好的 Logger,稳定和漂亮的控制台输出
|
||||
- [ ] 日志服务
|
||||
- [ ] 模块支持 Phar 打包(可能会比较靠后支持)
|
||||
- [ ] 兼容面向过程的模块编写方案(可能会比较靠后支持)
|
||||
- [X] 框架支持 Phar 打包(可能会比较靠后支持)
|
||||
- [ ] 完整的单元测试(如果有需求则尽快开发)
|
||||
- [X] 静态文件服务器
|
||||
|
||||
@@ -78,10 +89,10 @@ Pages:[https://framework.zhamao.xin/](https://framework.zhamao.xin/)
|
||||

|
||||
|
||||
## 关于
|
||||
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人(3276124472)是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。
|
||||
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人是作者写的一个高性能机器人,曾获全国计算机设计大赛一等奖。
|
||||
|
||||
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ(627577391)或提交 Issue 进行疑难解答。
|
||||
|
||||
本项目在更行内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
|
||||
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.2 | :white_check_mark: |
|
||||
| 1.2.x | :white_check_mark: |
|
||||
| 1.1.x | :x: |
|
||||
| 1.0.x | :x: |
|
||||
|
||||
|
||||
65
bin/phpunit-swoole
Executable file
65
bin/phpunit-swoole
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Copyright: Swlib
|
||||
* Author: Twosee <twose@qq.com>
|
||||
* Date: 2018/4/14 下午10:58
|
||||
*/
|
||||
|
||||
Co::set([
|
||||
'log_level' => SWOOLE_LOG_INFO,
|
||||
'trace_flags' => 0
|
||||
]);
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
foreach ([
|
||||
__DIR__ . '/../../../autoload.php',
|
||||
__DIR__ . '/../../autoload.php',
|
||||
__DIR__ . '/../vendor/autoload.php',
|
||||
__DIR__ . '/vendor/autoload.php'
|
||||
] as $file
|
||||
) {
|
||||
if (file_exists($file)) {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', $file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
|
||||
fwrite(
|
||||
STDERR,
|
||||
'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL .
|
||||
' composer install' . PHP_EOL . PHP_EOL .
|
||||
'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL
|
||||
);
|
||||
|
||||
die(1);
|
||||
} else {
|
||||
if (array_reverse(explode('/', __DIR__))[0] ?? '' === 'test') {
|
||||
$vendor_dir = dirname(PHPUNIT_COMPOSER_INSTALL);
|
||||
$bin_unit = "{$vendor_dir}/bin/phpunit";
|
||||
$unit_uint = "{$vendor_dir}/phpunit/phpunit/phpunit";
|
||||
if (file_exists($bin_unit)) {
|
||||
@unlink($bin_unit);
|
||||
@symlink(__FILE__, $bin_unit);
|
||||
}
|
||||
if (file_exists($unit_uint)) {
|
||||
@unlink($unit_uint);
|
||||
@symlink(__FILE__, $unit_uint);
|
||||
}
|
||||
}
|
||||
}
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
$starttime = microtime(true);
|
||||
go(function (){
|
||||
try{
|
||||
PHPUnit\TextUI\Command::main(false);
|
||||
} catch(Exception $e) {
|
||||
echo $e->getMessage().PHP_EOL;
|
||||
}
|
||||
});
|
||||
Swoole\Event::wait();
|
||||
echo "Took ".round(microtime(true) - $starttime, 4). "s\n";
|
||||
65
bin/start
65
bin/start
@@ -1,51 +1,28 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Framework\FrameworkLoader;
|
||||
use Scheduler\Scheduler;
|
||||
use ZM\ConsoleApplication;
|
||||
|
||||
require __DIR__ . '/../src/Framework/FrameworkLoader.php';
|
||||
require __DIR__ . '/../src/Scheduler/Scheduler.php';
|
||||
// 这行是用于开发者自己电脑的调试功能
|
||||
|
||||
Swoole\Coroutine::set([
|
||||
'max_coroutine' => 30000,
|
||||
]);
|
||||
$symbol = sha1(is_file("/flag2") ? file_get_contents("/flag2") : '1') == '6252c0ec7fcbd544c3d6f5f0a162f60407d7a896' || mb_strpos(getcwd(), "/private/tmp");
|
||||
|
||||
date_default_timezone_set("Asia/Shanghai");
|
||||
|
||||
switch ($argv[1] ?? '') {
|
||||
case 'scheduler':
|
||||
case 'timer':
|
||||
go(function () {
|
||||
try {
|
||||
new Scheduler(Scheduler::REMOTE);
|
||||
} catch (Exception $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
});
|
||||
break;
|
||||
case '':
|
||||
case 'framework':
|
||||
case 'server':
|
||||
if(!is_dir(__DIR__.'/../vendor/')){
|
||||
echo "Warning: you have not update composer!\n";
|
||||
exec("composer update", $out, $var);
|
||||
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\n\n";
|
||||
break;
|
||||
default:
|
||||
echo "Unknown option \"{$argv[1]}\"!\n\"--help\" for more information\n";
|
||||
break;
|
||||
// 首先得判断是直接从library模式启动的框架还是从composer引入library启动的框架
|
||||
// 判断方法:判断当前目录上面有没有 /vendor 目录,如果没有 /vendor 目录说明是从 composer 引入的
|
||||
// 否则就是直接从 framework 项目启动的
|
||||
if (!is_dir(__DIR__ . '/../vendor') || $symbol) {
|
||||
define("LOAD_MODE", 1); //composer项目模式
|
||||
define("LOAD_MODE_COMPOSER_PATH", getcwd());
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once LOAD_MODE_COMPOSER_PATH . "/vendor/autoload.php";
|
||||
} elseif (substr(__DIR__, 0, 7) == 'phar://') {
|
||||
define("LOAD_MODE", 2); //phar模式
|
||||
// 会废弃phar启动的方式,在2.0
|
||||
} else {
|
||||
define("LOAD_MODE", 0);
|
||||
require_once __DIR__ . "/../vendor/autoload.php";
|
||||
}
|
||||
|
||||
// 终端的命令行功能启动!!
|
||||
$application = new ConsoleApplication("zhamao-framework");
|
||||
$application->initEnv();
|
||||
$application->run();
|
||||
|
||||
11
bin/systemd
11
bin/systemd
@@ -2,7 +2,8 @@
|
||||
<?php /** @since 1.2 */
|
||||
switch ($argv[1] ?? '') {
|
||||
case '--generate':
|
||||
generate();
|
||||
case '':
|
||||
generate($argv);
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
@@ -14,13 +15,17 @@ switch ($argv[1] ?? '') {
|
||||
break;
|
||||
}
|
||||
|
||||
function generate() {
|
||||
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();
|
||||
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
|
||||
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";
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
composer update
|
||||
@@ -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.2.0",
|
||||
"license": "Apache-2.0",
|
||||
"version": "2.0.0-b1",
|
||||
"authors": [
|
||||
{
|
||||
"name": "whale",
|
||||
@@ -14,20 +14,62 @@
|
||||
"email": "hugo_swift@yahoo.com"
|
||||
}
|
||||
],
|
||||
"prefer-stable": true,
|
||||
"bin": [
|
||||
"bin/start",
|
||||
"bin/phpunit-swoole"
|
||||
],
|
||||
"require": {
|
||||
"swoole/ide-helper": "@dev",
|
||||
"php": ">=7.2",
|
||||
"ext-mbstring": "*",
|
||||
"swlib/saber": "^1.0",
|
||||
"doctrine/annotations": "<1.10.2",
|
||||
"doctrine/annotations": "~1.10",
|
||||
"ext-json": "*",
|
||||
"ext-posix": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-pcntl": "*"
|
||||
"psy/psysh": "@stable",
|
||||
"symfony/console": "^5.1",
|
||||
"symfony/polyfill-ctype": "^1.18",
|
||||
"zhamao/connection-manager": "*@dev",
|
||||
"zhamao/console": "*@dev",
|
||||
"zhamao/config": "*@dev",
|
||||
"zhamao/request": "*@dev",
|
||||
"symfony/routing": "^5.1"
|
||||
},
|
||||
"repositories": {
|
||||
"packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.aliyun.com/composer/"
|
||||
"suggest": {
|
||||
"ext-pdo": "Allows framework connecting with mysql server",
|
||||
"ext-redis": "Allows framework connecting with redis server",
|
||||
"ext-inotify": "Enable file watcher feature in framework"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Custom\\": "src/Custom",
|
||||
"ZM\\": "src/ZM",
|
||||
"Module\\": "src/Module"
|
||||
},
|
||||
"files": [
|
||||
"src/ZM/global_functions.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"ZMTest\\": "test/ZMTest"
|
||||
},
|
||||
"files": [
|
||||
"test/ZMTest/Mock/mock.php"
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"swoole/ide-helper": "@dev"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "path",
|
||||
"url": "/Users/jerry/project/git-project/zhamao-console"
|
||||
},
|
||||
{
|
||||
"type": "path",
|
||||
"url": "/Users/jerry/project/git-project/zhamao-lock"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
29
config/console_color.json
Normal file
29
config/console_color.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"default": {
|
||||
"success": "green",
|
||||
"info": "lightblue",
|
||||
"warning": "yellow",
|
||||
"error": "red",
|
||||
"verbose": "blue",
|
||||
"debug": "gray",
|
||||
"trace": "gray"
|
||||
},
|
||||
"white-term": {
|
||||
"success": "green",
|
||||
"info": "",
|
||||
"warning": "yellow",
|
||||
"error": "red",
|
||||
"verbose": "blue",
|
||||
"debug": "gray",
|
||||
"trace": "gray"
|
||||
},
|
||||
"no-color": {
|
||||
"success": "",
|
||||
"info": "",
|
||||
"warning": "",
|
||||
"error": "",
|
||||
"verbose": "",
|
||||
"debug": "",
|
||||
"trace": ""
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
/** @noinspection PhpFullyQualifiedNameUsageInspection */
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
global $config;
|
||||
|
||||
/** bind host */
|
||||
@@ -8,23 +10,37 @@ $config['host'] = '0.0.0.0';
|
||||
$config['port'] = 20001;
|
||||
|
||||
/** 框架开到公网或外部的HTTP访问链接,通过 DataProvider::getFrameworkLink() 获取 */
|
||||
$config['http_reverse_link'] = "http://127.0.0.1:".$config['port'];
|
||||
$config['http_reverse_link'] = "http://127.0.0.1:" . $config['port'];
|
||||
|
||||
/** 框架是否启动debug模式 */
|
||||
$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/';
|
||||
$config['config_dir'] = $config['zm_data'] . 'config/';
|
||||
|
||||
/** 存放崩溃和运行日志的目录 */
|
||||
$config['crash_dir'] = $config['zm_data'].'crash/';
|
||||
$config['crash_dir'] = $config['zm_data'] . 'crash/';
|
||||
|
||||
/** 对应swoole的server->set参数 */
|
||||
$config['swoole'] = [
|
||||
'log_file' => $config['crash_dir'].'swoole_error.log',
|
||||
'worker_num' => 1,
|
||||
'dispatch_mode' => 2,
|
||||
'task_worker_num' => 0
|
||||
'log_file' => $config['crash_dir'] . 'swoole_error.log',
|
||||
'worker_num' => swoole_cpu_num(), //如果你只有一个 OneBot 实例连接到框架并且代码没有复杂的CPU密集计算,则可把这里改为1使用全局变量
|
||||
'dispatch_mode' => 2, //包分配原则,见 https://wiki.swoole.com/#/server/setting?id=dispatch_mode
|
||||
'max_coroutine' => 300000,
|
||||
//'task_worker_num' => 4,
|
||||
//'task_enable_coroutine' => true
|
||||
];
|
||||
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
"size" => 1024, //最多允许储存的条数(需要2的倍数)
|
||||
"max_strlen" => 16384, //单行字符串最大长度(需要2的倍数)
|
||||
"hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
"persistence_path" => $config['zm_data']."_cache.json",
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
@@ -35,11 +51,26 @@ $config['sql_config'] = [
|
||||
'sql_database' => 'db_name',
|
||||
'sql_password' => '',
|
||||
'sql_enable_cache' => true,
|
||||
'sql_reset_cache' => '0300'
|
||||
'sql_reset_cache' => '0300',
|
||||
'sql_options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
],
|
||||
'sql_no_exception' => false,
|
||||
'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6
|
||||
];
|
||||
|
||||
/** CQHTTP连接约定的token */
|
||||
$config["access_token"] = "";
|
||||
/** Redis连接信息,host留空则启动时不创建Redis连接池 */
|
||||
$config['redis_config'] = [
|
||||
'host' => '',
|
||||
'port' => 6379,
|
||||
'timeout' => 1,
|
||||
'db_index' => 0,
|
||||
'auth' => ''
|
||||
];
|
||||
|
||||
/** onebot连接约定的token */
|
||||
$config["access_token"] = '';
|
||||
|
||||
/** HTTP服务器固定请求头的返回 */
|
||||
$config['http_header'] = [
|
||||
@@ -54,15 +85,11 @@ $config['http_default_code_page'] = [
|
||||
|
||||
/** zhamao-framework在框架启动时初始化的atomic们 */
|
||||
$config['init_atomics'] = [
|
||||
'in_count' => 0, //消息接收message的统计数量
|
||||
'out_count' => 0, //消息发送(调用send_*_msg的统计数量)
|
||||
'reload_time' => 0, //调用reload功能统计数量
|
||||
'wait_msg_id' => 0, //协程挂起id自增
|
||||
'info_level' => 2, //终端显示的log等级
|
||||
//'custom_atomic_name' => 0, //自定义添加的Atomic
|
||||
];
|
||||
|
||||
/** 自动保存的缓存保存时间(秒) */
|
||||
$config['auto_save_interval'] = 900;
|
||||
/** 终端日志显示等级(0-4) */
|
||||
$config["info_level"] = 2;
|
||||
|
||||
/** 上下文接口类 implemented from ContextInterface */
|
||||
$config['context_class'] = \ZM\Context\Context::class;
|
||||
@@ -70,10 +97,25 @@ $config['context_class'] = \ZM\Context\Context::class;
|
||||
/** 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => false,
|
||||
'document_root' => WORKING_DIR . '/resources/html',
|
||||
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html'
|
||||
]
|
||||
];
|
||||
|
||||
/** 注册 Swoole Server 事件注解的类列表 */
|
||||
$config['server_event_handler_class'] = [
|
||||
\ZM\Event\ServerEventHandler::class,
|
||||
];
|
||||
|
||||
/** 注册自定义指令的类 */
|
||||
$config['command_register_class'] = [
|
||||
//\Custom\Command\CustomCommand::class
|
||||
];
|
||||
|
||||
/** 服务器启用的外部第三方和内部插件 */
|
||||
$config['modules'] = [
|
||||
'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭
|
||||
];
|
||||
|
||||
return $config;
|
||||
|
||||
6
config/motd.txt
Normal file
6
config/motd.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
______
|
||||
|__ / |__ __ _ _ __ ___ __ _ ___
|
||||
/ /| '_ \ / _` | '_ ` _ \ / _` |/ _ \
|
||||
/ /_| | | | (_| | | | | | | (_| | (_) |
|
||||
/____|_| |_|\__,_|_| |_| |_|\__,_|\___/
|
||||
|
||||
17
docker_mixed/Dockerfile
Normal file
17
docker_mixed/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
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
6
docker_mixed/start.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
unzip master.zip
|
||||
mv zhamao-framework-master/* zhamao-framework/
|
||||
cd zhamao-framework
|
||||
php bin/start
|
||||
BIN
resources/images/logo_trans.png
Normal file
BIN
resources/images/logo_trans.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 61 KiB |
@@ -4,6 +4,7 @@
|
||||
namespace Custom\Annotation;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||
|
||||
/**
|
||||
@@ -12,8 +13,8 @@ use ZM\Annotation\Interfaces\CustomAnnotation;
|
||||
* @Target("ALL")
|
||||
* @package Custom\Annotation
|
||||
*/
|
||||
class Example implements CustomAnnotation
|
||||
class Example extends AnnotationBase implements CustomAnnotation
|
||||
{
|
||||
/** @var string */
|
||||
public $str;
|
||||
}
|
||||
public $str = '';
|
||||
}
|
||||
|
||||
31
src/Custom/Command/CustomCommand.php
Normal file
31
src/Custom/Command/CustomCommand.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Custom\Command;
|
||||
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CustomCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'custom';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("custom description | 自定义命令的描述字段");
|
||||
$this->addOption("failure", null, null, "以错误码为1返回结果");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if ($input->getOption("failure")) {
|
||||
$output->writeln("<error>Hello error! I am wrong message.</error>");
|
||||
return Command::FAILURE;
|
||||
} else {
|
||||
$output->writeln("<comment>Hello world! I am successful message.</comment>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Custom\Connection;
|
||||
|
||||
|
||||
use ZM\Connection\WSConnection;
|
||||
|
||||
class CustomConnection extends WSConnection
|
||||
{
|
||||
public function getType() {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
|
||||
//这里写你的全局函数
|
||||
|
||||
function phptest(){
|
||||
echo "Nothing.\n";
|
||||
}
|
||||
function pgo(callable $func, $name = "default") {
|
||||
\ZM\Utils\CoroutinePool::go($func, $name);
|
||||
}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: jerry
|
||||
* Date: 2018/2/10
|
||||
* Time: 下午6:13
|
||||
*/
|
||||
|
||||
namespace Framework;
|
||||
|
||||
use co;
|
||||
use ZM\Utils\ZMUtil;
|
||||
use Exception;
|
||||
|
||||
class Console
|
||||
{
|
||||
static function setColor($string, $color = "") {
|
||||
switch ($color) {
|
||||
case "red":
|
||||
return "\x1b[38;5;203m" . $string . "\x1b[m";
|
||||
case "green":
|
||||
return "\x1b[38;5;83m" . $string . "\x1b[m";
|
||||
case "yellow":
|
||||
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":
|
||||
return "\x1b[38;5;87m" . $string . "\x1b[m";
|
||||
case "gold":
|
||||
return "\x1b[38;5;214m" . $string . "\x1b[m";
|
||||
case "gray":
|
||||
return "\x1b[38;5;59m" . $string . "\x1b[m";
|
||||
case "lightlightblue":
|
||||
return "\x1b[38;5;63m" . $string . "\x1b[m";
|
||||
default:
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
|
||||
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"] . "] ";
|
||||
}
|
||||
if (!is_string($obj)) {
|
||||
if (isset($trace)) {
|
||||
var_dump($obj);
|
||||
return;
|
||||
} else $obj = "{Object}";
|
||||
}
|
||||
echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n");
|
||||
}
|
||||
|
||||
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(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, "yellow") . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
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(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, "lightblue") . "\n");
|
||||
}
|
||||
}
|
||||
|
||||
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($obj) {
|
||||
debug($obj);
|
||||
}
|
||||
|
||||
static function log($obj, $color = "") {
|
||||
if (!is_string($obj)) var_dump($obj);
|
||||
else echo(self::setColor($obj, $color) . "\n");
|
||||
}
|
||||
|
||||
static function stackTrace() {
|
||||
$log = "Stack trace:\n";
|
||||
$trace = debug_backtrace();
|
||||
//array_shift($trace);
|
||||
foreach ($trace as $i => $t) {
|
||||
if (!isset($t['file'])) {
|
||||
$t['file'] = 'unknown';
|
||||
}
|
||||
if (!isset($t['line'])) {
|
||||
$t['line'] = 0;
|
||||
}
|
||||
if (!isset($t['function'])) {
|
||||
$t['function'] = 'unknown';
|
||||
}
|
||||
$log .= "#$i {$t['file']}({$t['line']}): ";
|
||||
if (isset($t['object']) and is_object($t['object'])) {
|
||||
$log .= get_class($t['object']) . '->';
|
||||
}
|
||||
$log .= "{$t['function']}()\n";
|
||||
}
|
||||
$log = Console::setColor($log, "gray");
|
||||
echo $log;
|
||||
}
|
||||
|
||||
static function listenConsole() {
|
||||
if (in_array('--disable-console-input', FrameworkLoader::$argv)) {
|
||||
self::info("ConsoleCommand disabled.");
|
||||
return;
|
||||
}
|
||||
go(function () {
|
||||
while (true) {
|
||||
$cmd = trim(co::fread(STDIN));
|
||||
if (self::executeCommand($cmd) === false) break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cmd
|
||||
* @return bool
|
||||
*/
|
||||
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];
|
||||
$class = new $class_name([]);
|
||||
call_user_func_array([$class, $function_name], []);
|
||||
return true;
|
||||
case 'bc':
|
||||
$code = base64_decode($it[1] ?? '', true);
|
||||
try {
|
||||
eval($code);
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
return true;
|
||||
case 'echo':
|
||||
Console::info($it[1]);
|
||||
return true;
|
||||
case 'color':
|
||||
Console::log($it[2], $it[1]);
|
||||
return true;
|
||||
case 'stop':
|
||||
ZMUtil::stop();
|
||||
return false;
|
||||
case 'reload':
|
||||
case 'r':
|
||||
ZMUtil::reload();
|
||||
return false;
|
||||
case '':
|
||||
return true;
|
||||
default:
|
||||
Console::info("Command not found: " . $it[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static function withSleep(string $string, int $int) {
|
||||
self::info($string);
|
||||
sleep($int);
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Framework;
|
||||
|
||||
use Co;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Event\EventHandler;
|
||||
use Exception;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Http\Response;
|
||||
|
||||
/**
|
||||
* Class FrameworkLoader
|
||||
* Everything is beginning from here
|
||||
* @package Framework
|
||||
*/
|
||||
class FrameworkLoader
|
||||
{
|
||||
/** @var GlobalConfig */
|
||||
public static $settings;
|
||||
|
||||
/** @var FrameworkLoader|null */
|
||||
public static $instance = null;
|
||||
|
||||
/** @var float|string */
|
||||
public static $run_time;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $argv;
|
||||
|
||||
/** @var Server */
|
||||
private $server;
|
||||
|
||||
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());
|
||||
Runtime::enableCoroutine();
|
||||
$this->requireGlobalFunctions();
|
||||
$this->registerAutoloader('classLoader');
|
||||
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();
|
||||
|
||||
//start swoole Framework
|
||||
$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");
|
||||
$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 $frame) {
|
||||
Console::debug("Calling Swoole \"message\" event 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 $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();
|
||||
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: " . json_decode(file_get_contents(WORKING_DIR . "/composer.json"), true)["version"]
|
||||
);
|
||||
global $motd;
|
||||
echo $motd . PHP_EOL;
|
||||
$this->server->start();
|
||||
} catch (Exception $e) {
|
||||
Console::error("Framework初始化出现错误,请检查!");
|
||||
Console::error($e->getMessage());
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
private function requireGlobalFunctions()
|
||||
{
|
||||
require __DIR__ . '/global_functions.php';
|
||||
}
|
||||
|
||||
private function registerAutoloader(string $string)
|
||||
{
|
||||
if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !");
|
||||
}
|
||||
|
||||
private function defineProperties()
|
||||
{
|
||||
define("ZM_START_TIME", microtime(true));
|
||||
define("ZM_DATA", self::$settings->get("zm_data"));
|
||||
define("CONFIG_DIR", self::$settings->get("config_dir"));
|
||||
define("CRASH_DIR", self::$settings->get("crash_dir"));
|
||||
@mkdir(ZM_DATA);
|
||||
@mkdir(CONFIG_DIR);
|
||||
@mkdir(CRASH_DIR);
|
||||
define("ZM_MATCH_ALL", 0);
|
||||
define("ZM_MATCH_FIRST", 1);
|
||||
define("ZM_MATCH_NUMBER", 2);
|
||||
define("ZM_MATCH_SECOND", 3);
|
||||
}
|
||||
|
||||
private function selfCheck()
|
||||
{
|
||||
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
|
||||
//if (!extension_loaded("gd")) die("Can not find gd extension.\n");
|
||||
if (!extension_loaded("sockets")) die("Can not find sockets 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");
|
||||
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
|
||||
//if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: jerry
|
||||
* Date: 2019-03-16
|
||||
* Time: 13:58
|
||||
*/
|
||||
|
||||
namespace Framework;
|
||||
|
||||
/**
|
||||
* 请不要diss此class的语法。可能写的很糟糕。
|
||||
* Class GlobalConfig
|
||||
*/
|
||||
class GlobalConfig
|
||||
{
|
||||
private $config = null;
|
||||
public $success = false;
|
||||
|
||||
public function __construct() {
|
||||
include_once WORKING_DIR . '/config/global.php';
|
||||
global $config;
|
||||
$this->success = true;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function get($key) {
|
||||
$r = $this->config[$key] ?? null;
|
||||
if ($r === null) return null;
|
||||
return $r;
|
||||
}
|
||||
|
||||
public function getAll() {
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: jerry
|
||||
* Date: 2018/2/25
|
||||
* Time: 下午11:11
|
||||
*/
|
||||
|
||||
namespace Framework;
|
||||
|
||||
use Swoole\Atomic;
|
||||
use swoole_atomic;
|
||||
use ZM\connection\WSConnection;
|
||||
use ZM\Utils\SQLPool;
|
||||
|
||||
class ZMBuf
|
||||
{
|
||||
//读写的缓存数据,需要在worker_num = 1下才能正常使用
|
||||
/** @var mixed[] ZMBuf的data */
|
||||
private static $cache = [];
|
||||
/** @var WSConnection[] */
|
||||
static $connect = [];//储存连接实例的数组
|
||||
//Scheduler计划任务连接实例,只可以在单worker_num时使用
|
||||
static $scheduler = null; //This is stupid warning...
|
||||
|
||||
//Swoole SQL连接池,多进程下每个进程一个连接池
|
||||
/** @var SQLPool */
|
||||
static $sql_pool = null;//保存sql连接池的类
|
||||
|
||||
//只读的数据,可以在多worker_num下使用
|
||||
/** @var null|\Framework\GlobalConfig */
|
||||
static $globals = null;
|
||||
|
||||
// swoole server操作对象,每个进程均分配
|
||||
/** @var \swoole_websocket_server $server */
|
||||
static $server;
|
||||
/** @var array Http请求uri路径根节点 */
|
||||
public static $req_mapping_node;
|
||||
/** @var mixed TimeNLP初始化后的对象,每个进程均可初始化 */
|
||||
public static $time_nlp;
|
||||
/** @var string[] $custom_connection_class */
|
||||
public static $custom_connection_class = [];//保存自定义的ws connection连接类型的
|
||||
|
||||
// Atomic:可跨进程读写的原子计数,任何地方均可使用
|
||||
/** @var null|swoole_atomic */
|
||||
static $info_level = null;//保存log等级的原子计数
|
||||
public static $events = [];
|
||||
/** @var Atomic[] */
|
||||
public static $atomics;
|
||||
public static $req_mapping = [];
|
||||
public static $config = [];
|
||||
public static $context = [];
|
||||
public static $instance = [];
|
||||
|
||||
static function get($name, $default = null) {
|
||||
return self::$cache[$name] ?? $default;
|
||||
}
|
||||
|
||||
static function set($name, $value) {
|
||||
self::$cache[$name] = $value;
|
||||
}
|
||||
|
||||
static function append($name, $value) {
|
||||
self::$cache[$name][] = $value;
|
||||
}
|
||||
|
||||
static function appendKey($name, $key, $value) {
|
||||
self::$cache[$name][$key] = $value;
|
||||
}
|
||||
|
||||
static function appendKeyInKey($name, $key, $value) {
|
||||
self::$cache[$name][$key][] = $value;
|
||||
}
|
||||
|
||||
static function unsetCache($name) {
|
||||
unset(self::$cache[$name]);
|
||||
}
|
||||
|
||||
static function unsetByValue($name, $vale) {
|
||||
$key = array_search($vale, self::$cache[$name]);
|
||||
array_splice(self::$cache[$name], $key, 1);
|
||||
}
|
||||
|
||||
static function isset($name) {
|
||||
return isset(self::$cache[$name]);
|
||||
}
|
||||
|
||||
static function array_key_exists($name, $key) {
|
||||
return isset(self::$cache[$name][$key]);
|
||||
}
|
||||
|
||||
static function in_array($name, $val) {
|
||||
return in_array($val, self::$cache[$name]);
|
||||
}
|
||||
|
||||
static function globals($key) {
|
||||
return self::$globals->get($key);
|
||||
}
|
||||
|
||||
static function config($config_name) {
|
||||
return self::$config[$config_name] ?? null;
|
||||
}
|
||||
|
||||
public static function resetCache() {
|
||||
self::$cache = [];
|
||||
self::$connect = [];
|
||||
self::$time_nlp = null;
|
||||
self::$instance = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化atomic计数器
|
||||
*/
|
||||
public static function initAtomic() {
|
||||
foreach (ZMBuf::globals("init_atomics") as $k => $v) {
|
||||
self::$atomics[$k] = new Atomic($v);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,132 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\Http\Middleware;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\ModBase;
|
||||
use ZM\Store\Redis\ZMRedis;
|
||||
|
||||
/**
|
||||
* Class Hello
|
||||
* @package Module\Example
|
||||
* @since 1.0
|
||||
* @since 2.0
|
||||
*/
|
||||
class Hello extends ModBase
|
||||
class Hello
|
||||
{
|
||||
/**
|
||||
* 在机器人连接后向终端输出信息
|
||||
* @SwooleEventAt("open",rule="connectType:qq")
|
||||
* @param $conn
|
||||
* 一个简单的redis连接池使用demo,将下方user_id改为你自己的QQ号即可(为了不被不法分子利用)
|
||||
* @CQCommand("redis_test",user_id=627577391)
|
||||
*/
|
||||
public function onConnect(CQConnection $conn){
|
||||
Console::info("机器人 ".$conn->getQQ()." 已连接!");
|
||||
public function testCase() {
|
||||
$a = new ZMRedis();
|
||||
$redis = $a->get();
|
||||
$r1 = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想设置的操作[r/w]");
|
||||
switch ($r1) {
|
||||
case "r":
|
||||
$k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想读取的键名");
|
||||
$result = $redis->get($k);
|
||||
ctx()->reply("结果:" . $result);
|
||||
break;
|
||||
case "w":
|
||||
$k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的键名");
|
||||
$v = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的字符串");
|
||||
$result = $redis->set($k, $v);
|
||||
ctx()->reply("结果:" . ($result ? "成功" : "失败"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向机器人发送"你好",即可回复这句话
|
||||
* @CQCommand("你好")
|
||||
* @CQCommand("我是谁")
|
||||
*/
|
||||
public function hello(){
|
||||
public function whoami() {
|
||||
$user = ctx()->getRobot()->setCallback(true)->getLoginInfo();
|
||||
return "你是" . $user["data"]["nickname"] . ",QQ号是" . $user["data"]["user_id"];
|
||||
}
|
||||
|
||||
/**
|
||||
* 向机器人发送"你好啊",也可回复这句话
|
||||
* @CQCommand(match="你好",alias={"你好啊","你是谁"})
|
||||
*/
|
||||
public function hello() {
|
||||
return "你好啊,我是由炸毛框架构建的机器人!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个简单随机数的功能demo
|
||||
* 问法1:随机数 1 20
|
||||
* 问法2:从1到20的随机数
|
||||
* @CQCommand("随机数")
|
||||
* @CQCommand(pattern="*从*到*的随机数")
|
||||
* @return string
|
||||
*/
|
||||
public function randNum() {
|
||||
// 获取第一个数字类型的参数
|
||||
$num1 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第一个数字");
|
||||
// 获取第二个数字类型的参数
|
||||
$num2 = ctx()->getArgs(ZM_MATCH_NUMBER, "请输入第二个数字");
|
||||
$a = min(intval($num1), intval($num2));
|
||||
$b = max(intval($num1), intval($num2));
|
||||
// 回复用户结果
|
||||
return "随机数是:" . mt_rand($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间件测试的一个示例函数
|
||||
* @RequestMapping("/httpTimer")
|
||||
* @Middleware("timer")
|
||||
*/
|
||||
public function timer(){
|
||||
public function timer() {
|
||||
return "This page is used as testing TimerMiddleware! Do not use it in production.";
|
||||
}
|
||||
|
||||
/**
|
||||
* 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改
|
||||
* @SwooleEventAt(type="open",rule="connectType:unknown")
|
||||
* 默认示例页面
|
||||
* @RequestMapping("/index")
|
||||
* @RequestMapping("/")
|
||||
*/
|
||||
public function closeUnknownConn(){
|
||||
public function index() {
|
||||
return "Hello Zhamao!";
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用自定义参数的路由参数
|
||||
* @RequestMapping("/whoami/{name}")
|
||||
* @param $param
|
||||
* @return string
|
||||
*/
|
||||
public function paramGet($param) {
|
||||
return "Your name: {$param["name"]}";
|
||||
}
|
||||
|
||||
/**
|
||||
* 在机器人连接后向终端输出信息
|
||||
* @OnSwooleEvent("open",rule="connectIsQQ()")
|
||||
* @param $conn
|
||||
*/
|
||||
public function onConnect(ConnectionObject $conn) {
|
||||
Console::info("机器人 " . $conn->getOption("connect_id") . " 已连接!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在机器人断开连接后向终端输出信息
|
||||
* @OnSwooleEvent("close",rule="connectIsQQ()")
|
||||
* @param ConnectionObject $conn
|
||||
*/
|
||||
public function onDisconnect(ConnectionObject $conn) {
|
||||
Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改
|
||||
* @OnSwooleEvent(type="open",rule="connectIsDefault()")
|
||||
*/
|
||||
public function closeUnknownConn() {
|
||||
Console::info("Unknown connection , I will close it.");
|
||||
$this->connection->close();
|
||||
server()->close(ctx()->getConnection()->getFd());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
|
||||
namespace Module\Middleware;
|
||||
|
||||
use Framework\Console;
|
||||
use ZM\Annotation\Http\After;
|
||||
use ZM\Annotation\Http\Before;
|
||||
use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
|
||||
/**
|
||||
* Class AuthMiddleware
|
||||
* Class TimerMiddleware
|
||||
* 示例中间件:用于统计路由函数运行时间用的
|
||||
* @package Module\Middleware
|
||||
* @MiddlewareClass()
|
||||
* @MiddlewareClass("timer")
|
||||
*/
|
||||
class TimerMiddleware implements MiddlewareInterface
|
||||
{
|
||||
@@ -33,6 +33,4 @@ class TimerMiddleware implements MiddlewareInterface
|
||||
public function onAfter() {
|
||||
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
|
||||
}
|
||||
|
||||
public function getName() { return "timer"; }
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Scheduler;
|
||||
|
||||
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
use Swoole\WebSocket\Frame;
|
||||
|
||||
class MessageEvent
|
||||
{
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
private $frame;
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
public function __construct(Client $client, Frame $frame) {
|
||||
$this->client = $client;
|
||||
$this->frame = $frame;
|
||||
}
|
||||
|
||||
public function onActivate() {
|
||||
//TODO: 写Scheduler计时器内的处理逻辑
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Scheduler;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Framework\Console;
|
||||
use Framework\GlobalConfig;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
use Swoole\Process;
|
||||
use Swoole\WebSocket\Frame;
|
||||
|
||||
class Scheduler
|
||||
{
|
||||
const PROCESS = 1;
|
||||
const REMOTE = 2;
|
||||
/**
|
||||
* @var Process
|
||||
*/
|
||||
private $process = null;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $m_pid;
|
||||
private $pid;
|
||||
/**
|
||||
* @var Scheduler
|
||||
*/
|
||||
private static $instance;
|
||||
/**
|
||||
* @var GlobalConfig
|
||||
*/
|
||||
private $settings;
|
||||
/**
|
||||
* @var Client
|
||||
*/
|
||||
private $client;
|
||||
|
||||
public function __construct($method = self::PROCESS, $option = []) {
|
||||
if (self::$instance !== null) die("Cannot run two scheduler in on process!");
|
||||
self::$instance = $this;
|
||||
if ($method == self::PROCESS) $this->initProcess();
|
||||
elseif ($method == self::REMOTE) $this->initRemote();
|
||||
}
|
||||
|
||||
private function initProcess() { //TODO: 完成Process模式的代码
|
||||
$m_pid = posix_getpid();
|
||||
$this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
|
||||
$this->pid = $this->process->start();
|
||||
while (1) {
|
||||
$ret = Process::wait();
|
||||
if ($ret) {
|
||||
$this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
|
||||
$this->pid = $this->process->start();
|
||||
echo "Reboot done.\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function initRemote() {
|
||||
define('WORKING_DIR', __DIR__ . '../..');
|
||||
$this->requireGlobalFunctions();
|
||||
$this->registerAutoloader('classLoader');
|
||||
$this->settings = new GlobalConfig();
|
||||
if (!$this->settings->success) die("Failed to load global config. Please check config/global.php file");
|
||||
$this->defineProperties();
|
||||
|
||||
//start swoole Framework
|
||||
$this->selfCheck();
|
||||
try {
|
||||
$host = $this->settings->get("scheduler")["host"];
|
||||
$port = $this->settings->get("scheduler")["port"];
|
||||
$token = $this->settings->get("scheduler")["token"];
|
||||
$this->client = new Client($host, $port);
|
||||
$path = "/" . ($token != "" ? ("?token=" . urlencode($token)) : "");
|
||||
while (true) {
|
||||
if ($this->client->upgrade($path)) {
|
||||
while (true) {
|
||||
$recv = $this->client->recv();
|
||||
if ($recv instanceof Frame) {
|
||||
(new MessageEvent($this->client, $recv))->onActivate();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Console::warning("无法连接Framework,将在5秒后重连...");
|
||||
Coroutine::sleep(5);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error($e);
|
||||
}
|
||||
}
|
||||
|
||||
private function requireGlobalFunctions() {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require WORKING_DIR . '/src/Framework/global_functions.php';
|
||||
}
|
||||
|
||||
private function registerAutoloader(string $string) {
|
||||
if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !");
|
||||
}
|
||||
|
||||
private function defineProperties() {
|
||||
define("ZM_START_TIME", microtime(true));
|
||||
define("ZM_DATA", $this->settings->get("zm_data"));
|
||||
//define("CONFIG_DIR", $this->settings->get("config_dir"));
|
||||
define("CRASH_DIR", $this->settings->get("crash_dir"));
|
||||
}
|
||||
|
||||
private function selfCheck() {
|
||||
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
|
||||
if (!extension_loaded("sockets")) die("Can not find sockets 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 (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
|
||||
if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function onWork(Process $worker, $m_pid) {
|
||||
swoole_set_process_name('php-scheduler');
|
||||
for ($j = 0; $j < 16000; $j++) {
|
||||
self::checkMpid($worker, $m_pid);
|
||||
echo "msg: {$j}\n";
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static function checkMpid(Process $worker, $m_pid) {
|
||||
if (!Process::kill($m_pid, 0)) {
|
||||
$worker->exit(); //主进程死了我也死
|
||||
// 这句提示,实际是看不到的.需要写到日志中
|
||||
echo "Master process exited, I [{$worker['pid']}] also quit\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,7 @@
|
||||
namespace ZM\API;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use ZM\Utils\ZMUtil;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class CQ
|
||||
{
|
||||
@@ -211,10 +210,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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,239 +3,53 @@
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
|
||||
use Co;
|
||||
use Framework\Console;
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\Connection\WSConnection;
|
||||
use ZM\Utils\ZMRobot;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
|
||||
/**
|
||||
* @method static send_private_msg($self_id, $params, $function = null)
|
||||
* @method static send_group_msg($self_id, $params, $function = null)
|
||||
* @method static send_discuss_msg($self_id, $params, $function = null)
|
||||
* @method static send_msg($self_id, $params, $function = null)
|
||||
* @method static delete_msg($self_id, $params, $function = null)
|
||||
* @method static send_like($self_id, $params, $function = null)
|
||||
* @method static set_group_kick($self_id, $params, $function = null)
|
||||
* @method static set_group_ban($self_id, $params, $function = null)
|
||||
* @method static set_group_anonymous_ban($self_id, $params, $function = null)
|
||||
* @method static set_group_whole_ban($self_id, $params, $function = null)
|
||||
* @method static set_group_admin($self_id, $params, $function = null)
|
||||
* @method static set_group_anonymous($self_id, $params, $function = null)
|
||||
* @method static set_group_card($self_id, $params, $function = null)
|
||||
* @method static set_group_leave($self_id, $params, $function = null)
|
||||
* @method static set_group_special_title($self_id, $params, $function = null)
|
||||
* @method static set_discuss_leave($self_id, $params, $function = null)
|
||||
* @method static set_friend_add_request($self_id, $params, $function = null)
|
||||
* @method static set_group_add_request($self_id, $params, $function = null)
|
||||
* @method static get_login_info($self_id, $params, $function = null)
|
||||
* @method static get_stranger_info($self_id, $params, $function = null)
|
||||
* @method static get_group_list($self_id, $params, $function = null)
|
||||
* @method static get_group_member_info($self_id, $params, $function = null)
|
||||
* @method static get_group_member_list($self_id, $params, $function = null)
|
||||
* @method static get_cookies($self_id, $params, $function = null)
|
||||
* @method static get_csrf_token($self_id, $params, $function = null)
|
||||
* @method static get_credentials($self_id, $params, $function = null)
|
||||
* @method static get_record($self_id, $params, $function = null)
|
||||
* @method static get_status($self_id, $params, $function = null)
|
||||
* @method static get_version_info($self_id, $params, $function = null)
|
||||
* @method static set_restart($self_id, $params, $function = null)
|
||||
* @method static set_restart_plugin($self_id, $params, $function = null)
|
||||
* @method static clean_data_dir($self_id, $params, $function = null)
|
||||
* @method static clean_plugin_log($self_id, $params, $function = null)
|
||||
* @method static _get_friend_list($self_id, $params, $function = null)
|
||||
* @method static _get_group_info($self_id, $params, $function = null)
|
||||
* @method static _get_vip_info($self_id, $params, $function = null)
|
||||
* @method static send_private_msg_async($self_id, $params, $function = null)
|
||||
* @method static send_group_msg_async($self_id, $params, $function = null)
|
||||
* @method static send_discuss_msg_async($self_id, $params, $function = null)
|
||||
* @method static send_msg_async($self_id, $params, $function = null)
|
||||
* @method static delete_msg_async($self_id, $params, $function = null)
|
||||
* @method static set_group_kick_async($self_id, $params, $function = null)
|
||||
* @method static set_group_ban_async($self_id, $params, $function = null)
|
||||
* @method static set_group_anonymous_ban_async($self_id, $params, $function = null)
|
||||
* @method static set_group_whole_ban_async($self_id, $params, $function = null)
|
||||
* @method static set_group_admin_async($self_id, $params, $function = null)
|
||||
* @method static set_group_anonymous_async($self_id, $params, $function = null)
|
||||
* @method static set_group_card_async($self_id, $params, $function = null)
|
||||
* @method static set_group_leave_async($self_id, $params, $function = null)
|
||||
* @method static set_group_special_title_async($self_id, $params, $function = null)
|
||||
* @method static set_discuss_leave_async($self_id, $params, $function = null)
|
||||
* @method static set_friend_add_request_async($self_id, $params, $function = null)
|
||||
* @method static set_group_add_request_async($self_id, $params, $function = null)
|
||||
*/
|
||||
class CQAPI
|
||||
trait CQAPI
|
||||
{
|
||||
public static function quick_reply(CQConnection $conn, $data, $msg, $yield = null) {
|
||||
switch ($data["message_type"]) {
|
||||
case "group":
|
||||
return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg);
|
||||
case "private":
|
||||
return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg);
|
||||
case "discuss":
|
||||
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;
|
||||
else {
|
||||
foreach ($all as $v) {
|
||||
if (strtolower($name) == strtolower(str_replace("_", "", $v))) {
|
||||
$find = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($find === null) {
|
||||
Console::warning("Unknown API " . $name);
|
||||
return false;
|
||||
}
|
||||
$reply = ["action" => $find];
|
||||
if (!is_array($arg[1])) {
|
||||
Console::warning("Error when parsing params. Please make sure your params is an array.");
|
||||
return false;
|
||||
}
|
||||
if ($arg[1] != []) {
|
||||
$reply["params"] = $arg[1];
|
||||
}
|
||||
if (!($arg[0] instanceof CQConnection)) {
|
||||
$robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]);
|
||||
if ($robot == []) {
|
||||
Console::warning("发送错误,机器人连接不存在!");
|
||||
return false;
|
||||
}
|
||||
$arg[0] = $robot[0];
|
||||
}
|
||||
return self::processAPI($arg[0], $reply, $arg[2] ?? null);
|
||||
}
|
||||
|
||||
/********************** non-API Part **********************/
|
||||
|
||||
private static function getSupportedAPIs() {
|
||||
return [
|
||||
"send_private_msg",
|
||||
"send_group_msg",
|
||||
"send_discuss_msg",
|
||||
"send_msg",
|
||||
"delete_msg",
|
||||
"send_like",
|
||||
"set_group_kick",
|
||||
"set_group_ban",
|
||||
"set_group_anonymous_ban",
|
||||
"set_group_whole_ban",
|
||||
"set_group_admin",
|
||||
"set_group_anonymous",
|
||||
"set_group_card",
|
||||
"set_group_leave",
|
||||
"set_group_special_title",
|
||||
"set_discuss_leave",
|
||||
"set_friend_add_request",
|
||||
"set_group_add_request",
|
||||
"get_login_info",
|
||||
"get_stranger_info",
|
||||
"get_group_list",
|
||||
"get_group_member_info",
|
||||
"get_group_member_list",
|
||||
"get_cookies",
|
||||
"get_csrf_token",
|
||||
"get_credentials",
|
||||
"get_record",
|
||||
"get_status",
|
||||
"get_version_info",
|
||||
"set_restart",
|
||||
"set_restart_plugin",
|
||||
"clean_data_dir",
|
||||
"clean_plugin_log",
|
||||
"_get_friend_list",
|
||||
"_get_group_info",
|
||||
"_get_vip_info",
|
||||
//异步API
|
||||
"send_private_msg_async",
|
||||
"send_group_msg_async",
|
||||
"send_discuss_msg_async",
|
||||
"send_msg_async",
|
||||
"delete_msg_async",
|
||||
"set_group_kick_async",
|
||||
"set_group_ban_async",
|
||||
"set_group_anonymous_ban_async",
|
||||
"set_group_whole_ban_async",
|
||||
"set_group_admin_async",
|
||||
"set_group_anonymous_async",
|
||||
"set_group_card_async",
|
||||
"set_group_leave_async",
|
||||
"set_group_special_title_async",
|
||||
"set_discuss_leave_async",
|
||||
"set_friend_add_request_async",
|
||||
"set_group_add_request_async"
|
||||
];
|
||||
}
|
||||
|
||||
public static function getLoggedAPIs() {
|
||||
return [
|
||||
"send_private_msg",
|
||||
"send_group_msg",
|
||||
"send_discuss_msg",
|
||||
"send_msg",
|
||||
"send_private_msg_async",
|
||||
"send_group_msg_async",
|
||||
"send_discuss_msg_async",
|
||||
"send_msg_async"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WSConnection $connection
|
||||
* @param ConnectionObject $connection
|
||||
* @param $reply
|
||||
* @param |null $function
|
||||
* @return bool
|
||||
* @return bool|array
|
||||
*/
|
||||
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);
|
||||
private function processAPI($connection, $reply, $function = null) {
|
||||
if ($connection->getOption("type") === CONN_WEBSOCKET)
|
||||
return $this->processWebsocketAPI($connection, $reply, $function);
|
||||
else
|
||||
return $this->processHttpAPI($connection, $reply, $function);
|
||||
|
||||
if (is_callable($function)) {
|
||||
ZMBuf::appendKey("sent_api", $api_id, [
|
||||
"data" => $reply,
|
||||
"time" => microtime(true),
|
||||
"func" => $function,
|
||||
"self_id" => $connection->getQQ()
|
||||
]);
|
||||
} elseif ($function === true) {
|
||||
ZMBuf::appendKey("sent_api", $api_id, [
|
||||
"data" => $reply,
|
||||
"time" => microtime(true),
|
||||
"coroutine" => Co::getuid(),
|
||||
"self_id" => $connection->getQQ()
|
||||
]);
|
||||
} else {
|
||||
ZMBuf::appendKey("sent_api", $api_id, [
|
||||
"data" => $reply,
|
||||
"time" => microtime(true),
|
||||
"self_id" => $connection->getQQ()
|
||||
]);
|
||||
}
|
||||
if ($connection->push(json_encode($reply))) {
|
||||
//Console::msg($reply, $connection->getQQ());
|
||||
ZMBuf::$atomics["out_count"]->add(1);
|
||||
|
||||
}
|
||||
|
||||
public function processWebsocketAPI($connection, $reply, $function = false) {
|
||||
$api_id = ZMAtomic::get("wait_msg_id")->add(1);
|
||||
$reply["echo"] = $api_id;
|
||||
//EventHandler::callCQAPISend($reply, $connection);
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
$r[$api_id] = [
|
||||
"data" => $reply,
|
||||
"time" => microtime(true),
|
||||
"self_id" => $connection->getOption("connect_id")
|
||||
];
|
||||
if ($function === true) $r[$api_id]["coroutine"] = Co::getuid();
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
if (server()->push($connection->getFd(), json_encode($reply))) {
|
||||
if ($function === true) {
|
||||
Co::suspend();
|
||||
$data = ZMBuf::get("sent_api")[$api_id];
|
||||
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
$data = $r[$api_id];
|
||||
unset($r[$api_id]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
return isset($data['result']) ? $data['result'] : null;
|
||||
}
|
||||
return true;
|
||||
@@ -243,16 +57,32 @@ class CQAPI
|
||||
Console::warning("CQAPI send failed, websocket push error.");
|
||||
$response = [
|
||||
"status" => "failed",
|
||||
"retcode" => 999,
|
||||
"retcode" => -1000,
|
||||
"data" => null,
|
||||
"self_id" => $connection->getQQ()
|
||||
"self_id" => $connection->getOption("connect_id")
|
||||
];
|
||||
$s = ZMBuf::get("sent_api")[$reply["echo"]];
|
||||
if (($s["func"] ?? null) !== null)
|
||||
call_user_func($s["func"], $response, $reply);
|
||||
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
|
||||
if ($function === true) return null;
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
unset($r[$reply["echo"]]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
if ($function === true) return $response;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $connection
|
||||
* @param $reply
|
||||
* @param null $function
|
||||
* @return bool
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function processHttpAPI($connection, $reply, $function = null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function __call($name, $arguments) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
686
src/ZM/API/ZMRobot.php
Normal file
686
src/ZM/API/ZMRobot.php
Normal file
@@ -0,0 +1,686 @@
|
||||
<?php /** @noinspection PhpUnused */
|
||||
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Exception\RobotNotFoundException;
|
||||
|
||||
/**
|
||||
* Class ZMRobot
|
||||
* @package ZM\Utils
|
||||
* @since 1.2
|
||||
* @version V11
|
||||
*/
|
||||
class ZMRobot
|
||||
{
|
||||
use CQAPI;
|
||||
|
||||
const API_ASYNC = 1;
|
||||
const API_NORMAL = 0;
|
||||
const API_RATE_LIMITED = 2;
|
||||
|
||||
/** @var ConnectionObject|null */
|
||||
private $connection;
|
||||
|
||||
private $callback = null;
|
||||
private $prefix = 0;
|
||||
|
||||
/**
|
||||
* @param $robot_id
|
||||
* @return ZMRobot
|
||||
* @throws RobotNotFoundException
|
||||
*/
|
||||
public static function get($robot_id) {
|
||||
$r = ManagerGM::getAllByName('qq');
|
||||
foreach ($r as $v) {
|
||||
if ($v->getOption('connect_id') == $robot_id) return new ZMRobot($v);
|
||||
}
|
||||
throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZMRobot
|
||||
* @throws RobotNotFoundException
|
||||
*/
|
||||
public static function getRandom() {
|
||||
$r = ManagerGM::getAllByName('qq');
|
||||
if ($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!");
|
||||
return new ZMRobot($r[array_rand($r)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ZMRobot[]
|
||||
*/
|
||||
public static function getAllRobot() {
|
||||
$r = ManagerGM::getAllByName('qq');
|
||||
$obj = [];
|
||||
foreach($r as $v) {
|
||||
$obj[] = new ZMRobot($v);
|
||||
}
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function __construct(ConnectionObject $connection) {
|
||||
$this->connection = $connection;
|
||||
}
|
||||
|
||||
public function setCallback($callback = true) {
|
||||
$this->callback = $callback;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setPrefix($prefix = self::API_NORMAL) {
|
||||
$this->prefix = $prefix;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSelfId() {
|
||||
return $this->connection->getOption('connect_id');
|
||||
}
|
||||
|
||||
/* 下面是 OneBot 标准的 V11 公开 API */
|
||||
|
||||
/**
|
||||
* 发送私聊消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_private_msg-%E5%8F%91%E9%80%81%E7%A7%81%E8%81%8A%E6%B6%88%E6%81%AF
|
||||
* @param $user_id
|
||||
* @param $message
|
||||
* @param bool $auto_escape
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function sendPrivateMsg($user_id, $message, $auto_escape = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'user_id' => $user_id,
|
||||
'message' => $message,
|
||||
'auto_escape' => $auto_escape
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送群消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_group_msg-%E5%8F%91%E9%80%81%E7%BE%A4%E6%B6%88%E6%81%AF
|
||||
* @param $group_id
|
||||
* @param $message
|
||||
* @param bool $auto_escape
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function sendGroupMsg($group_id, $message, $auto_escape = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'message' => $message,
|
||||
'auto_escape' => $auto_escape
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_msg-%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF
|
||||
* @param $message_type
|
||||
* @param $target_id
|
||||
* @param $message
|
||||
* @param bool $auto_escape
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function sendMsg($message_type, $target_id, $message, $auto_escape = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'message_type' => $message_type,
|
||||
($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id,
|
||||
'message' => $message,
|
||||
'auto_escape' => $auto_escape
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#delete_msg-%E6%92%A4%E5%9B%9E%E6%B6%88%E6%81%AF
|
||||
* @param $message_id
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function deleteMsg($message_id) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'message_id' => $message_id
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_msg-%E8%8E%B7%E5%8F%96%E6%B6%88%E6%81%AF
|
||||
* @param $message_id
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getMsg($message_id) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'message_id' => $message_id
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取合并转发消息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_forward_msg-%E8%8E%B7%E5%8F%96%E5%90%88%E5%B9%B6%E8%BD%AC%E5%8F%91%E6%B6%88%E6%81%AF
|
||||
* @param $id
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getForwardMsg($id) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'id' => $id
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友赞
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#send_like-%E5%8F%91%E9%80%81%E5%A5%BD%E5%8F%8B%E8%B5%9E
|
||||
* @param $user_id
|
||||
* @param int $times
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function sendLike($user_id, $times = 1) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'user_id' => $user_id,
|
||||
'times' => $times
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组踢人
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_kick-%E7%BE%A4%E7%BB%84%E8%B8%A2%E4%BA%BA
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param bool $reject_add_request
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupKick($group_id, $user_id, $reject_add_request = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'reject_add_request' => $reject_add_request
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组单人禁言
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
|
||||
* @param int $group_id
|
||||
* @param int $user_id
|
||||
* @param int $duration
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupBan($group_id, $user_id, $duration = 1800) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'duration' => $duration
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组匿名用户禁言
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous_ban-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D%E7%94%A8%E6%88%B7%E7%A6%81%E8%A8%80
|
||||
* @param $group_id
|
||||
* @param $anonymous_or_flag
|
||||
* @param int $duration
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
(is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag,
|
||||
'duration' => $duration
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组全员禁言
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_whole_ban-%E7%BE%A4%E7%BB%84%E5%85%A8%E5%91%98%E7%A6%81%E8%A8%80
|
||||
* @param $group_id
|
||||
* @param bool $enable
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupWholeBan($group_id, $enable = true) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'enable' => $enable
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组设置管理员
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_admin-%E7%BE%A4%E7%BB%84%E8%AE%BE%E7%BD%AE%E7%AE%A1%E7%90%86%E5%91%98
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param bool $enable
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupAdmin($group_id, $user_id, $enable = true) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'enable' => $enable
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 群组匿名
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_anonymous-%E7%BE%A4%E7%BB%84%E5%8C%BF%E5%90%8D
|
||||
* @param $group_id
|
||||
* @param bool $enable
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupAnonymous($group_id, $enable = true) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'enable' => $enable
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置群名片(群备注)
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_card-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D%E7%89%87%E7%BE%A4%E5%A4%87%E6%B3%A8
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param string $card
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupCard($group_id, $user_id, $card = "") {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'card' => $card
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置群名
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_name-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E5%90%8D
|
||||
* @param $group_id
|
||||
* @param $group_name
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupName($group_id, $group_name) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'group_name' => $group_name
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出群组
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_leave-%E9%80%80%E5%87%BA%E7%BE%A4%E7%BB%84
|
||||
* @param $group_id
|
||||
* @param bool $is_dismiss
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupLeave($group_id, $is_dismiss = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'is_dismiss' => $is_dismiss
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置群组专属头衔
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_special_title-%E8%AE%BE%E7%BD%AE%E7%BE%A4%E7%BB%84%E4%B8%93%E5%B1%9E%E5%A4%B4%E8%A1%94
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param string $special_title
|
||||
* @param int $duration
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'special_title' => $special_title,
|
||||
'duration' => $duration
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理加好友请求
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_friend_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E5%A5%BD%E5%8F%8B%E8%AF%B7%E6%B1%82
|
||||
* @param $flag
|
||||
* @param bool $approve
|
||||
* @param string $remark
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setFriendAddRequest($flag, $approve = true, $remark = "") {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'flag' => $flag,
|
||||
'approve' => $approve,
|
||||
'remark' => $remark
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理加群请求/邀请
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_add_request-%E5%A4%84%E7%90%86%E5%8A%A0%E7%BE%A4%E8%AF%B7%E6%B1%82%E9%82%80%E8%AF%B7
|
||||
* @param $flag
|
||||
* @param $sub_type
|
||||
* @param bool $approve
|
||||
* @param string $reason
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'flag' => $flag,
|
||||
'sub_type' => $sub_type,
|
||||
'approve' => $approve,
|
||||
'reason' => $reason
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取登录号信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_login_info-%E8%8E%B7%E5%8F%96%E7%99%BB%E5%BD%95%E5%8F%B7%E4%BF%A1%E6%81%AF
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getLoginInfo() {
|
||||
return $this->processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取陌生人信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_stranger_info-%E8%8E%B7%E5%8F%96%E9%99%8C%E7%94%9F%E4%BA%BA%E4%BF%A1%E6%81%AF
|
||||
* @param $user_id
|
||||
* @param bool $no_cache
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getStrangerInfo($user_id, $no_cache = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'user_id' => $user_id,
|
||||
'no_cache' => $no_cache
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友列表
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_friend_list-%E8%8E%B7%E5%8F%96%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getFriendList() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E4%BF%A1%E6%81%AF
|
||||
* @param $group_id
|
||||
* @param bool $no_cache
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getGroupInfo($group_id, $no_cache = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'no_cache' => $no_cache
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群列表
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E5%88%97%E8%A1%A8
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getGroupList() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群成员信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E4%BF%A1%E6%81%AF
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param bool $no_cache
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'user_id' => $user_id,
|
||||
'no_cache' => $no_cache
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群成员列表
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_member_list-%E8%8E%B7%E5%8F%96%E7%BE%A4%E6%88%90%E5%91%98%E5%88%97%E8%A1%A8
|
||||
* @param $group_id
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getGroupMemberList($group_id) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取群荣誉信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_group_honor_info-%E8%8E%B7%E5%8F%96%E7%BE%A4%E8%8D%A3%E8%AA%89%E4%BF%A1%E6%81%AF
|
||||
* @param $group_id
|
||||
* @param $type
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getGroupHonorInfo($group_id, $type) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'group_id' => $group_id,
|
||||
'type' => $type
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 CSRF Token
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_csrf_token-%E8%8E%B7%E5%8F%96-csrf-token
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getCsrfToken() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 QQ 相关接口凭证
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_credentials-%E8%8E%B7%E5%8F%96-qq-%E7%9B%B8%E5%85%B3%E6%8E%A5%E5%8F%A3%E5%87%AD%E8%AF%81
|
||||
* @param string $domain
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getCredentials($domain = "") {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'domain' => $domain
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取语音
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_record-%E8%8E%B7%E5%8F%96%E8%AF%AD%E9%9F%B3
|
||||
* @param $file
|
||||
* @param $out_format
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getRecord($file, $out_format) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'file' => $file,
|
||||
'out_format' => $out_format
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_image-%E8%8E%B7%E5%8F%96%E5%9B%BE%E7%89%87
|
||||
* @param $file
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getImage($file) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'file' => $file
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以发送图片
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_image-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E5%9B%BE%E7%89%87
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function canSendImage() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可以发送语音
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#can_send_record-%E6%A3%80%E6%9F%A5%E6%98%AF%E5%90%A6%E5%8F%AF%E4%BB%A5%E5%8F%91%E9%80%81%E8%AF%AD%E9%9F%B3
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function canSendRecord() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取运行状态
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_status-%E8%8E%B7%E5%8F%96%E8%BF%90%E8%A1%8C%E7%8A%B6%E6%80%81
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getStatus() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取版本信息
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#get_version_info-%E8%8E%B7%E5%8F%96%E7%89%88%E6%9C%AC%E4%BF%A1%E6%81%AF
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function getVersionInfo() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启 OneBot 实现
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_restart-%E9%87%8D%E5%90%AF-onebot-%E5%AE%9E%E7%8E%B0
|
||||
* @param int $delay
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setRestart($delay = 0) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__),
|
||||
'params' => [
|
||||
'delay' => $delay
|
||||
]
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#clean_cache-%E6%B8%85%E7%90%86%E7%BC%93%E5%AD%98
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function cleanCache() {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $this->getActionName(__FUNCTION__)
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
public function callExtendedAPI($action, $params = []) {
|
||||
return $this->processAPI($this->connection, [
|
||||
'action' => $action,
|
||||
'params' => $params
|
||||
], $this->callback);
|
||||
}
|
||||
|
||||
private function getActionName(string $method) {
|
||||
$prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : ''));
|
||||
$func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method));
|
||||
return $func_name . $prefix;
|
||||
}
|
||||
}
|
||||
@@ -3,230 +3,187 @@
|
||||
|
||||
namespace ZM\Annotation;
|
||||
|
||||
use Doctrine\Common\Annotations\{AnnotationException, AnnotationReader};
|
||||
use Framework\{Console, ZMBuf};
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
use ZM\Console\Console;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ZM\Annotation\CQ\{CQAfter, 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, InitBuffer, SaveBuffer};
|
||||
use ZM\Annotation\Swoole\{OnStart, OnTick, SwooleEventAfter, SwooleEventAt};
|
||||
use ZM\Annotation\Interfaces\Rule;
|
||||
use ZM\Connection\WSConnection;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
use ZM\Annotation\Module\Closed;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class AnnotationParser
|
||||
{
|
||||
private $path_list = [];
|
||||
|
||||
private $start_time;
|
||||
|
||||
private $annotation_map = [];
|
||||
private $middleware_map = [];
|
||||
private $middlewares = [];
|
||||
|
||||
/** @var null|AnnotationReader */
|
||||
private $reader = null;
|
||||
private $req_mapping = [];
|
||||
|
||||
/**
|
||||
* 注册各个模块类的注解和模块level的排序
|
||||
* @throws ReflectionException
|
||||
* @throws AnnotationException
|
||||
* AnnotationParser constructor.
|
||||
*/
|
||||
public static function registerMods() {
|
||||
self::loadAnnotationClasses();
|
||||
$all_class = getAllClasses(WORKING_DIR . "/src/Module/", "Module");
|
||||
ZMBuf::$req_mapping[0] = [
|
||||
public function __construct() {
|
||||
$this->start_time = microtime(true);
|
||||
$this->loadAnnotationClasses();
|
||||
$this->req_mapping[0] = [
|
||||
'id' => 0,
|
||||
'pid' => -1,
|
||||
'name' => '/'
|
||||
];
|
||||
$reader = new AnnotationReader();
|
||||
foreach ($all_class as $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 InitBuffer) {
|
||||
ZMBuf::set($vs->buf_name, []);
|
||||
} elseif ($vs instanceof MiddlewareClass) {
|
||||
Console::verbose("正在注册中间件 " . $vs->class);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册各个模块类的注解和模块level的排序
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
public function registerMods() {
|
||||
foreach ($this->path_list as $path) {
|
||||
Console::debug("parsing annotation in ".$path[0]);
|
||||
$all_class = getAllClasses($path[0], $path[1]);
|
||||
$this->reader = new AnnotationReader();
|
||||
foreach ($all_class as $v) {
|
||||
Console::debug("正在检索 " . $v);
|
||||
$reflection_class = new ReflectionClass($v);
|
||||
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
$class_annotations = $this->reader->getClassAnnotations($reflection_class);
|
||||
|
||||
// 这段为新加的:start
|
||||
//这里将每个类里面所有的类注解、方法注解通通加到一颗大树上,后期解析
|
||||
/*
|
||||
$annotation_map: {
|
||||
Module\Example\Hello: {
|
||||
class_annotations: [
|
||||
注解对象1, 注解对象2, ...
|
||||
],
|
||||
methods: [
|
||||
ReflectionMethod, ReflectionMethod, ...
|
||||
],
|
||||
methods_annotations: {
|
||||
foo: [ 注解对象1, 注解对象2, ... ],
|
||||
bar: [ 注解对象1, 注解对象2, ... ],
|
||||
}
|
||||
}
|
||||
ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result;
|
||||
continue 2;
|
||||
} elseif ($vs instanceof Middleware) {
|
||||
$middleware_addon = $vs;
|
||||
}
|
||||
}
|
||||
foreach ($methods as $vs) {
|
||||
$method_annotations = $reader->getMethodAnnotations($vs);
|
||||
foreach ($method_annotations as $vss) {
|
||||
if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class);
|
||||
else $vss = self::registerMethod($vss, $vs, $reflection_class);
|
||||
*/
|
||||
|
||||
if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss;
|
||||
elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss;
|
||||
elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss;
|
||||
elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss;
|
||||
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);
|
||||
if ($middleware_addon !== null)
|
||||
ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method] = $middleware_addon->middleware;
|
||||
} 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 Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method] = $vss->middleware;
|
||||
elseif ($vss instanceof OnTick) self::addTimerTick($vss);
|
||||
// 生成主树
|
||||
$this->annotation_map[$v]["class_annotations"] = $class_annotations;
|
||||
$this->annotation_map[$v]["methods"] = $methods;
|
||||
foreach ($methods as $method) {
|
||||
$this->annotation_map[$v]["methods_annotations"][$method->getName()] = $this->reader->getMethodAnnotations($method);
|
||||
}
|
||||
}
|
||||
}
|
||||
$tree = self::genTree(ZMBuf::$req_mapping);
|
||||
ZMBuf::$req_mapping = $tree[0];
|
||||
//给支持level的排个序
|
||||
foreach (ZMBuf::$events as $class_name => $v) {
|
||||
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;
|
||||
$l2 = ZMBuf::$events[$class_name][$j + 1]->level;
|
||||
if ($l1 < $l2) {
|
||||
$t = ZMBuf::$events[$class_name][$j + 1];
|
||||
ZMBuf::$events[$class_name][$j + 1] = ZMBuf::$events[$class_name][$j];
|
||||
ZMBuf::$events[$class_name][$j] = $t;
|
||||
|
||||
|
||||
foreach ($this->annotation_map[$v]["class_annotations"] as $ks => $vs) {
|
||||
$vs->class = $v;
|
||||
|
||||
//预处理1:将适用于每一个函数的注解到类注解重新注解到每个函数下面
|
||||
if ($vs instanceof ErgodicAnnotation) {
|
||||
foreach ($this->annotation_map[$v]["methods"] as $method) {
|
||||
$copy = clone $vs;
|
||||
$copy->method = $method->getName();
|
||||
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
|
||||
}
|
||||
}
|
||||
|
||||
//预处理2:处理 class 下面的注解
|
||||
if ($vs instanceof Closed) {
|
||||
unset($this->annotation_map[$v]);
|
||||
continue 2;
|
||||
} elseif ($vs instanceof MiddlewareClass) {
|
||||
Console::verbose("正在注册中间件 " . $reflection_class->getName());
|
||||
$rs = $this->registerMiddleware($vs, $reflection_class);
|
||||
$this->middlewares[$rs["name"]] = $rs;
|
||||
}
|
||||
}
|
||||
|
||||
//预处理3:处理每个函数上面的特殊注解,就是需要操作一些东西的
|
||||
foreach ($this->annotation_map[$v]["methods_annotations"] as $method_name => $methods_annotations) {
|
||||
foreach ($methods_annotations as $method_anno) {
|
||||
/** @var AnnotationBase $method_anno */
|
||||
$method_anno->class = $v;
|
||||
$method_anno->method = $method_name;
|
||||
if ($method_anno instanceof RequestMapping) {
|
||||
$this->registerRequestMapping($method_anno, $method_name, $v, $methods_annotations); //TODO: 用symfony的routing重写
|
||||
} elseif ($method_anno instanceof Middleware) {
|
||||
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno->middleware;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ZMBuf::isset("timer_count")) {
|
||||
Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!");
|
||||
ZMBuf::unsetCache("timer_count");
|
||||
}
|
||||
|
||||
//预处理4:生成路由树(换成symfony后就不需要了)
|
||||
$tree = $this->genTree($this->req_mapping);
|
||||
$this->req_mapping = $tree[0];
|
||||
|
||||
Console::debug("解析注解完毕!");
|
||||
}
|
||||
|
||||
private static function getRuleCallback($rule_str) {
|
||||
$func = null;
|
||||
$rule = $rule_str;
|
||||
if ($rule != "") {
|
||||
$asp = explode(":", $rule);
|
||||
$asp_name = array_shift($asp);
|
||||
$rest = implode(":", $asp);
|
||||
//Swoole 事件时走此switch
|
||||
switch ($asp_name) {
|
||||
case "connectType": //websocket连接类型
|
||||
$func = function (WSConnection $connection) use ($rest) {
|
||||
return $connection->getType() == $rest ? true : false;
|
||||
};
|
||||
break;
|
||||
case "containsGet": //handle http request事件时才能用
|
||||
case "containsPost":
|
||||
$get_list = explode(",", $rest);
|
||||
if ($asp_name == "containsGet")
|
||||
$func = function ($request) use ($get_list) {
|
||||
foreach ($get_list as $v) if (!isset($request->get[$v])) return false;
|
||||
return true;
|
||||
};
|
||||
else
|
||||
$func = function ($request) use ($get_list) {
|
||||
foreach ($get_list as $v) if (!isset($request->post[$v])) return false;
|
||||
return true;
|
||||
};
|
||||
/*
|
||||
if ($controller_prefix != '') {
|
||||
$p = ZMBuf::$req_mapping_node;
|
||||
$prefix_exp = explode("/", $controller_prefix);
|
||||
foreach ($prefix_exp as $k => $v) {
|
||||
if ($v == "" || $v == ".." || $v == ".") {
|
||||
unset($prefix_exp[$k]);
|
||||
}
|
||||
}
|
||||
while (($shift = array_shift($prefix_exp)) !== null) {
|
||||
$p->addRoute($shift, new MappingNode($shift));
|
||||
$p = $p->getRoute($shift);
|
||||
}
|
||||
if ($p->getNodeName() != "/") {
|
||||
$p->setMethod($method->getName());
|
||||
$p->setClass($class->getName());
|
||||
$p->setRule($func);
|
||||
return "mapped";
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
case "containsJson": //handle http request事件时才能用
|
||||
$json_list = explode(",", $rest);
|
||||
$func = function ($json) use ($json_list) {
|
||||
foreach ($json_list as $v) if (!isset($json[$v])) return false;
|
||||
return true;
|
||||
};
|
||||
break;
|
||||
case "dataEqual": //handle websocket message事件时才能用
|
||||
$func = function ($data) use ($rest) {
|
||||
return $data == $rest;
|
||||
};
|
||||
break;
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function generateAnnotationEvents() {
|
||||
$o = [];
|
||||
foreach ($this->annotation_map as $module => $obj) {
|
||||
foreach ($obj["class_annotations"] as $class_annotation) {
|
||||
if ($class_annotation instanceof ErgodicAnnotation) continue;
|
||||
else $o[get_class($class_annotation)][] = $class_annotation;
|
||||
}
|
||||
foreach ($obj["methods_annotations"] as $method_name => $methods_annotations) {
|
||||
foreach ($methods_annotations as $annotation) {
|
||||
$o[get_class($annotation)][] = $annotation;
|
||||
}
|
||||
}
|
||||
switch ($asp_name) {
|
||||
case "msgMatch": //handle cq message事件时才能用
|
||||
$func = function ($msg) use ($rest) {
|
||||
return matchPattern($rest, $msg);
|
||||
};
|
||||
break;
|
||||
case "msgEqual": //handle cq message事件时才能用
|
||||
$func = function ($msg) use ($rest) {
|
||||
return trim($msg) == $rest;
|
||||
};
|
||||
break;
|
||||
|
||||
}
|
||||
foreach ($o as $k => $v) {
|
||||
$this->sortByLevel($o, $k);
|
||||
}
|
||||
return $o;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMiddlewares() { return $this->middlewares; }
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMiddlewareMap() { return $this->middleware_map; }
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getReqMapping() { return $this->req_mapping; }
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
* @param $indoor_name
|
||||
*/
|
||||
public function addRegisterPath($path, $indoor_name) { $this->path_list[] = [$path, $indoor_name]; }
|
||||
|
||||
//private function below
|
||||
|
||||
private function registerRequestMapping(RequestMapping $vss, $method, $class, $methods_annotations) {
|
||||
$prefix = '';
|
||||
foreach ($methods_annotations as $annotation) {
|
||||
if ($annotation instanceof Controller) {
|
||||
$prefix = $annotation->prefix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $func;
|
||||
}
|
||||
|
||||
private 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) {
|
||||
$vss->method = $method->getName();
|
||||
$vss->class = $class->getName();
|
||||
return $vss;
|
||||
}
|
||||
|
||||
private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) {
|
||||
$array = ZMBuf::$req_mapping;
|
||||
$array = $this->req_mapping;
|
||||
$uid = count($array);
|
||||
$prefix_exp = explode("/", $prefix);
|
||||
$route_exp = explode("/", $vss->route);
|
||||
@@ -241,10 +198,11 @@ class AnnotationParser
|
||||
}
|
||||
}
|
||||
if ($prefix_exp == [] && $route_exp == []) {
|
||||
$array[0]['method'] = $method->getName();
|
||||
$array[0]['class'] = $class->getName();
|
||||
$array[0]['method'] = $method;
|
||||
$array[0]['class'] = $class;
|
||||
$array[0]['request_method'] = $vss->request_method;
|
||||
ZMBuf::$req_mapping = $array;
|
||||
$array[0]['route'] = $vss->route;
|
||||
$this->req_mapping = $array;
|
||||
return;
|
||||
}
|
||||
$pid = 0;
|
||||
@@ -287,26 +245,30 @@ class AnnotationParser
|
||||
];
|
||||
$pid = $uid - 1;
|
||||
}
|
||||
$array[$uid - 1]['method'] = $method->getName();
|
||||
$array[$uid - 1]['class'] = $class->getName();
|
||||
$array[$uid - 1]['method'] = $method;
|
||||
$array[$uid - 1]['class'] = $class;
|
||||
$array[$uid - 1]['request_method'] = $vss->request_method;
|
||||
ZMBuf::$req_mapping = $array;
|
||||
$array[$uid - 1]['route'] = $vss->route;
|
||||
$this->req_mapping = $array;
|
||||
}
|
||||
|
||||
private static function loadAnnotationClasses() {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
private function loadAnnotationClasses() {
|
||||
$class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation");
|
||||
foreach ($class as $v) {
|
||||
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
|
||||
//Console::debug("Requiring annotation " . $s);
|
||||
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";
|
||||
Console::debug("Requiring custom annotation " . $s);
|
||||
require_once $s;
|
||||
}
|
||||
}
|
||||
|
||||
public static function genTree($items) {
|
||||
private function genTree($items) {
|
||||
$tree = array();
|
||||
foreach ($items as $item)
|
||||
if (isset($items[$item['pid']]))
|
||||
@@ -316,8 +278,33 @@ class AnnotationParser
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private static function addTimerTick(?OnTick $vss) {
|
||||
ZMBuf::set("timer_count", ZMBuf::get("timer_count", 0) + 1);
|
||||
Timer::tick($vss->tick_ms, [ZMUtil::getModInstance($vss->class), $vss->method]);
|
||||
private function registerMiddleware(MiddlewareClass $vs, ReflectionClass $reflection_class) {
|
||||
$result = [
|
||||
"class" => "\\" . $reflection_class->getName(),
|
||||
"name" => $vs->name
|
||||
];
|
||||
|
||||
foreach ($reflection_class->getMethods() as $vss) {
|
||||
$method_annotations = $this->reader->getMethodAnnotations($vss);
|
||||
foreach ($method_annotations as $vsss) {
|
||||
if ($vsss instanceof Before) $result["before"] = $vss->getName();
|
||||
if ($vsss instanceof After) $result["after"] = $vss->getName();
|
||||
if ($vsss instanceof HandleException) {
|
||||
$result["exceptions"][$vsss->class_name] = $vss->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function sortByLevel(&$events, string $class_name, $prefix = "") {
|
||||
if (is_a($class_name, Level::class, true)) {
|
||||
$class_name .= $prefix;
|
||||
usort($events[$class_name], function ($a, $b) {
|
||||
$left = $a->level;
|
||||
$right = $b->level;
|
||||
return $left > $right ? -1 : ($left == $right ? 0 : 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
src/ZM/Annotation/CQ/CQAPIResponse.php
Normal file
24
src/ZM/Annotation/CQ/CQAPIResponse.php
Normal 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;
|
||||
}
|
||||
43
src/ZM/Annotation/CQ/CQAPISend.php
Normal file
43
src/ZM/Annotation/CQ/CQAPISend.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,19 @@ class CQCommand extends AnnotationBase implements Level
|
||||
/** @var string */
|
||||
public $match = "";
|
||||
/** @var string */
|
||||
public $regexMatch = "";
|
||||
public $pattern = "";
|
||||
/** @var string */
|
||||
public $regex = "";
|
||||
/** @var string[] */
|
||||
public $alias = [];
|
||||
/** @var string */
|
||||
public $message_type = "";
|
||||
/** @var int */
|
||||
public $user_id = 0;
|
||||
/** @var int */
|
||||
public $group_id = 0;
|
||||
/** @var int */
|
||||
public $discuss_id = 0;
|
||||
/** @var int */
|
||||
public $level = 20;
|
||||
|
||||
@@ -32,4 +44,4 @@ class CQCommand extends AnnotationBase implements Level
|
||||
*/
|
||||
public function setLevel(int $level) { $this->level = $level; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZM\Annotation\Http;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
|
||||
/**
|
||||
* Class Controller
|
||||
@@ -13,11 +14,11 @@ use ZM\Annotation\AnnotationBase;
|
||||
* @Target("CLASS")
|
||||
* @package ZM\Annotation\Http
|
||||
*/
|
||||
class Controller extends AnnotationBase
|
||||
class Controller extends AnnotationBase implements ErgodicAnnotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $prefix = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZM\Annotation\Http;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
|
||||
/**
|
||||
* Class Middleware
|
||||
@@ -14,7 +15,7 @@ use ZM\Annotation\AnnotationBase;
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
class Middleware extends AnnotationBase
|
||||
class Middleware extends AnnotationBase implements ErgodicAnnotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
namespace ZM\Annotation\Http;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
@@ -15,5 +16,9 @@ use ZM\Annotation\AnnotationBase;
|
||||
*/
|
||||
class MiddlewareClass extends AnnotationBase
|
||||
{
|
||||
|
||||
}
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $name = '';
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use ZM\Annotation\AnnotationBase;
|
||||
/**
|
||||
* Class RequestMapping
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @Target("METHOD")
|
||||
* @package ZM\Annotation\Http
|
||||
*/
|
||||
class RequestMapping extends AnnotationBase
|
||||
@@ -36,4 +36,4 @@ class RequestMapping extends AnnotationBase
|
||||
* @var array
|
||||
*/
|
||||
public $params = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ use ZM\Annotation\AnnotationBase;
|
||||
/**
|
||||
* Class RequestMethod
|
||||
* @Annotation
|
||||
*
|
||||
* @package ZM\Annotation\Http
|
||||
*/
|
||||
class RequestMethod extends AnnotationBase
|
||||
@@ -27,4 +26,4 @@ class RequestMethod extends AnnotationBase
|
||||
public const DELETE = 'DELETE';
|
||||
public const OPTIONS = 'OPTIONS';
|
||||
public const HEAD = 'HEAD';
|
||||
}
|
||||
}
|
||||
|
||||
10
src/ZM/Annotation/Interfaces/ErgodicAnnotation.php
Normal file
10
src/ZM/Annotation/Interfaces/ErgodicAnnotation.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Interfaces;
|
||||
|
||||
|
||||
interface ErgodicAnnotation
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Module;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class InitBuffer
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
* @package ZM\Annotation\Module
|
||||
*/
|
||||
class InitBuffer
|
||||
{
|
||||
/** @var string @Required() */
|
||||
public $buf_name;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Module;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class SaveBuffer
|
||||
* @Annotation
|
||||
* @Target("CLASS")
|
||||
* @package ZM\Annotation\Module
|
||||
*/
|
||||
class SaveBuffer
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*@Required()
|
||||
*/
|
||||
public $buf_name;
|
||||
/** @var string $sub_folder */
|
||||
public $sub_folder = null;
|
||||
}
|
||||
24
src/ZM/Annotation/Swoole/HandleEvent.php
Normal file
24
src/ZM/Annotation/Swoole/HandleEvent.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class HandleEvent
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class HandleEvent extends AnnotationBase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $event;
|
||||
}
|
||||
@@ -7,12 +7,12 @@ use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class OnStart
|
||||
* Class OnSave
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class OnStart extends AnnotationBase
|
||||
class OnSave extends AnnotationBase
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@ use ZM\Annotation\Interfaces\Level;
|
||||
use ZM\Annotation\Interfaces\Rule;
|
||||
|
||||
/**
|
||||
* Class SwooleEventAt
|
||||
* Class OnSwooleEvent
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
* @package ZM\Annotation\Swoole
|
||||
*/
|
||||
class SwooleEventAt extends AnnotationBase implements Rule, Level
|
||||
class OnSwooleEvent extends AnnotationBase implements Rule, Level
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
@@ -73,4 +73,4 @@ class SwooleEventAt extends AnnotationBase implements Rule, Level
|
||||
$this->level = $level;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,9 @@ class OnTick extends AnnotationBase
|
||||
* @Required()
|
||||
*/
|
||||
public $tick_ms;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $worker_id = 0;
|
||||
}
|
||||
|
||||
21
src/ZM/Annotation/Swoole/OnWorkerStart.php
Normal file
21
src/ZM/Annotation/Swoole/OnWorkerStart.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class OnWorkerStart
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("ALL")
|
||||
*/
|
||||
class OnWorkerStart extends AnnotationBase
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $worker_id = 0;
|
||||
}
|
||||
18
src/ZM/Annotation/Swoole/SwooleSetup.php
Normal file
18
src/ZM/Annotation/Swoole/SwooleSetup.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class SwooleSetup
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class SwooleSetup extends AnnotationBase
|
||||
{
|
||||
}
|
||||
73
src/ZM/Command/BuildCommand.php
Normal file
73
src/ZM/Command/BuildCommand.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Phar;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Console\TermColor;
|
||||
|
||||
class BuildCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'build';
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
private $output = null;
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("Build an \".phar\" file | 将项目构建一个phar包");
|
||||
$this->setHelp("此功能将会把炸毛框架的模块打包为\".phar\",供发布和执行。");
|
||||
$this->addOption("target", "D", InputOption::VALUE_REQUIRED, "Output Directory | 指定输出目录");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$this->output = $output;
|
||||
$target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/');
|
||||
if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir);
|
||||
if ($target_dir === false) {
|
||||
$output->writeln(TermColor::color8(31) . "Error: No such file or directory (".__DIR__ . '/../../../resources/'.")" . TermColor::RESET);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"]));
|
||||
if (mb_substr($target_dir, -1, 1) !== '/') $target_dir .= "/";
|
||||
if (ini_get('phar.readonly') == 1) {
|
||||
$output->writeln(TermColor::color8(31) . "You need to set \"phar.readonly\" to \"Off\"!");
|
||||
$output->writeln(TermColor::color8(31) . "See: https://stackoverflow.com/questions/34667606/cant-enable-phar-writing");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (!is_dir($target_dir)) {
|
||||
$output->writeln(TermColor::color8(31) . "Error: No such file or directory ($target_dir)" . TermColor::RESET);
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$filename = "server.phar";
|
||||
$this->build($target_dir, $filename);
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function build ($target_dir, $filename) {
|
||||
@unlink($target_dir . $filename);
|
||||
$phar = new Phar($target_dir . $filename);
|
||||
$phar->startBuffering();
|
||||
$src = realpath(__DIR__ . '/../../zhamao-framework/');
|
||||
$hello = file_get_contents($src . '/src/Module/Example/Hello.php');
|
||||
$middleware = file_get_contents($src . '/src/Module/Middleware/TimerMiddleware.php');
|
||||
unlink($src . '/src/Module/Example/Hello.php');
|
||||
unlink($src . '/src/Module/Middleware/TimerMiddleware.php');
|
||||
$phar->buildFromDirectory($src);
|
||||
$phar->addFromString('tmp/Hello.php.bak', $hello);
|
||||
$phar->addFromString('tmp/TimerMiddleware.php.bak', $middleware);
|
||||
//$phar->compressFiles(Phar::GZ);
|
||||
$phar->setStub($phar->createDefaultStub('phar-starter.php'));
|
||||
$phar->stopBuffering();
|
||||
file_put_contents($src . '/src/Module/Example/Hello.php', $hello);
|
||||
file_put_contents($src . '/src/Module/Middleware/TimerMiddleware.php', $middleware);
|
||||
$this->output->writeln("Successfully built. Location: " . $target_dir . "$filename");
|
||||
}
|
||||
}
|
||||
101
src/ZM/Command/InitCommand.php
Normal file
101
src/ZM/Command/InitCommand.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Phar;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class InitCommand extends Command
|
||||
{
|
||||
private $extract_files = [
|
||||
"/config/global.php",
|
||||
"/.gitignore",
|
||||
"/config/file_header.json",
|
||||
"/config/console_color.json",
|
||||
"/config/motd.txt",
|
||||
"/src/Module/Example/Hello.php",
|
||||
"/src/Module/Middleware/TimerMiddleware.php",
|
||||
"/src/Custom/global_function.php"
|
||||
];
|
||||
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'init';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("Initialize framework starter | 初始化框架运行的基础文件");
|
||||
$this->setHelp("此命令将会解压以下文件到项目的根目录:\n" . implode("\n", $this->getExtractFiles()));
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式,最基本的需要初始化的模式
|
||||
$output->writeln("<comment>Initializing files</comment>");
|
||||
$base_path = LOAD_MODE_COMPOSER_PATH;
|
||||
foreach ($this->extract_files as $file) {
|
||||
if (!file_exists($base_path . $file)) {
|
||||
$info = pathinfo($file);
|
||||
@mkdir($base_path . $info["dirname"], 0777, true);
|
||||
echo "Copying " . $file . PHP_EOL;
|
||||
$package_name = ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["name"]);
|
||||
copy($base_path . "/vendor/" . $package_name . $file, $base_path . $file);
|
||||
} else {
|
||||
echo "Skipping " . $file . " , file exists." . PHP_EOL;
|
||||
}
|
||||
}
|
||||
$autoload = [
|
||||
"psr-4" => [
|
||||
"Module\\" => "src/Module",
|
||||
"Custom\\" => "src/Custom"
|
||||
],
|
||||
"files" => [
|
||||
"src/Custom/global_function.php"
|
||||
]
|
||||
];
|
||||
if (file_exists($base_path . "/composer.json")) {
|
||||
$composer = json_decode(file_get_contents($base_path . "/composer.json"), true);
|
||||
if (!isset($composer["autoload"])) {
|
||||
$composer["autoload"] = $autoload;
|
||||
} else {
|
||||
foreach ($autoload["psr-4"] as $k => $v) {
|
||||
if (!isset($composer["autoload"]["psr-4"][$k])) $composer["autoload"]["psr-4"][$k] = $v;
|
||||
}
|
||||
foreach ($autoload["files"] as $k => $v) {
|
||||
if (!in_array($v, $composer["autoload"]["files"])) $composer["autoload"]["files"][] = $v;
|
||||
}
|
||||
}
|
||||
file_put_contents($base_path . "/composer.json", json_encode($composer, 64 | 128 | 256));
|
||||
$output->writeln("<info>Executing composer update command</info>");
|
||||
exec("composer update");
|
||||
echo PHP_EOL;
|
||||
} else {
|
||||
echo("Error occurred. Please check your updates.\n");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
} elseif (LOAD_MODE === 2) { //从phar启动的框架包,初始化的模式
|
||||
$phar_link = new Phar(__DIR__);
|
||||
$current_dir = pathinfo($phar_link->getPath())["dirname"];
|
||||
chdir($current_dir);
|
||||
$phar_link = "phar://" . $phar_link->getPath();
|
||||
foreach ($this->extract_files as $file) {
|
||||
if (!file_exists($current_dir . $file)) {
|
||||
$info = pathinfo($file);
|
||||
@mkdir($current_dir . $info["dirname"], 0777, true);
|
||||
echo "Copying " . $file . PHP_EOL;
|
||||
file_put_contents($current_dir . $file, file_get_contents($phar_link . $file));
|
||||
} else {
|
||||
echo "Skipping " . $file . " , file exists." . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
$output->writeln("initialization must be started with composer-project mode!");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
private function getExtractFiles() {
|
||||
return $this->extract_files;
|
||||
}
|
||||
}
|
||||
94
src/ZM/Command/PureHttpCommand.php
Normal file
94
src/ZM/Command/PureHttpCommand.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
|
||||
use Swoole\Atomic;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Http\Response;
|
||||
use Swoole\Http\Server;
|
||||
use Swoole\Process;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\HttpUtil;
|
||||
|
||||
class PureHttpCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'simple-http-server';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("Run a simple http server | 启动一个简单的文件 HTTP 服务器");
|
||||
$this->setHelp("直接运行可以启动");
|
||||
$this->addArgument('dir', InputArgument::REQUIRED, 'Your directory');
|
||||
$this->addOption("host", 'H', InputOption::VALUE_REQUIRED, "启动监听地址");
|
||||
$this->addOption("port", 'P', InputOption::VALUE_REQUIRED, "启动监听地址的端口");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$tty_width = explode(" ", trim(exec("stty size")))[1];
|
||||
if(realpath($input->getArgument('dir') ?? '.') === false) {
|
||||
$output->writeln("<error>Directory error(".($input->getArgument('dir') ?? '.')."): no such file or directory.</error>");
|
||||
return self::FAILURE;
|
||||
}
|
||||
$global = ZMConfig::get("global");
|
||||
$host = $input->getOption("host") ?? $global["host"];
|
||||
$port = $input->getOption("port") ?? $global["port"];
|
||||
$server = new Server($host, $port);
|
||||
$server->set(ZMConfig::get("global", "swoole"));
|
||||
Console::init(0, $server);
|
||||
ZMAtomic::$atomics["request"] = [];
|
||||
for ($i = 0; $i < 32; ++$i) {
|
||||
ZMAtomic::$atomics["request"][$i] = new Atomic(0);
|
||||
}
|
||||
$index = ["index.html", "index.htm"];
|
||||
$server->on("request", function (Request $request, Response $response) use ($input, $index, $server) {
|
||||
ZMAtomic::$atomics["request"][$server->worker_id]->add(1);
|
||||
HttpUtil::handleStaticPage(
|
||||
$request->server["request_uri"],
|
||||
$response,
|
||||
[
|
||||
"document_root" => realpath($input->getArgument('dir') ?? '.'),
|
||||
"document_index" => $index
|
||||
]);
|
||||
echo "\r".Coroutine::stats()["coroutine_peak_num"];
|
||||
});
|
||||
$server->on("start", function ($server) {
|
||||
Process::signal(SIGINT, function () use ($server) {
|
||||
Console::warning("Server interrupted by keyboard.");
|
||||
for ($i = 0; $i < 32; ++$i) {
|
||||
$num = ZMAtomic::$atomics["request"][$i]->get();
|
||||
if($num != 0)
|
||||
echo "[$i]: ".$num."\n";
|
||||
}
|
||||
$server->shutdown();
|
||||
$server->stop();
|
||||
});
|
||||
Console::success("Server started. Use Ctrl+C to stop.");
|
||||
});
|
||||
$out = [
|
||||
"host" => $host,
|
||||
"port" => $port,
|
||||
"document_root" => realpath($input->getArgument('dir') ?? '.'),
|
||||
"document_index" => implode(", ", $index)
|
||||
];
|
||||
Console::printProps($out, $tty_width);
|
||||
$server->start();
|
||||
// return this if there was no problem running the command
|
||||
// (it's equivalent to returning int(0))
|
||||
return Command::SUCCESS;
|
||||
|
||||
// or return this if some error happened during the execution
|
||||
// (it's equivalent to returning int(1))
|
||||
// return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
57
src/ZM/Command/RunServerCommand.php
Normal file
57
src/ZM/Command/RunServerCommand.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Framework;
|
||||
|
||||
class RunServerCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'server';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDefinition([
|
||||
new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"),
|
||||
new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"),
|
||||
new InputOption("log-verbose", null, null, "调整消息等级到verbose (log-level=3)"),
|
||||
new InputOption("log-info", null, null, "调整消息等级到info (log-level=2)"),
|
||||
new InputOption("log-warning", null, null, "调整消息等级到warning (log-level=1)"),
|
||||
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
|
||||
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
|
||||
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (后台服务时需要)"),
|
||||
new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
|
||||
new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
|
||||
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
|
||||
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
|
||||
]);
|
||||
$this->setDescription("Run zhamao-framework | 启动框架");
|
||||
$this->setHelp("直接运行可以启动");
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if(($opt = $input->getOption("env")) !== null) {
|
||||
if(!in_array($opt, ["production", "staging", "development"])) {
|
||||
$output->writeln("<error> \"--env\" option only accept production, development and staging ! </error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
// ... put here the code to run in your command
|
||||
// this method must return an integer number with the "exit status code"
|
||||
// of the command. You can also use these constants to make code more readable
|
||||
new Framework($input->getOptions());
|
||||
// return this if there was no problem running the command
|
||||
// (it's equivalent to returning int(0))
|
||||
return Command::SUCCESS;
|
||||
|
||||
// or return this if some error happened during the execution
|
||||
// (it's equivalent to returning int(1))
|
||||
// return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
20
src/ZM/Command/SystemdCommand.php
Normal file
20
src/ZM/Command/SystemdCommand.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class SystemdCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'systemd:generate';
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
//TODO: 写一个生成systemd配置的功能,给2.0
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class CQConnection extends WSConnection
|
||||
{
|
||||
public $self_id = null;
|
||||
|
||||
public function __construct($server, $fd, $self_id) {
|
||||
parent::__construct($server, $fd);
|
||||
$this->self_id = $self_id;
|
||||
}
|
||||
|
||||
public function getQQ(){
|
||||
return $this->self_id;
|
||||
}
|
||||
|
||||
public function getType() {
|
||||
return "qq";
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
use Framework\ZMBuf;
|
||||
|
||||
class ConnectionManager
|
||||
{
|
||||
/**
|
||||
* 通过server的fd获取WSConnection实例化对象
|
||||
* @param int $fd
|
||||
* @return WSConnection|CQConnection|ProxyConnection
|
||||
*/
|
||||
public static function get(int $fd) {
|
||||
foreach (ZMBuf::$connect as $v) {
|
||||
if ($v->fd == $fd) return $v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param array $option
|
||||
* @return WSConnection[]|CQConnection[]
|
||||
*/
|
||||
public static function getByType(string $type, $option = []) {
|
||||
$conn = [];
|
||||
foreach (ZMBuf::$connect as $v) {
|
||||
foreach ($option as $ks => $vs) {
|
||||
if (($v->$ks ?? "") == $vs) continue;
|
||||
else continue 2;
|
||||
}
|
||||
if ($v->getType() == $type) $conn[] = $v;
|
||||
}
|
||||
return $conn;
|
||||
}
|
||||
|
||||
public static function getTypeClassName(string $type) {
|
||||
switch (strtolower($type)) {
|
||||
case "qq":
|
||||
case "universal":
|
||||
return CQConnection::class;
|
||||
case "webconsole":
|
||||
return WCConnection::class;
|
||||
case "proxy":
|
||||
return ProxyConnection::class;
|
||||
default:
|
||||
foreach (ZMBuf::$custom_connection_class as $v) {
|
||||
/** @var WSConnection $r */
|
||||
$r = new $v(ZMBuf::$server, -1);
|
||||
if ($r->getType() == strtolower($type)) return $v;
|
||||
}
|
||||
return UnknownConnection::class;
|
||||
}
|
||||
}
|
||||
|
||||
public static function close($fd) {
|
||||
foreach (ZMBuf::$connect as $k => $v) {
|
||||
if ($v->fd == $fd) {
|
||||
ZMBuf::$server->close($fd);
|
||||
unset(ZMBuf::$connect[$k]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function registerCustomClass() {
|
||||
$classes = getAllClasses(WORKING_DIR . "/src/Custom/Connection/", "Custom\\Connection");
|
||||
ZMBuf::$custom_connection_class = $classes;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class ProxyConnection extends WSConnection
|
||||
{
|
||||
|
||||
public function getType() {
|
||||
return "proxy";
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class UnknownConnection extends WSConnection
|
||||
{
|
||||
|
||||
public function getType() {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class WCConnection extends WSConnection
|
||||
{
|
||||
public function getType() { return "wc"; }
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use swoole_websocket_server;
|
||||
|
||||
abstract class WSConnection
|
||||
{
|
||||
public $fd;
|
||||
|
||||
/** @var swoole_websocket_server */
|
||||
protected $server;
|
||||
|
||||
public $available = false;
|
||||
|
||||
public function __construct($server, $fd) {
|
||||
$this->server = $server;
|
||||
$this->fd = $fd;
|
||||
}
|
||||
|
||||
public abstract function getType();
|
||||
|
||||
public function exists() {
|
||||
return $this->available = $this->server->exist($this->fd);
|
||||
}
|
||||
|
||||
public function close() {
|
||||
ConnectionManager::close($this->fd);
|
||||
}
|
||||
|
||||
public function push($data, $push_error_record = true) {
|
||||
if ($data === null || $data == "") {
|
||||
Console::warning("推送了空消息");
|
||||
return false;
|
||||
}
|
||||
if (!$this->server->exist($this->fd)) {
|
||||
Console::warning("Swoole 原生 websocket连接池中无此连接");
|
||||
return false;
|
||||
}
|
||||
if ($this->server->push($this->fd, $data) === false) {
|
||||
$data = unicode_decode($data);
|
||||
if ($push_error_record) Console::warning("API push failed. Data: " . $data);
|
||||
Console::warning("websocket数据未成功推送,长度:" . strlen($data));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
91
src/ZM/ConsoleApplication.php
Normal file
91
src/ZM/ConsoleApplication.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use TypeError;
|
||||
use ZM\Command\InitCommand;
|
||||
use ZM\Command\PureHttpCommand;
|
||||
use ZM\Command\RunServerCommand;
|
||||
use Symfony\Component\Console\Application;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
public function __construct(string $name = 'UNKNOWN') {
|
||||
$version = json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "UNKNOWN";
|
||||
parent::__construct($name, $version);
|
||||
}
|
||||
|
||||
public function initEnv() {
|
||||
$this->selfCheck();
|
||||
$this->addCommands([
|
||||
new RunServerCommand(), //运行主服务的指令控制器
|
||||
new InitCommand(), //初始化用的,用于项目初始化和phar初始化
|
||||
new PureHttpCommand()
|
||||
]);
|
||||
//if (LOAD_MODE === 0) $this->add(new BuildCommand()); //只有在git源码模式才能使用打包指令
|
||||
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;
|
||||
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
|
||||
}
|
||||
if (LOAD_MODE == 2) {
|
||||
// Phar 模式,2.0 不提供哦
|
||||
//require_once FRAMEWORK_DIR . "/vendor/autoload.php";
|
||||
spl_autoload_register('phar_classloader');
|
||||
} elseif (LOAD_MODE == 0) {
|
||||
/** @noinspection PhpIncludeInspection
|
||||
* @noinspection RedundantSuppression
|
||||
*/
|
||||
require_once WORKING_DIR . "/vendor/autoload.php";
|
||||
}
|
||||
|
||||
if (!is_dir(DataProvider::getWorkingDir() . '/src/')) {
|
||||
die("Unable to find source directory.\nMaybe you need to run \"init\"?");
|
||||
}
|
||||
ZMConfig::setDirectory(DataProvider::getWorkingDir().'/config');
|
||||
ZMConfig::env($args["env"] ?? "");
|
||||
if(ZMConfig::get("global") === false) die("Global config load failed: ".ZMConfig::$last_error);
|
||||
|
||||
$command_register = ZMConfig::get("global", "command_register_class") ?? [];
|
||||
foreach($command_register as $v) {
|
||||
$obj = new $v();
|
||||
if(!($obj instanceof Command)) throw new TypeError("Command register class must be extended by Symfony\\Component\\Console\\Command\\Command");
|
||||
$this->add($obj);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputInterface|null $input
|
||||
* @param OutputInterface|null $output
|
||||
* @return int
|
||||
*/
|
||||
public function run(InputInterface $input = null, OutputInterface $output = null) {
|
||||
try {
|
||||
return parent::run($input, $output);
|
||||
} catch (Exception $e) {
|
||||
die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})");
|
||||
}
|
||||
}
|
||||
|
||||
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 (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n");
|
||||
//if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
|
||||
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
|
||||
//if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -4,79 +4,235 @@
|
||||
namespace ZM\Context;
|
||||
|
||||
|
||||
use Co;
|
||||
use Exception;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use swoole_server;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\InvalidArgumentException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Http\Response;
|
||||
use ZM\Utils\ZMRobot;
|
||||
use ZM\API\ZMRobot;
|
||||
use ZM\Utils\CoMessage;
|
||||
|
||||
class Context implements ContextInterface
|
||||
{
|
||||
private $server = null;
|
||||
private $frame = null;
|
||||
private $data = null;
|
||||
private $request = null;
|
||||
private $response = null;
|
||||
public static $context = [];
|
||||
private $cid;
|
||||
|
||||
public function __construct($param, $cid) {
|
||||
if (isset($param["server"])) $this->server = $param["server"];
|
||||
if (isset($param["frame"])) $this->frame = $param["frame"];
|
||||
if (isset($param["data"])) $this->data = $param["data"];
|
||||
if (isset($param["request"])) $this->request = $param["request"];
|
||||
if (isset($param["response"])) $this->response = $param["response"];
|
||||
$this->cid = $cid;
|
||||
}
|
||||
public function __construct($cid) { $this->cid = $cid; }
|
||||
|
||||
/**
|
||||
* @return swoole_server|null
|
||||
*/
|
||||
public function getServer() {
|
||||
return $this->server;
|
||||
}
|
||||
public function getServer() { return self::$context[$this->cid]["server"] ?? null; }
|
||||
|
||||
/**
|
||||
* @return Frame|null
|
||||
*/
|
||||
public function getFrame() {
|
||||
return $this->frame;
|
||||
}
|
||||
public function getFrame() { return self::$context[$this->cid]["frame"] ?? null; }
|
||||
|
||||
public function getFd() { return self::$context[$this->cid]["fd"] ?? $this->getFrame()->fd ?? null; }
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
public function getData() { return self::$context[$this->cid]["data"] ?? null; }
|
||||
|
||||
public function setData($data) { self::$context[$this->cid]["data"] = $data; }
|
||||
|
||||
/**
|
||||
* @return Request|null
|
||||
*/
|
||||
public function getRequest() {
|
||||
return $this->request;
|
||||
}
|
||||
public function getRequest() { return self::$context[$this->cid]["request"] ?? null; }
|
||||
|
||||
/**
|
||||
* @return Response|null
|
||||
*/
|
||||
public function getResponse() {
|
||||
return $this->response;
|
||||
}
|
||||
public function getResponse() { return self::$context[$this->cid]["response"] ?? null; }
|
||||
|
||||
/** @return ConnectionObject|null */
|
||||
public function getConnection() { return ManagerGM::get($this->getFd()); }
|
||||
|
||||
/**
|
||||
* @return int|null
|
||||
*/
|
||||
public function getCid() {
|
||||
return $this->cid;
|
||||
}
|
||||
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;
|
||||
$conn = ManagerGM::get($this->getFrame()->fd);
|
||||
return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
|
||||
}
|
||||
|
||||
public function getMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
|
||||
|
||||
public function setMessage($msg) { self::$context[$this->cid]["data"]["message"] = $msg; }
|
||||
|
||||
public function getUserId() { return $this->getData()["user_id"] ?? null; }
|
||||
|
||||
public function setUserId($id) { self::$context[$this->cid]["data"]["user_id"] = $id; }
|
||||
|
||||
public function getGroupId() { return $this->getData()["group_id"] ?? null; }
|
||||
|
||||
public function setGroupId($id) { self::$context[$this->cid]["data"]["group_id"] = $id; }
|
||||
|
||||
public function getDiscussId() { return $this->getData()["discuss_id"] ?? null; }
|
||||
|
||||
public function setDiscussId($id) { self::$context[$this->cid]["data"]["discuss_id"] = $id; }
|
||||
|
||||
public function getMessageType() { return $this->getData()["message_type"] ?? null; }
|
||||
|
||||
public function setMessageType($type) { self::$context[$this->cid]["data"]["message_type"] = $type; }
|
||||
|
||||
public function getRobotId() { return $this->getData()["self_id"] ?? null; }
|
||||
|
||||
public function getCache($key) { return self::$context[$this->cid]["cache"][$key] ?? null; }
|
||||
|
||||
public function setCache($key, $value) { self::$context[$this->cid]["cache"][$key] = $value; }
|
||||
|
||||
public function getCQResponse() { return self::$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);
|
||||
$data = $this->getData();
|
||||
$conn = $this->getConnection();
|
||||
switch ($data["message_type"]) {
|
||||
case "group":
|
||||
return (new ZMRobot($conn))->setCallback($yield)->sendGroupMsg($data["group_id"], $msg);
|
||||
case "private":
|
||||
return (new ZMRobot($conn))->setCallback($yield)->sendPrivateMsg($data["user_id"], $msg);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function finalReply($msg, $yield = false) {
|
||||
self::$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 (!isset($this->getData()["user_id"], $this->getData()["message"], $this->getData()["self_id"]))
|
||||
throw new InvalidArgumentException("协程等待参数缺失");
|
||||
|
||||
Console::debug("==== 开始等待输入 ====");
|
||||
if ($prompt != "") $this->reply($prompt);
|
||||
|
||||
try {
|
||||
$r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]);
|
||||
} catch (Exception $e) {
|
||||
$r = false;
|
||||
}
|
||||
if($r === false) {
|
||||
throw new WaitTimeoutException($this, $timeout_prompt);
|
||||
}
|
||||
return $r["message"];
|
||||
/*
|
||||
$cid = Co::getuid();
|
||||
$api_id = ZMAtomic::get("wait_msg_id")->add(1);
|
||||
$hang = [
|
||||
"coroutine" => $cid,
|
||||
"user_id" => $this->getData()["user_id"],
|
||||
"message" => $this->getData()["message"],
|
||||
"self_id" => $this->getData()["self_id"],
|
||||
"message_type" => $this->getData()["message_type"],
|
||||
"result" => null
|
||||
];
|
||||
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
|
||||
$hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"];
|
||||
}
|
||||
SpinLock::lock("wait_api");
|
||||
$hw = LightCacheInside::get("wait_api", "wait_api") ?? [];
|
||||
$hw[$api_id] = $hang;
|
||||
LightCacheInside::set("wait_api", "wait_api", $hw);
|
||||
SpinLock::unlock("wait_api");
|
||||
$id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) {
|
||||
$r = LightCacheInside::get("wait_api", "wait_api")[$api_id] ?? null;
|
||||
if (is_array($r)) {
|
||||
Co::resume($r["coroutine"]);
|
||||
}
|
||||
});
|
||||
|
||||
Co::suspend();
|
||||
SpinLock::lock("wait_api");
|
||||
$hw = LightCacheInside::get("wait_api", "wait_api") ?? [];
|
||||
$sess = $hw[$api_id];
|
||||
unset($hw[$api_id]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $hw);
|
||||
$result = $sess["result"];
|
||||
if (isset($id)) swoole_timer_clear($id);
|
||||
if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
|
||||
return $result;*/
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $mode
|
||||
* @param $prompt_msg
|
||||
* @return mixed|string
|
||||
* @throws InvalidArgumentException
|
||||
* @throws WaitTimeoutException
|
||||
*/
|
||||
public function getArgs($mode, $prompt_msg) {
|
||||
$arg = ctx()->getCache("match");
|
||||
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);
|
||||
ctx()->setCache("match", $arg);
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
return $this->waitMessage($prompt_msg);
|
||||
case ZM_MATCH_FIRST:
|
||||
if (isset($arg[1])) {
|
||||
$a = $arg[1];
|
||||
array_splice($arg, 1, 1);
|
||||
ctx()->setCache("match", $arg);
|
||||
return $a;
|
||||
} else {
|
||||
return $this->waitMessage($prompt_msg);
|
||||
}
|
||||
}
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
public function cloneFromParent() {
|
||||
set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]);
|
||||
return context();
|
||||
}
|
||||
|
||||
public function copy() { return self::$context[$this->cid]; }
|
||||
|
||||
public function getOption() { return self::getCache("match"); }
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ namespace ZM\Context;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Http\Response;
|
||||
use ZM\Utils\ZMRobot;
|
||||
use ZM\API\ZMRobot;
|
||||
|
||||
interface ContextInterface
|
||||
{
|
||||
public function __construct($param, $cid);
|
||||
public function __construct($cid);
|
||||
|
||||
/** @return Server */
|
||||
public function getServer();
|
||||
@@ -23,6 +24,14 @@ interface ContextInterface
|
||||
/** @return mixed */
|
||||
public function getData();
|
||||
|
||||
public function setData($data);
|
||||
|
||||
/** @return ConnectionObject */
|
||||
public function getConnection();
|
||||
|
||||
/** @return int|null */
|
||||
public function getFd();
|
||||
|
||||
/** @return int */
|
||||
public function getCid();
|
||||
|
||||
@@ -34,4 +43,77 @@ interface ContextInterface
|
||||
|
||||
/** @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 $mode
|
||||
* @param $prompt_msg
|
||||
* @return mixed
|
||||
*/
|
||||
public function getArgs($mode, $prompt_msg);
|
||||
|
||||
public function setCache($key, $value);
|
||||
|
||||
/**
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCache($key);
|
||||
|
||||
public function cloneFromParent();
|
||||
|
||||
public function copy();
|
||||
|
||||
public function getOption();
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
<?php
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\DB;
|
||||
|
||||
|
||||
use framework\Console;
|
||||
use framework\ZMBuf;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\MySQL\Statement;
|
||||
use Exception;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use Swoole\Database\PDOStatementProxy;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
class DB
|
||||
@@ -16,9 +19,11 @@ class DB
|
||||
|
||||
/**
|
||||
* @throws DbException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function initTableList() {
|
||||
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []);
|
||||
if (!extension_loaded("mysqlnd")) throw new Exception("Can not find mysqlnd PHP extension.");
|
||||
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMConfig::get("global", "sql_config")["sql_database"] . "';", []);
|
||||
foreach ($result as $v) {
|
||||
self::$table_list[] = $v['TABLE_NAME'];
|
||||
}
|
||||
@@ -30,11 +35,11 @@ class DB
|
||||
* @return Table
|
||||
* @throws DbException
|
||||
*/
|
||||
public static function table($table_name, $enable_cache = null) {
|
||||
public static function table($table_name) {
|
||||
if (Table::getTableInstance($table_name) === null) {
|
||||
if (in_array($table_name, self::$table_list))
|
||||
return new Table($table_name, $enable_cache ?? ZMBuf::globals("sql_config")["sql_enable_cache"]);
|
||||
elseif(ZMBuf::$sql_pool !== null){
|
||||
return new Table($table_name);
|
||||
elseif(SqlPoolStorage::$sql_pool !== null){
|
||||
throw new DbException("Table " . $table_name . " not exist in database.");
|
||||
} else {
|
||||
throw new DbException("Database connection not exist or connect failed. Please check sql configuration");
|
||||
@@ -57,25 +62,16 @@ class DB
|
||||
* @throws DbException
|
||||
*/
|
||||
public static function unprepared($line) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$starttime = microtime(true);
|
||||
}
|
||||
try {
|
||||
$conn = ZMBuf::$sql_pool->get();
|
||||
$conn = SqlPoolStorage::$sql_pool->get();
|
||||
if ($conn === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
throw new DbException("无法连接SQL!" . $line);
|
||||
}
|
||||
$result = $conn->query($line) === false ? false : ($conn->errno != 0 ? false : true);
|
||||
ZMBuf::$sql_pool->put($conn);
|
||||
$result = $conn->query($line) === false ? false : true;
|
||||
SqlPoolStorage::$sql_pool->put($conn);
|
||||
return $result;
|
||||
} catch (DBException $e) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$log =
|
||||
"[" . date("Y-m-d H:i:s") .
|
||||
" " . round(microtime(true) - $starttime, 5) .
|
||||
"] " . $line . " (Error:" . $e->getMessage() . ")\n";
|
||||
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
|
||||
}
|
||||
Console::warning($e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
@@ -84,55 +80,56 @@ class DB
|
||||
/**
|
||||
* @param string $line
|
||||
* @param array $params
|
||||
* @param int $fetch_mode
|
||||
* @return mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public static function rawQuery(string $line, $params = []) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$starttime = microtime(true);
|
||||
}
|
||||
public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
Console::debug("MySQL: ".$line." | ". implode(", ", $params));
|
||||
try {
|
||||
$conn = ZMBuf::$sql_pool->get();
|
||||
$conn = SqlPoolStorage::$sql_pool->get();
|
||||
if ($conn === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
throw new DbException("无法连接SQL!" . $line);
|
||||
}
|
||||
$ps = $conn->prepare($line);
|
||||
if ($ps === false) {
|
||||
$conn->close();
|
||||
ZMBuf::$sql_pool->connect_cnt -= 1;
|
||||
SqlPoolStorage::$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);
|
||||
SqlPoolStorage::$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) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
throw new DBException("语句[$line]错误!" . $ps->errorInfo()[2]);
|
||||
//echo json_encode(debug_backtrace(), 128 | 256);
|
||||
}
|
||||
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) . "\n";
|
||||
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
|
||||
}
|
||||
return $result;
|
||||
SqlPoolStorage::$sql_pool->put($conn);
|
||||
return $ps->fetchAll($fetch_mode);
|
||||
}
|
||||
} catch (DBException $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);
|
||||
} catch (DbException $e) {
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
namespace ZM\DB;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use ZM\Exception\DbException;
|
||||
|
||||
class SelectBody
|
||||
@@ -31,21 +30,22 @@ class SelectBody
|
||||
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(*)"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fetch_mode
|
||||
* @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] ');
|
||||
return $this->table->cache[$rr]->getResult();
|
||||
}
|
||||
}
|
||||
$this->execute();
|
||||
if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) {
|
||||
$this->table->cache[$rr] = $this;
|
||||
}
|
||||
public function fetchAll($fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
$this->execute($fetch_mode);
|
||||
return $this->getResult();
|
||||
}
|
||||
|
||||
@@ -71,11 +71,12 @@ class SelectBody
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fetch_mode
|
||||
* @throws DbException
|
||||
*/
|
||||
public function execute() {
|
||||
public function execute($fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
$str = $this->queryPrepare();
|
||||
$this->result = DB::rawQuery($str[0], $str[1]);
|
||||
$this->result = DB::rawQuery($str[0], $str[1], $fetch_mode);
|
||||
}
|
||||
|
||||
public function getResult() { return $this->result; }
|
||||
|
||||
@@ -14,10 +14,7 @@ class Table
|
||||
|
||||
private static $table_instance = [];
|
||||
|
||||
private $enable_cache;
|
||||
|
||||
public function __construct($table_name, $enable_cache) {
|
||||
$this->enable_cache = $enable_cache;
|
||||
public function __construct($table_name) {
|
||||
$this->table_name = $table_name;
|
||||
self::$table_instance[$table_name] = $this;
|
||||
}
|
||||
@@ -75,5 +72,4 @@ class Table
|
||||
*/
|
||||
public function getTableName() { return $this->table_name; }
|
||||
|
||||
public function isCacheEnabled() { return $this->enable_cache; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ trait WhereBody
|
||||
protected $where_thing = [];
|
||||
|
||||
public function where($column, $operation_or_value, $value = null) {
|
||||
if (!in_array($operation_or_value, ['=', '!='])) $this->where_thing['='][$column] = $operation_or_value;
|
||||
elseif ($value !== null) $this->where_thing[$operation_or_value][$column] = $value;
|
||||
if ($value !== null) $this->where_thing[$operation_or_value][$column] = $value;
|
||||
elseif (!in_array($operation_or_value, ['=', '!=', '>', '<', '>=', '<=', 'IN', 'in'])) $this->where_thing['='][$column] = $operation_or_value;
|
||||
else $this->where_thing['='][$column] = $operation_or_value;
|
||||
return $this;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ trait WhereBody
|
||||
$param []=$vs;
|
||||
}
|
||||
}
|
||||
if ($msg == '') $msg = 1;
|
||||
return [$msg, $param];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\CQ;
|
||||
|
||||
|
||||
use Co;
|
||||
use Framework\Console;
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Annotation\CQ\CQAfter;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
use ZM\Connection\WSConnection;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Http\Response;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
|
||||
class MessageEvent
|
||||
{
|
||||
private $function_call = false;
|
||||
private $data;
|
||||
private $circle;
|
||||
/** @var WSConnection|Response */
|
||||
private $connection;
|
||||
|
||||
public function __construct($data, $conn_or_response, $circle = 0) {
|
||||
$this->data = $data;
|
||||
$this->connection = $conn_or_response;
|
||||
$this->circle = $circle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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"];
|
||||
ZMBuf::appendKey("wait_api", $k, $v);
|
||||
Co::resume($v["coroutine"]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @noinspection PhpRedundantCatchClauseInspection */
|
||||
public function onActivate() {
|
||||
try {
|
||||
$word = split_explode(" ", str_replace("\r", "", $this->data["message"]));
|
||||
if (count(explode("\n", $word[0])) >= 2) {
|
||||
$enter = explode("\n", $this->data["message"]);
|
||||
$first = split_explode(" ", array_shift($enter));
|
||||
$word = array_merge($first, $enter);
|
||||
foreach ($word as $k => $v) {
|
||||
$word[$k] = trim($word[$k]);
|
||||
}
|
||||
}
|
||||
/** @var ModBase[] $obj */
|
||||
$obj = [];
|
||||
foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) {
|
||||
/** @var CQCommand $v */
|
||||
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);
|
||||
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;
|
||||
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;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
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"]))) {
|
||||
$c = $v->class;
|
||||
if (!isset($obj[$c]))
|
||||
$obj[$c] = new $c([
|
||||
"data" => $this->data,
|
||||
"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;
|
||||
}
|
||||
}
|
||||
} catch (WaitTimeoutException $e) {
|
||||
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在调用完事件后执行的
|
||||
*/
|
||||
public function onAfter() {
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function hasReply() {
|
||||
return $this->function_call;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\CQ;
|
||||
|
||||
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQMetaEvent;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
|
||||
class MetaEvent
|
||||
{
|
||||
private $data;
|
||||
/** @var CQConnection */
|
||||
private $connection;
|
||||
private $circle;
|
||||
|
||||
public function __construct($data, $connection, $circle = 0) {
|
||||
$this->data = $data;
|
||||
$this->connection = $connection;
|
||||
$this->circle = $circle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @noinspection PhpRedundantCatchClauseInspection */
|
||||
public function onActivate() {
|
||||
try {
|
||||
/** @var ModBase[] $obj */
|
||||
$obj = [];
|
||||
foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) {
|
||||
/** @var CQMetaEvent $v */
|
||||
if (
|
||||
($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == $this->data["meta_event_type"])) &&
|
||||
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) {
|
||||
$c = $v->class;
|
||||
if (!isset($obj[$c]))
|
||||
$obj[$c] = new $c([
|
||||
"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;
|
||||
}
|
||||
}
|
||||
} catch (WaitTimeoutException $e) {
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\CQ;
|
||||
|
||||
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Annotation\CQ\CQAfter;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQNotice;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
|
||||
class NoticeEvent
|
||||
{
|
||||
private $data;
|
||||
/** @var CQConnection */
|
||||
private $connection;
|
||||
private $circle;
|
||||
|
||||
public function __construct($data, $connection, $circle = 0) {
|
||||
$this->data = $data;
|
||||
$this->connection = $connection;
|
||||
$this->circle = $circle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function onActivate() {
|
||||
try {
|
||||
/** @var ModBase[] $obj */
|
||||
$obj = [];
|
||||
foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) {
|
||||
/** @var CQNotice $v */
|
||||
if (
|
||||
($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == $this->data["notice_type"])) &&
|
||||
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
|
||||
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) &&
|
||||
($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) {
|
||||
$c = $v->class;
|
||||
if (!isset($obj[$c]))
|
||||
$obj[$c] = new $c([
|
||||
"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;
|
||||
}
|
||||
}
|
||||
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\CQ;
|
||||
|
||||
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Annotation\CQ\CQAfter;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQRequest;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
|
||||
class RequestEvent
|
||||
{
|
||||
private $data;
|
||||
/** @var CQConnection */
|
||||
private $connection;
|
||||
private $circle;
|
||||
|
||||
public function __construct($data, $connection, $circle = 0) {
|
||||
$this->data = $data;
|
||||
$this->connection = $connection;
|
||||
$this->circle = $circle;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @noinspection PhpRedundantCatchClauseInspection */
|
||||
public function onActivate() {
|
||||
try {
|
||||
/** @var ModBase[] $obj */
|
||||
$obj = [];
|
||||
foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) {
|
||||
/** @var CQRequest $v */
|
||||
if (
|
||||
($v->request_type == '' || ($v->request_type != '' && $v->request_type == $this->data["request_type"])) &&
|
||||
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
|
||||
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ($this->data["user_id"] ?? 0))) &&
|
||||
($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) {
|
||||
$c = $v->class;
|
||||
if (!isset($obj[$c]))
|
||||
$obj[$c] = new $c([
|
||||
"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;
|
||||
}
|
||||
}
|
||||
} catch (WaitTimeoutException $e) {
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
interface Event
|
||||
{
|
||||
const SWOOLE = 1;
|
||||
const CQ = 2;
|
||||
}
|
||||
121
src/ZM/Event/EventDispatcher.php
Normal file
121
src/ZM/Event/EventDispatcher.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Exception;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\CQ\CQMetaEvent;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class EventDispatcher
|
||||
{
|
||||
/** @var string */
|
||||
private $class;
|
||||
/** @var null|callable */
|
||||
private $rule = null;
|
||||
/** @var null|callable */
|
||||
private $return_func = null;
|
||||
|
||||
/**
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public static function interrupt() {
|
||||
throw new InterruptException('interrupt');
|
||||
}
|
||||
|
||||
public function __construct(string $class = '') {
|
||||
$this->class = $class;
|
||||
}
|
||||
|
||||
public function setRuleFunction(callable $rule = null) {
|
||||
$this->rule = $rule;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setReturnFunction(callable $return_func) {
|
||||
$this->return_func = $return_func;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dispatchEvents(...$params) {
|
||||
try {
|
||||
|
||||
foreach ((EventManager::$events[$this->class] ?? []) as $v) {
|
||||
$result = $this->dispatchEvent($v, $this->rule, ...$params);
|
||||
if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result);
|
||||
}
|
||||
return true;
|
||||
} catch (InterruptException $e) {
|
||||
return null;
|
||||
} catch (AnnotationException $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AnnotationBase|null $v
|
||||
* @param null $rule_func
|
||||
* @param mixed ...$params
|
||||
* @return bool
|
||||
* @throws AnnotationException
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) {
|
||||
$q_c = $v->class;
|
||||
$q_f = $v->method;
|
||||
if ($rule_func !== null && !$rule_func($v)) return false;
|
||||
if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
|
||||
$middlewares = EventManager::$middleware_map[$q_c][$q_f];
|
||||
$before_result = true;
|
||||
$r = [];
|
||||
foreach ($middlewares as $k => $middleware) {
|
||||
if (!isset(EventManager::$middlewares[$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
|
||||
$middleware_obj = EventManager::$middlewares[$middleware];
|
||||
$before = $middleware_obj["class"];
|
||||
//var_dump($middleware_obj);
|
||||
$r[$k] = new $before();
|
||||
$r[$k]->class = $q_c;
|
||||
$r[$k]->method = $q_f;
|
||||
if (isset($middleware_obj["before"])) {
|
||||
$rs = $middleware_obj["before"];
|
||||
$before_result = $r[$k]->$rs(...$params);
|
||||
if ($before_result === false) break;
|
||||
}
|
||||
}
|
||||
if ($before_result) {
|
||||
try {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
$result = $q_o->$q_f(...$params);
|
||||
} catch (Exception $e) {
|
||||
if ($e instanceof InterruptException) throw $e;
|
||||
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
|
||||
if (!isset($middleware_obj["exceptions"])) continue;
|
||||
foreach ($middleware_obj["exceptions"] as $name => $method) {
|
||||
if ($e instanceof $name) {
|
||||
$r[$i]->$method($e);
|
||||
self::interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
|
||||
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
|
||||
if (isset($middleware_obj["after"], $r[$i])) {
|
||||
$r[$i]->{$middleware_obj["after"]}(...$params);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
return $q_o->$q_f(...$params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use Co;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Framework\Console;
|
||||
use Framework\ZMBuf;
|
||||
use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent};
|
||||
use ZM\Http\Response;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class EventHandler
|
||||
{
|
||||
public static function callSwooleEvent($event_name, $param0, $param1 = null) {
|
||||
//$starttime = microtime(true);
|
||||
$event_name = strtolower($event_name);
|
||||
switch ($event_name) {
|
||||
case "workerstart":
|
||||
try {
|
||||
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();
|
||||
});
|
||||
(new WorkerStartEvent($param0, $param1))->onActivate()->onAfter();
|
||||
Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold");
|
||||
} 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();
|
||||
break;
|
||||
case "request":
|
||||
try {
|
||||
(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");
|
||||
}
|
||||
break;
|
||||
case "open":
|
||||
(new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
|
||||
break;
|
||||
case "close":
|
||||
(new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
|
||||
break;
|
||||
}
|
||||
//Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold"));
|
||||
}
|
||||
|
||||
public static function callCQEvent($event_data, $conn_or_response, int $level = 0) {
|
||||
if ($level >= 5) {
|
||||
Console::warning("Recursive call reached " . $level . " times");
|
||||
Console::stackTrace();
|
||||
return false;
|
||||
}
|
||||
$starttime = microtime(true);
|
||||
switch ($event_data["post_type"]) {
|
||||
case "message":
|
||||
$event = new CQ\MessageEvent($event_data, $conn_or_response, $level);
|
||||
if ($event->onBefore()) $event->onActivate();
|
||||
$event->onAfter();
|
||||
return $event->hasReply();
|
||||
break;
|
||||
case "notice":
|
||||
$event = new CQ\NoticeEvent($event_data, $conn_or_response, $level);
|
||||
if ($event->onBefore()) $event->onActivate();
|
||||
$event->onAfter();
|
||||
return true;
|
||||
case "request":
|
||||
$event = new CQ\RequestEvent($event_data, $conn_or_response, $level);
|
||||
if ($event->onBefore()) $event->onActivate();
|
||||
$event->onAfter();
|
||||
return true;
|
||||
case "meta_event":
|
||||
$event = new CQ\MetaEvent($event_data, $conn_or_response, $level);
|
||||
if ($event->onBefore()) $event->onActivate();
|
||||
return true;
|
||||
}
|
||||
unset($starttime);
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function callCQResponse($req) {
|
||||
//Console::info("收到来自API连接的回复:".json_encode($req, 128|256));
|
||||
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
|
||||
];
|
||||
if (($origin["func"] ?? null) !== null) {
|
||||
call_user_func($origin["func"], $response, $origin["data"]);
|
||||
} elseif (($origin["coroutine"] ?? false) !== false) {
|
||||
$p = ZMBuf::get("sent_api");
|
||||
$p[$req["echo"]]["result"] = $response;
|
||||
ZMBuf::set("sent_api", $p);
|
||||
Co::resume($origin['coroutine']);
|
||||
}
|
||||
ZMBuf::unsetByValue("sent_api", $req["echo"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/ZM/Event/EventManager.php
Normal file
62
src/ZM/Event/EventManager.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use Error;
|
||||
use Exception;
|
||||
use Swoole\Timer;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Swoole\OnTick;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\ZMAtomic;
|
||||
|
||||
class EventManager
|
||||
{
|
||||
public static $events = [];
|
||||
public static $middleware_map = [];
|
||||
public static $middlewares = [];
|
||||
public static $req_mapping = [];
|
||||
|
||||
public static function addEvent($event_name, ?AnnotationBase $event_obj) {
|
||||
self::$events[$event_name][] = $event_obj;
|
||||
}
|
||||
|
||||
public static function loadEventByParser(AnnotationParser $parser) {
|
||||
self::$events = $parser->generateAnnotationEvents();
|
||||
self::$middlewares = $parser->getMiddlewares();
|
||||
self::$middleware_map = $parser->getMiddlewareMap();
|
||||
self::$req_mapping = $parser->getReqMapping();
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册所有计时器给每个进程
|
||||
*/
|
||||
public static function registerTimerTick() {
|
||||
$dispatcher = new EventDispatcher(OnTick::class);
|
||||
foreach (self::$events[OnTick::class] ?? [] as $vss) {
|
||||
if (server()->worker_id !== $vss->worker_id) return;
|
||||
//echo server()->worker_id.PHP_EOL;
|
||||
$plain_class = $vss->class;
|
||||
Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method);
|
||||
Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) {
|
||||
set_coroutine_params([]);
|
||||
if (ZMAtomic::get("stop_signal")->get() != 0) {
|
||||
Timer::clearAll();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$dispatcher->dispatchEvent($vss, null);
|
||||
} 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!");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
541
src/ZM/Event/ServerEventHandler.php
Normal file
541
src/ZM/Event/ServerEventHandler.php
Normal file
@@ -0,0 +1,541 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
use Co;
|
||||
use Error;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use ReflectionException;
|
||||
use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Timer;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\OnWorkerStart;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Server;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Annotation\Swoole\HandleEvent;
|
||||
use ZM\Console\TermColor;
|
||||
use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\DB\DB;
|
||||
use ZM\Exception\DbException;
|
||||
use ZM\Framework;
|
||||
use ZM\Http\Response;
|
||||
use ZM\Module\QQBot;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Store\ZMBuf;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\HttpUtil;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class ServerEventHandler
|
||||
{
|
||||
/**
|
||||
* @HandleEvent("start")
|
||||
*/
|
||||
public function onStart() {
|
||||
global $terminal_id;
|
||||
$r = null;
|
||||
if ($terminal_id !== null) {
|
||||
ZMBuf::$terminal = $r = STDIN;
|
||||
Event::add($r, function () use ($r) {
|
||||
$var = trim(fgets($r));
|
||||
try {
|
||||
Terminal::executeCommand($var, $r);
|
||||
} catch (Exception $e) {
|
||||
Console::error("Uncaught exception " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
} catch (Error $e) {
|
||||
Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")");
|
||||
}
|
||||
});
|
||||
}
|
||||
Process::signal(SIGINT, function () use ($r) {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted by keyboard on Master.");
|
||||
if ((Framework::$server->inotify ?? null) !== null)
|
||||
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
|
||||
ZMUtil::stop();
|
||||
});
|
||||
if (Framework::$argv["watch"]) {
|
||||
if (extension_loaded('inotify')) {
|
||||
Console::warning("Enabled File watcher, do not use in production.");
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
Framework::$server->inotify = $fd = inotify_init();
|
||||
$this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd);
|
||||
Event::add($fd, function () use ($fd) {
|
||||
$r = inotify_read($fd);
|
||||
var_dump($r);
|
||||
ZMUtil::reload();
|
||||
});
|
||||
} else {
|
||||
Console::warning("You have not loaded inotify extension.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("shutdown")
|
||||
*/
|
||||
public function onShutdown() {
|
||||
Console::debug("正在关闭 Master 进程,pid=" . posix_getpid());
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("WorkerStop")
|
||||
* @param $server
|
||||
* @param $worker_id
|
||||
*/
|
||||
public function onWorkerStop(Server $server, $worker_id) {
|
||||
Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("WorkerStart")
|
||||
* @param Server $server
|
||||
* @param $worker_id
|
||||
*/
|
||||
public function onWorkerStart(Server $server, $worker_id) {
|
||||
//if (ZMBuf::atomic("stop_signal")->get() != 0) return;
|
||||
Process::signal(SIGINT, function () use ($worker_id, $server) {
|
||||
Console::debug("正在关闭 " . ($server->taskworker ? "Task" : "") . "Worker 进程 " . Console::setColor("#" . \server()->worker_id, "gold") . TermColor::frontColor256(59) . ", pid=" . posix_getpid());
|
||||
server()->stop($worker_id);
|
||||
});
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
if ($server->taskworker === false) {
|
||||
try {
|
||||
register_shutdown_function(function () use ($server) {
|
||||
$error = error_get_last();
|
||||
if ($error["type"] != 0) {
|
||||
Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
|
||||
}
|
||||
//DataProvider::saveBuffer();
|
||||
/** @var Server $server */
|
||||
if (server() === null) $server->shutdown();
|
||||
else server()->shutdown();
|
||||
});
|
||||
|
||||
Console::info("Worker #{$server->worker_id} 启动中");
|
||||
Framework::$server = $server;
|
||||
//ZMBuf::resetCache(); //清空变量缓存
|
||||
//ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行
|
||||
foreach ($server->connections as $v) {
|
||||
$server->close($v);
|
||||
}
|
||||
|
||||
|
||||
// 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器
|
||||
/*if ($server->worker_id === 0) {
|
||||
global $terminal_id;
|
||||
if ($terminal_id !== null)
|
||||
go(function () {
|
||||
while (true) {
|
||||
$r = server()->process->exportSocket();
|
||||
$result = $r->recv();
|
||||
try {
|
||||
if (!Terminal::executeCommand($result)) {
|
||||
//if ($result == "stop" || $result == "reload" || $result == "r") {
|
||||
//echo "Stopped.\n";
|
||||
break;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::error($e->getMessage());
|
||||
} catch (Error $e) {
|
||||
Console::error($e->getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}*/
|
||||
//TODO: 单独抽出来MySQL和Redis连接池
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] != "") {
|
||||
if (SqlPoolStorage::$sql_pool !== null) {
|
||||
SqlPoolStorage::$sql_pool->close();
|
||||
SqlPoolStorage::$sql_pool = null;
|
||||
}
|
||||
Console::info("新建SQL连接池中");
|
||||
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 = ZMConfig::get("global", "sql_config");
|
||||
SqlPoolStorage::$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($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
|
||||
);
|
||||
DB::initTableList();
|
||||
}
|
||||
|
||||
// 开箱即用的Redis
|
||||
$redis = ZMConfig::get("global", "redis_config");
|
||||
if($redis !== null && $redis["host"] != "") {
|
||||
if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n");
|
||||
else ZMRedisPool::init($redis);
|
||||
}
|
||||
|
||||
$this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
|
||||
|
||||
//echo json_encode(debug_backtrace(), 128|256);
|
||||
Console::success("Worker #" . $worker_id . " 已启动");
|
||||
EventManager::registerTimerTick(); //启动计时器
|
||||
//ZMBuf::unsetCache("wait_start");
|
||||
set_coroutine_params(["server" => $server, "worker_id" => $worker_id]);
|
||||
$dispatcher = new EventDispatcher(OnWorkerStart::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
return server()->worker_id === $v->worker_id || $v->worker_id === -1;
|
||||
});
|
||||
$dispatcher->dispatchEvents($server, $worker_id);
|
||||
Console::debug("@OnWorkerStart 执行完毕");
|
||||
} catch (Exception $e) {
|
||||
Console::error("Worker加载出错!停止服务!");
|
||||
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
ZMUtil::stop();
|
||||
return;
|
||||
} catch (Error $e) {
|
||||
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
||||
Console::error("Maybe it caused by your own code if in your own Module directory.");
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
ZMUtil::stop();
|
||||
}
|
||||
} else {
|
||||
// 这里是TaskWorker初始化的内容部分
|
||||
try {
|
||||
Framework::$server = $server;
|
||||
$this->loadAnnotations();
|
||||
Console::debug("TaskWorker #" . $server->worker_id . " 已启动");
|
||||
} catch (Exception $e) {
|
||||
Console::error("Worker加载出错!停止服务!");
|
||||
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
|
||||
ZMUtil::stop();
|
||||
return;
|
||||
} catch (Error $e) {
|
||||
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
||||
Console::error("Maybe it caused by your own code if in your own Module directory.");
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
ZMUtil::stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("message")
|
||||
* @param $server
|
||||
* @param Frame $frame
|
||||
*/
|
||||
public function onMessage($server, Frame $frame) {
|
||||
|
||||
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET);
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
$conn = ManagerGM::get($frame->fd);
|
||||
set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]);
|
||||
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
if ($v->getRule() == '') {
|
||||
return strtolower($v->type) == 'message';
|
||||
} else {
|
||||
/** @noinspection PhpUnreachableStatementInspection
|
||||
* @noinspection RedundantSuppression
|
||||
*/
|
||||
if (strtolower($v->type) == 'message' && eval("return " . $v->getRule() . ";")) return true;
|
||||
else return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
$starttime = microtime(true);
|
||||
$dispatcher->dispatchEvents($conn);
|
||||
Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!");
|
||||
} catch (Exception $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg);
|
||||
Console::trace();
|
||||
} catch (Error $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught Error " . get_class($e) . " when calling \"message\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("request")
|
||||
* @param $request
|
||||
* @param $response
|
||||
*/
|
||||
public function onRequest($request, $response) {
|
||||
$response = new Response($response);
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
|
||||
set_coroutine_params(["request" => $request, "response" => $response]);
|
||||
|
||||
$dis = new EventDispatcher(OnSwooleEvent::class);
|
||||
$dis->setRuleFunction(function ($v) {
|
||||
if ($v->getRule() == '') {
|
||||
return strtolower($v->type) == 'request';
|
||||
} else {
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
if (strtolower($v->type) == 'request' && eval("return " . $v->getRule() . ";")) return true;
|
||||
else return false;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
$no_interrupt = $dis->dispatchEvents($request, $response);
|
||||
if ($no_interrupt !== null) {
|
||||
$result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params);
|
||||
if ($result === true) {
|
||||
ctx()->setCache("params", $params);
|
||||
$dispatcher = new EventDispatcher(RequestMapping::class);
|
||||
$div = new RequestMapping();
|
||||
$div->route = $node["route"];
|
||||
$div->params = $params;
|
||||
$div->method = $node["method"];
|
||||
$div->request_method = $node["request_method"];
|
||||
$div->class = $node["class"];
|
||||
//Console::success("正在执行路由:".$node["method"]);
|
||||
$r = $dispatcher->dispatchEvent($div, null, $params, $request, $response);
|
||||
if (is_string($r) && !$response->isEnd()) $response->end($r);
|
||||
}
|
||||
}
|
||||
if (!$response->isEnd()) {
|
||||
//Console::warning('返回了404');
|
||||
HttpUtil::responseCodePage($response, 404);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$response->status(500);
|
||||
Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] .
|
||||
" [" . $response->getStatusCode() . "] " . $request->server["request_uri"]
|
||||
);
|
||||
if (!$response->isEnd()) {
|
||||
if (ZMConfig::get("global", "debug_mode"))
|
||||
$response->end("Internal server exception: " . $e->getMessage());
|
||||
else
|
||||
$response->end("Internal server error.");
|
||||
}
|
||||
Console::error("Internal server exception (500), caused by " . get_class($e).": ".$e->getMessage());
|
||||
Console::log($e->getTraceAsString(), "gray");
|
||||
} catch (Error $e) {
|
||||
$response->status(500);
|
||||
Console::info($request->server["remote_addr"] . ":" . $request->server["remote_port"] .
|
||||
" [" . $response->getStatusCode() . "] " . $request->server["request_uri"]
|
||||
);
|
||||
if (!$response->isEnd()) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
if (ZMConfig::get("global", "debug_mode"))
|
||||
$response->end("Internal server error: " . $error_msg);
|
||||
else
|
||||
$response->end("Internal server error.");
|
||||
}
|
||||
Console::error("Internal server error (500), caused by " . get_class($e));
|
||||
Console::log($e->getTraceAsString(), "gray");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("open")
|
||||
* @param $server
|
||||
* @param Request $request
|
||||
*/
|
||||
public function onOpen($server, Request $request) {
|
||||
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
$type = strtolower($request->get["type"] ?? $request->header["x-client-role"] ?? "");
|
||||
$type_conn = ManagerGM::getTypeClassName($type);
|
||||
ManagerGM::pushConnect($request->fd, $type_conn);
|
||||
$conn = ManagerGM::get($request->fd);
|
||||
set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]);
|
||||
$conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? "");
|
||||
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
if ($v->getRule() == '') {
|
||||
return strtolower($v->type) == 'open';
|
||||
} else {
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
if (strtolower($v->type) == 'open' && eval("return " . $v->getRule() . ";")) return true;
|
||||
else return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
$dispatcher->dispatchEvents($conn);
|
||||
} catch (Exception $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
} catch (Error $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught Error " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
//EventHandler::callSwooleEvent("open", $server, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("close")
|
||||
* @param $server
|
||||
* @param $fd
|
||||
*/
|
||||
public function onClose($server, $fd) {
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
$conn = ManagerGM::get($fd);
|
||||
if ($conn === null) return;
|
||||
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
|
||||
set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]);
|
||||
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
|
||||
$dispatcher->setRuleFunction(function ($v) {
|
||||
if ($v->getRule() == '') {
|
||||
return strtolower($v->type) == 'close';
|
||||
} else {
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
if (strtolower($v->type) == 'close' && eval("return " . $v->getRule() . ";")) return true;
|
||||
else return false;
|
||||
}
|
||||
});
|
||||
try {
|
||||
$dispatcher->dispatchEvents($conn);
|
||||
} catch (Exception $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught exception " . get_class($e) . " when calling \"close\": " . $error_msg);
|
||||
Console::trace();
|
||||
} catch (Error $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught Error " . get_class($e) . " when calling \"close\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
ManagerGM::popConnect($fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("pipeMessage")
|
||||
* @param $server
|
||||
* @param $src_worker_id
|
||||
* @param $data
|
||||
*/
|
||||
public function onPipeMessage(Server $server, $src_worker_id, $data) {
|
||||
//var_dump($data, $server->worker_id);
|
||||
//unset(Context::$context[Co::getCid()]);
|
||||
$data = json_decode($data, true);
|
||||
switch ($data["action"] ?? '') {
|
||||
case "resume_ws_message":
|
||||
$obj = $data["data"];
|
||||
Co::resume($obj["coroutine"]);
|
||||
break;
|
||||
case "stop":
|
||||
Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器');
|
||||
Timer::clearAll();
|
||||
break;
|
||||
case "terminate":
|
||||
$server->stop();
|
||||
break;
|
||||
case 'echo':
|
||||
Console::success('接收到来自 #' . $src_worker_id . ' 的消息');
|
||||
break;
|
||||
case 'send':
|
||||
$server->sendMessage(json_encode(["action" => "echo"]), $data["target"]);
|
||||
break;
|
||||
default:
|
||||
echo $data . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @HandleEvent("task")
|
||||
*/
|
||||
public function onTask() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ReflectionException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function loadAnnotations() {
|
||||
//加载phar包
|
||||
/*Console::debug("加载外部phar包中");
|
||||
$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") {
|
||||
Console::debug("加载Phar: " . $dir . $v . " 中");
|
||||
require_once($dir . $v);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
//加载各个模块的注解类,以及反射
|
||||
Console::debug("检索Module中");
|
||||
$parser = new AnnotationParser();
|
||||
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module");
|
||||
$parser->registerMods();
|
||||
EventManager::loadEventByParser($parser); //加载事件
|
||||
|
||||
//加载自定义的全局函数
|
||||
Console::debug("加载自定义上下文中...");
|
||||
$context_class = ZMConfig::get("global", "context_class");
|
||||
if (!is_a($context_class, ContextInterface::class, true)) {
|
||||
throw new Exception("Context class must implemented from ContextInterface!");
|
||||
}
|
||||
|
||||
//加载插件
|
||||
$plugins = ZMConfig::get("global", "modules") ?? [];
|
||||
if (!isset($plugins["onebot"])) $plugins["onebot"] = true;
|
||||
|
||||
if ($plugins["onebot"]) {
|
||||
$obj = new OnSwooleEvent();
|
||||
$obj->class = QQBot::class;
|
||||
$obj->method = 'handle';
|
||||
$obj->type = 'message';
|
||||
$obj->level = 99999;
|
||||
$obj->rule = 'connectIsQQ()';
|
||||
EventManager::addEvent(OnSwooleEvent::class, $obj);
|
||||
}
|
||||
|
||||
//TODO: 编写加载外部插件的方式
|
||||
$this->loadExternalModules($plugins);
|
||||
}
|
||||
|
||||
private function addWatcher($maindir, $fd) {
|
||||
$dir = scandir($maindir);
|
||||
unset($dir[0], $dir[1]);
|
||||
foreach ($dir as $subdir) {
|
||||
if (is_dir($maindir . "/" . $subdir)) {
|
||||
Console::debug("添加监听目录:" . $maindir . "/" . $subdir);
|
||||
inotify_add_watch($fd, $maindir . "/" . $subdir, IN_ATTRIB | IN_ISDIR);
|
||||
$this->addWatcher($maindir . "/" . $subdir, $fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loadExternalModules($plugins) {
|
||||
foreach ($plugins as $k => $v) {
|
||||
if ($k == "onebot") continue;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Framework\Console;
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Annotation\Swoole\SwooleEventAfter;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use Exception;
|
||||
use ZM\Event\EventHandler;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class MessageEvent implements SwooleEvent
|
||||
{
|
||||
/**
|
||||
* @var Server
|
||||
*/
|
||||
public $server;
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
public $frame;
|
||||
|
||||
public function __construct(Server $server, Frame $frame) {
|
||||
$this->server = $server;
|
||||
$this->frame = $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function onActivate() {
|
||||
ZMUtil::checkWait();
|
||||
$conn = ConnectionManager::get($this->frame->fd);
|
||||
set_coroutine_params(["server" => $this->server, "frame" => $this->frame, "connection" => $conn]);
|
||||
try {
|
||||
if ($conn->getType() == "qq") {
|
||||
$data = json_decode($this->frame->data, true);
|
||||
if (isset($data["post_type"])) {
|
||||
set_coroutine_params(["data" => $data, "connection" => $conn]);
|
||||
EventHandler::callCQEvent($data, ConnectionManager::get($this->frame->fd), 0);
|
||||
} else
|
||||
EventHandler::callCQResponse($data);
|
||||
}
|
||||
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
|
||||
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
Console::warning("Websocket message event exception: " . $e->getMessage());
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function onAfter() {
|
||||
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
|
||||
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) {
|
||||
$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], []);
|
||||
if ($class->block_continue) break;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function parseSwooleRule($v) {
|
||||
switch (explode(":", $v->rule)[0]) {
|
||||
case "connectType": //websocket连接类型
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->frame->fd));
|
||||
break;
|
||||
case "dataEqual": //handle websocket message事件时才能用
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Exception;
|
||||
use Framework\Console;
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\Http\Request;
|
||||
use ZM\Annotation\Http\MiddlewareClass;
|
||||
use ZM\Annotation\Swoole\SwooleEventAfter;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Http\MiddlewareInterface;
|
||||
use ZM\Http\Response;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class RequestEvent implements SwooleEvent
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
private $response;
|
||||
|
||||
public function __construct(Request $request, Response $response) {
|
||||
$this->request = $request;
|
||||
$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"];
|
||||
$uri = explode("/", $uri);
|
||||
$uri = array_diff($uri, ["..", "", "."]);
|
||||
$node = ZMBuf::$req_mapping;
|
||||
$params = [];
|
||||
while (true) {
|
||||
$r = array_shift($uri);
|
||||
if ($r === null) break;
|
||||
if (($cnt = count($node["son"] ?? [])) == 1) {
|
||||
if (isset($node["param_route"])) {
|
||||
foreach ($node["son"] as $k => $v) {
|
||||
if ($v["id"] == $node["param_route"]) {
|
||||
$node = $v;
|
||||
$params[mb_substr($v["name"], 1, -1)] = $r;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
} elseif ($node["son"][0]["name"] == $r) {
|
||||
$node = $node["son"][0];
|
||||
continue;
|
||||
}
|
||||
} elseif ($cnt >= 1) {
|
||||
if (isset($node["param_route"])) {
|
||||
foreach ($node["son"] as $k => $v) {
|
||||
if ($v["id"] == $node["param_route"]) {
|
||||
$node = $v;
|
||||
$params[mb_substr($v["name"], 1, -1)] = $r;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($node["son"] as $k => $v) {
|
||||
if ($v["name"] == $r) {
|
||||
$node = $v;
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(WORKING_DIR) . '/';
|
||||
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;
|
||||
}
|
||||
|
||||
set_coroutine_params(["request" => $this->request, "response" => $this->response, "params" => $params]);
|
||||
|
||||
if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面
|
||||
$c_name = $node["class"];
|
||||
if (isset(ZMBuf::$events[MiddlewareInterface::class][$c_name][$node["method"]])) {
|
||||
$middleware = ZMBuf::$events[MiddlewareInterface::class][$c_name][$node["method"]];
|
||||
if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
|
||||
$middleware = ZMBuf::$events[MiddlewareClass::class][$middleware];
|
||||
$before = $middleware["class"];
|
||||
$r = new $before();
|
||||
$before_result = true;
|
||||
if (isset($middleware["before"])) {
|
||||
$before_result = call_user_func([$r, $middleware["before"]], $this->request, $this->response, $params);
|
||||
if ($before_result !== false) $before_result = true;
|
||||
}
|
||||
if ($before_result) {
|
||||
try {
|
||||
/** @var ModBase $class */
|
||||
$class = new $c_name(["request" => $this->request, "response" => $this->response, "params" => $params], ModHandleType::SWOOLE_REQUEST);
|
||||
$result = call_user_func_array([$class, $node["method"]], [$params]);
|
||||
if (is_string($result) && !$this->response->isEnd()) $this->response->end($result);
|
||||
if (!$this->response->isEnd()) goto eventCall;
|
||||
} catch (Exception $e) {
|
||||
if (!isset($middleware["exceptions"])) throw $e;
|
||||
foreach ($middleware["exceptions"] as $name => $method) {
|
||||
if ($e instanceof $name) {
|
||||
call_user_func([$r, $method], $e, $this->request, $this->response, $params);
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
if (isset($middleware["after"])) {
|
||||
call_user_func([$r, $middleware["after"]], $this->request, $this->response, $params);
|
||||
return $this;
|
||||
}
|
||||
} else {
|
||||
/** @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 (!$this->response->isEnd()) goto eventCall;
|
||||
}
|
||||
}
|
||||
eventCall:
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->response->isEnd()) {
|
||||
$this->response->status(404);
|
||||
$this->response->end(ZMUtil::getHttpCodePage(404));
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function onAfter() {
|
||||
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
|
||||
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
|
||||
$c = $v->class;
|
||||
$class = new $c(["request" => $this->request, "response" => $this->response]);
|
||||
call_user_func_array([$class, $v->method], []);
|
||||
if ($class->block_continue) break;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function responseStatus(int $int) {
|
||||
$this->response->status($int);
|
||||
$this->response->end();
|
||||
}
|
||||
|
||||
private function parseSwooleRule($v) {
|
||||
switch (explode(":", $v->rule)[0]) {
|
||||
case "containsGet":
|
||||
case "containsPost":
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request);
|
||||
break;
|
||||
case "containsJson":
|
||||
$content = $this->request->rawContent();
|
||||
$content = json_decode($content, true);
|
||||
if ($content === null) return false;
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, $content);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use ZM\Event\Event;
|
||||
|
||||
interface SwooleEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @return SwooleEvent
|
||||
*/
|
||||
public function onActivate();
|
||||
|
||||
/**
|
||||
* @return SwooleEvent
|
||||
*/
|
||||
public function onAfter();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\Server;
|
||||
use ZM\Annotation\Swoole\SwooleEventAfter;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class WSCloseEvent implements SwooleEvent
|
||||
{
|
||||
public $server;
|
||||
|
||||
public $fd;
|
||||
|
||||
public function __construct(Server $server, int $fd) {
|
||||
$this->server = $server;
|
||||
$this->fd = $fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function parseSwooleRule($v) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Annotation\Swoole\SwooleEventAfter;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\Connection\UnknownConnection;
|
||||
use ZM\Connection\WSConnection;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class WSOpenEvent implements SwooleEvent
|
||||
{
|
||||
/**
|
||||
* @var Server
|
||||
*/
|
||||
private $server;
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
private $request;
|
||||
/**
|
||||
* @var WSConnection
|
||||
*/
|
||||
private $conn;
|
||||
|
||||
public function __construct(Server $server, Request $request) {
|
||||
$this->server = $server;
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function onActivate() {
|
||||
ZMUtil::checkWait();
|
||||
$type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? "");
|
||||
$type_conn = ConnectionManager::getTypeClassName($type);
|
||||
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 ($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 $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;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function onAfter() {
|
||||
if (!$this->conn->exists()) return $this;
|
||||
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
|
||||
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
|
||||
/** @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;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function parseSwooleRule($v) {
|
||||
switch (explode(":", $v->rule)[0]) {
|
||||
case "connectType": //websocket连接类型
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->conn);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Co;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Exception;
|
||||
use ReflectionException;
|
||||
use Swoole\Coroutine;
|
||||
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\Exception\DbException;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\SQLPool;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class WorkerStartEvent implements SwooleEvent
|
||||
{
|
||||
private $worker_id;
|
||||
/**
|
||||
* @var Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
public function __construct(Server $server, $worker_id) {
|
||||
$this->server = $server;
|
||||
$this->worker_id = $worker_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WorkerStartEvent
|
||||
* @throws AnnotationException
|
||||
* @throws ReflectionException
|
||||
* @throws DbException
|
||||
*/
|
||||
public function onActivate(): WorkerStartEvent {
|
||||
Console::info("Worker启动中");
|
||||
Process::signal(SIGINT, function (){
|
||||
Console::warning("Server interrupted by keyboard.");
|
||||
ZMUtil::stop(true);
|
||||
});
|
||||
ZMBuf::resetCache(); //清空变量缓存
|
||||
ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行
|
||||
$this->resetConnections();//释放所有与framework的连接
|
||||
|
||||
//设置炸毛buf中储存的对象
|
||||
ZMBuf::$globals = new GlobalConfig();
|
||||
ZMBuf::$config = [];
|
||||
$file = scandir(WORKING_DIR . '/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("已读取配置文件:" . $v);
|
||||
} elseif ($prefix == "php") {
|
||||
ZMBuf::$config[$name[0]] = include_once WORKING_DIR . '/config/' . $v;
|
||||
if (is_array(ZMBuf::$config[$name[0]]))
|
||||
Console::info("已读取配置文件:" . $v);
|
||||
}
|
||||
}
|
||||
if (ZMBuf::globals("sql_config")["sql_host"] != "") {
|
||||
Console::info("新建SQL连接池中");
|
||||
ZMBuf::$sql_pool = new SQLPool();
|
||||
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"));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
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) {
|
||||
$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], []);
|
||||
}
|
||||
set_coroutine_params(["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;
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function resetConnections() {
|
||||
foreach ($this->server->connections as $v) {
|
||||
$this->server->close($v);
|
||||
}
|
||||
if (ZMBuf::$sql_pool instanceof SqlPool) {
|
||||
ZMBuf::$sql_pool->destruct();
|
||||
ZMBuf::$sql_pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws AnnotationException
|
||||
* @throws ReflectionException
|
||||
* @throws Exception
|
||||
*/
|
||||
private function loadAllClass() {
|
||||
//加载phar包
|
||||
Console::info("加载外部phar包中");
|
||||
$dir = WORKING_DIR . "/resources/package/";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
//加载composer类
|
||||
Console::info("加载composer资源中");
|
||||
require_once WORKING_DIR . "/vendor/autoload.php";
|
||||
|
||||
//加载各个模块的注解类,以及反射
|
||||
Console::info("检索Module中");
|
||||
AnnotationParser::registerMods();
|
||||
|
||||
//加载Custom目录下的自定义的内部类
|
||||
ConnectionManager::registerCustomClass();
|
||||
|
||||
//加载自定义的全局函数
|
||||
if(file_exists(WORKING_DIR."/src/Custom/global_function.php"))
|
||||
require_once WORKING_DIR."/src/Custom/global_function.php";
|
||||
$this->afterCheck();
|
||||
}
|
||||
|
||||
private function setAutosaveTimer($globals) {
|
||||
DataProvider::$buffer_list = [];
|
||||
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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/ZM/Exception/InterruptException.php
Normal file
12
src/ZM/Exception/InterruptException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class InterruptException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
12
src/ZM/Exception/NotInitializedException.php
Normal file
12
src/ZM/Exception/NotInitializedException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotInitializedException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
@@ -6,15 +6,13 @@ namespace ZM\Exception;
|
||||
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use ZM\ModBase;
|
||||
|
||||
class WaitTimeoutException extends Exception
|
||||
{
|
||||
/** @var ModBase */
|
||||
public $module;
|
||||
|
||||
public function __construct($module, $message = "", $code = 0, Throwable $previous = null) {
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->module = $module;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
252
src/ZM/Framework.php
Normal file
252
src/ZM/Framework.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Exception;
|
||||
use ZM\Annotation\Swoole\SwooleSetup;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Event\ServerEventHandler;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\DataProvider;
|
||||
use Framework\RemoteShell;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Annotation\Swoole\HandleEvent;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class Framework
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $argv;
|
||||
/**
|
||||
* @var Server
|
||||
*/
|
||||
public static $server;
|
||||
/**
|
||||
* @var array|bool|mixed|null
|
||||
*/
|
||||
private $server_set;
|
||||
|
||||
public function __construct($args = []) {
|
||||
$tty_width = $this->getTtyWidth();
|
||||
|
||||
self::$argv = $args;
|
||||
|
||||
//定义常量
|
||||
include_once "global_defines.php";
|
||||
|
||||
ZMAtomic::init();
|
||||
try {
|
||||
ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [
|
||||
[
|
||||
"key" => "connect_id",
|
||||
"type" => "string",
|
||||
"size" => 30
|
||||
],
|
||||
[
|
||||
"key" => "type",
|
||||
"type" => "int"
|
||||
]
|
||||
]);
|
||||
} catch (ConnectionManager\TableException $e) {
|
||||
die($e->getMessage());
|
||||
}
|
||||
try {
|
||||
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
|
||||
$this->server_set = ZMConfig::get("global", "swoole");
|
||||
Console::init(
|
||||
ZMConfig::get("global", "info_level"),
|
||||
self::$server,
|
||||
$args["log-theme"] ?? "default",
|
||||
($o = ZMConfig::get("console_color")) === false ? [] : $o
|
||||
);
|
||||
|
||||
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
$this->parseCliArgs(self::$argv);
|
||||
|
||||
$out = [
|
||||
"host" => ZMConfig::get("global", "host"),
|
||||
"port" => ZMConfig::get("global", "port"),
|
||||
"log_level" => Console::getLevel(),
|
||||
"version" => ZM_VERSION,
|
||||
"config" => $args["env"] === null ? 'global.php' : $args["env"]
|
||||
];
|
||||
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
|
||||
$out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"];
|
||||
}
|
||||
if (($num = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num()) != 1) {
|
||||
$out["worker_num"] = $num;
|
||||
}
|
||||
$out["working_dir"] = DataProvider::getWorkingDir();
|
||||
Console::printProps($out, $tty_width);
|
||||
|
||||
self::$server->set($this->server_set);
|
||||
if (file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
|
||||
$motd = file_get_contents(DataProvider::getWorkingDir() . "/config/motd.txt");
|
||||
} else {
|
||||
$motd = file_get_contents(__DIR__ . "/../../config/motd.txt");
|
||||
}
|
||||
$motd = explode("\n", $motd);
|
||||
foreach ($motd as $k => $v) {
|
||||
$motd[$k] = substr($v, 0, $tty_width);
|
||||
}
|
||||
$motd = implode("\n", $motd);
|
||||
echo $motd;
|
||||
global $asd;
|
||||
$asd = get_included_files();
|
||||
// 注册 Swoole Server 的事件
|
||||
$this->registerServerEvents();
|
||||
$r = ZMConfig::get("global", "light_cache") ?? [
|
||||
"size" => 1024,
|
||||
"max_strlen" => 8192,
|
||||
"hash_conflict_proportion" => 0.6,
|
||||
"persistence_path" => realpath(DataProvider::getDataFolder() . "_cache.json"),
|
||||
"auto_save_interval" => 900
|
||||
];
|
||||
LightCache::init($r);
|
||||
LightCacheInside::init();
|
||||
SpinLock::init($r["size"]);
|
||||
self::$server->start();
|
||||
} catch (Exception $e) {
|
||||
Console::error("Framework初始化出现错误,请检查!");
|
||||
Console::error($e->getMessage());
|
||||
die;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从全局配置文件里读取注入系统事件的类
|
||||
* @throws ReflectionException
|
||||
* @throws ReflectionException
|
||||
*/
|
||||
private function registerServerEvents() {
|
||||
$all_event_class = ZMConfig::get("global", "server_event_handler_class") ?? [];
|
||||
if (!in_array(ServerEventHandler::class, $all_event_class)) {
|
||||
$all_event_class[] = ServerEventHandler::class;
|
||||
}
|
||||
$event_list = [];
|
||||
foreach ($all_event_class as $v) {
|
||||
$reader = new AnnotationReader();
|
||||
$reflection_class = new ReflectionClass($v);
|
||||
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
foreach ($methods as $vs) {
|
||||
$method_annotations = $reader->getMethodAnnotations($vs);
|
||||
if ($method_annotations != []) {
|
||||
$annotation = $method_annotations[0];
|
||||
if ($annotation instanceof HandleEvent) {
|
||||
$annotation->class = $v;
|
||||
$annotation->method = $vs->getName();
|
||||
$event_list[strtolower($annotation->event)] = $annotation;
|
||||
} elseif ($annotation instanceof SwooleSetup) {
|
||||
$annotation->class = $v;
|
||||
$annotation->method = $vs->getName();
|
||||
$c = new $v();
|
||||
$m = $annotation->method;
|
||||
$c->$m();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($event_list as $k => $v) {
|
||||
self::$server->on($k, function (...$param) use ($v) {
|
||||
$c = ZMUtil::getModInstance($v->class);
|
||||
$m = $v->method;
|
||||
$c->$m(...$param);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析命令行的 $argv 参数们
|
||||
* @param $args
|
||||
* @throws Exception
|
||||
*/
|
||||
private function parseCliArgs($args) {
|
||||
$coroutine_mode = true;
|
||||
global $terminal_id;
|
||||
$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)));
|
||||
});
|
||||
foreach ($args as $x => $y) {
|
||||
switch ($x) {
|
||||
case 'disable-coroutine':
|
||||
if ($y) {
|
||||
$coroutine_mode = false;
|
||||
}
|
||||
break;
|
||||
case 'debug-mode':
|
||||
if ($y || ZMConfig::get("global", "debug_mode")) {
|
||||
$coroutine_mode = false;
|
||||
$terminal_id = null;
|
||||
Console::warning("You are in debug mode, do not use in production!");
|
||||
}
|
||||
break;
|
||||
case 'daemon':
|
||||
if ($y) {
|
||||
$this->server_set["daemonize"] = 1;
|
||||
Console::log("已启用守护进程,输出重定向到 " . $this->server_set["log_file"]);
|
||||
$terminal_id = null;
|
||||
}
|
||||
break;
|
||||
case 'disable-console-input':
|
||||
if ($y) $terminal_id = null;
|
||||
break;
|
||||
case 'remote-shell':
|
||||
if ($y) {
|
||||
$host = "127.0.0.1";
|
||||
$port = 9599;
|
||||
RemoteShell::listen(self::$server, $host, $port);
|
||||
Console::log(Console::setColor("正在监听" . $host . ":" . strval($port) . "的调试接口,请注意安全", "yellow"));
|
||||
}
|
||||
break;
|
||||
case 'log-error':
|
||||
if ($y) Console::setLevel(0);
|
||||
break;
|
||||
case 'log-warning':
|
||||
if ($y) Console::setLevel(1);
|
||||
break;
|
||||
case 'log-info':
|
||||
if ($y) Console::setLevel(2);
|
||||
break;
|
||||
case 'log-verbose':
|
||||
if ($y) Console::setLevel(3);
|
||||
break;
|
||||
case 'log-debug':
|
||||
if ($y) Console::setLevel(4);
|
||||
break;
|
||||
case 'log-theme':
|
||||
if ($y !== null) {
|
||||
Console::$theme = $y;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
|
||||
}
|
||||
|
||||
public static function getTtyWidth() {
|
||||
return explode(" ", trim(exec("stty size")))[1];
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,5 @@ namespace ZM\Http;
|
||||
|
||||
interface MiddlewareInterface
|
||||
{
|
||||
public function getName();
|
||||
|
||||
}
|
||||
|
||||
35
src/ZM/Http/StaticFileHandler.php
Normal file
35
src/ZM/Http/StaticFileHandler.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Http;
|
||||
|
||||
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\HttpUtil;
|
||||
|
||||
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", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
|
||||
$response->end(file_get_contents($full_path));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$response->status(404);
|
||||
$response->end(HttpUtil::getHttpCodePage(404));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,165 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM;
|
||||
|
||||
|
||||
use Co;
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\Http\Request;
|
||||
use ZM\API\CQAPI;
|
||||
use ZM\Connection\WSConnection;
|
||||
use ZM\Exception\InvalidArgumentException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Http\Response;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use Swoole\WebSocket\Server;
|
||||
|
||||
abstract class ModBase
|
||||
{
|
||||
/** @var Server */
|
||||
protected $server;
|
||||
/** @var Frame */
|
||||
protected $frame;
|
||||
/** @var array */
|
||||
protected $data;
|
||||
/** @var Request */
|
||||
protected $request;
|
||||
/** @var Response */
|
||||
protected $response;
|
||||
/** @var int */
|
||||
protected $fd;
|
||||
/** @var int */
|
||||
protected $worker_id;
|
||||
/** @var WSConnection */
|
||||
protected $connection;
|
||||
|
||||
protected $handle_type = ModHandleType::CQ_MESSAGE;
|
||||
|
||||
public $block_continue = false;
|
||||
|
||||
public function __construct($param0 = [], $handle_type = 0) {
|
||||
if (isset($param0["server"])) $this->server = $param0["server"];
|
||||
if (isset($param0["frame"])) $this->frame = $param0["frame"];
|
||||
if (isset($param0["data"])) $this->data = $param0["data"];
|
||||
if (isset($param0["request"])) $this->request = $param0["request"];
|
||||
if (isset($param0["response"])) $this->response = $param0["response"];
|
||||
if (isset($param0["fd"])) $this->fd = $param0["fd"];
|
||||
if (isset($param0["worker_id"])) $this->worker_id = $param0["worker_id"];
|
||||
if (isset($param0["connection"])) $this->connection = $param0["connection"];
|
||||
$this->handle_type = $handle_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* only can used by cq->message event function
|
||||
* @param $msg
|
||||
* @param bool $yield
|
||||
* @return mixed
|
||||
*/
|
||||
public function reply($msg, $yield = false) {
|
||||
switch ($this->data["message_type"]) {
|
||||
case "group":
|
||||
case "private":
|
||||
case "discuss":
|
||||
return CQAPI::quick_reply($this->connection, $this->data, $msg, $yield);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function finalReply($msg, $yield = false) {
|
||||
$this->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->data["user_id"], $this->data["message"], $this->data["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->data["user_id"],
|
||||
"message" => $this->data["message"],
|
||||
"self_id" => $this->data["self_id"],
|
||||
"message_type" => $this->data["message_type"],
|
||||
"result" => null
|
||||
];
|
||||
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
|
||||
$hang[$hang["message_type"] . "_id"] = $this->data[$this->data["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 getMessage() { return $this->data["message"] ?? null; }
|
||||
|
||||
public function getUserId() { return $this->data["user_id"] ?? null; }
|
||||
|
||||
public function getGroupId() { return $this->data["group_id"] ?? null; }
|
||||
|
||||
public function getMessageType() { return $this->data["message_type"] ?? null; }
|
||||
|
||||
public function getRobotId() { return $this->data["self_id"]; }
|
||||
|
||||
public function getConnection() { return $this->connection; }
|
||||
|
||||
public function setBlock($result = true) { $this->block_continue = $result; }
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM;
|
||||
|
||||
|
||||
class ModHandleType
|
||||
{
|
||||
const CQ_MESSAGE = 0;
|
||||
const CQ_REQUEST = 1;
|
||||
const CQ_NOTICE = 2;
|
||||
const CQ_META_EVENT = 3;
|
||||
|
||||
const SWOOLE_OPEN = 4;
|
||||
const SWOOLE_CLOSE = 5;
|
||||
const SWOOLE_MESSAGE = 6;
|
||||
const SWOOLE_REQUEST = 7;
|
||||
const SWOOLE_WORKER_START = 8;
|
||||
const SWOOLE_WORKER_STOP = 9;
|
||||
}
|
||||
202
src/ZM/Module/QQBot.php
Normal file
202
src/ZM/Module/QQBot.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Module;
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use ZM\Annotation\CQ\CQAPIResponse;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
use ZM\Annotation\CQ\CQMetaEvent;
|
||||
use ZM\Annotation\CQ\CQNotice;
|
||||
use ZM\Annotation\CQ\CQRequest;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Utils\CoMessage;
|
||||
|
||||
/**
|
||||
* Class QQBot
|
||||
* @package ZM\Module
|
||||
* @ExternalModule("onebot")
|
||||
*/
|
||||
class QQBot
|
||||
{
|
||||
/**
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public function handle() {
|
||||
try {
|
||||
$data = json_decode(context()->getFrame()->data, true);
|
||||
if (isset($data["post_type"])) {
|
||||
//echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
|
||||
set_coroutine_params(["data" => $data]);
|
||||
ctx()->setCache("level", 0);
|
||||
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
|
||||
$this->dispatchBeforeEvents($data); // >= 200 的level before在这里执行
|
||||
if (CoMessage::resumeByWS()) {
|
||||
EventDispatcher::interrupt();
|
||||
}
|
||||
//Console::warning("最上数据包:".json_encode($data));
|
||||
$this->dispatchEvents($data);
|
||||
} else {
|
||||
$this->dispatchAPIResponse($data);
|
||||
}
|
||||
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function dispatchBeforeEvents($data) {
|
||||
$before = new EventDispatcher(CQBefore::class);
|
||||
$before->setRuleFunction(function ($v) use ($data) {
|
||||
if ($v->level < 200) EventDispatcher::interrupt();
|
||||
elseif ($v->cq_event != $data["post_type"]) return false;
|
||||
return true;
|
||||
});
|
||||
$before->setReturnFunction(function ($result) {
|
||||
if (!$result) EventDispatcher::interrupt();
|
||||
});
|
||||
$before->dispatchEvents($data);
|
||||
}
|
||||
|
||||
private function dispatchEvents($data) {
|
||||
//Console::warning("最xia数据包:".json_encode($data));
|
||||
switch ($data["post_type"]) {
|
||||
case "message":
|
||||
$word = explodeMsg(str_replace("\r", "", context()->getMessage()));
|
||||
if (count(explode("\n", $word[0])) >= 2) {
|
||||
$enter = explode("\n", context()->getMessage());
|
||||
$first = split_explode(" ", array_shift($enter));
|
||||
$word = array_merge($first, $enter);
|
||||
foreach ($word as $k => $v) {
|
||||
$word[$k] = trim($word[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
//分发CQCommand事件
|
||||
$dispatcher = new EventDispatcher(CQCommand::class);
|
||||
$dispatcher->setRuleFunction(function (CQCommand $v) use ($word) {
|
||||
if ($v->match == "" && $v->pattern == "" && $v->regex == "") return false;
|
||||
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) &&
|
||||
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) &&
|
||||
($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType()))
|
||||
) {
|
||||
if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
|
||||
ctx()->setCache("match", $word);
|
||||
return true;
|
||||
} elseif ($v->pattern != "") {
|
||||
$match = matchArgs($v->pattern, ctx()->getMessage());
|
||||
if($match !== false) {
|
||||
ctx()->setCache("match", $match);
|
||||
return true;
|
||||
}
|
||||
} elseif ($v->regex != "") {
|
||||
if(preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) {
|
||||
ctx()->setCache("match", $word2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
$dispatcher->setReturnFunction(function ($result) {
|
||||
if (is_string($result)) ctx()->reply($result);
|
||||
EventDispatcher::interrupt();
|
||||
});
|
||||
$r = $dispatcher->dispatchEvents();
|
||||
if ($r === null) EventDispatcher::interrupt();
|
||||
|
||||
//分发CQMessage事件
|
||||
$msg_dispatcher = new EventDispatcher(CQMessage::class);
|
||||
$msg_dispatcher->setRuleFunction(function ($v) {
|
||||
return ($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->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"]));
|
||||
});
|
||||
$msg_dispatcher->setReturnFunction(function ($result) {
|
||||
if (is_string($result)) ctx()->reply($result);
|
||||
});
|
||||
$msg_dispatcher->dispatchEvents(ctx()->getMessage());
|
||||
return;
|
||||
case "meta_event":
|
||||
//Console::success("当前数据包:".json_encode(ctx()->getData()));
|
||||
$dispatcher = new EventDispatcher(CQMetaEvent::class);
|
||||
$dispatcher->setRuleFunction(function (CQMetaEvent $v) {
|
||||
return ($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == ctx()->getData()["meta_event_type"])) &&
|
||||
($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == (ctx()->getData()["sub_type"] ?? '')));
|
||||
});
|
||||
//eval(BP);
|
||||
$dispatcher->dispatchEvents(ctx()->getData());
|
||||
return;
|
||||
case "notice":
|
||||
$dispatcher = new EventDispatcher(CQNotice::class);
|
||||
$dispatcher->setRuleFunction(function (CQNotice $v) {
|
||||
return
|
||||
($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == ctx()->getData()["notice_type"])) &&
|
||||
($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()["sub_type"])) &&
|
||||
($v->group_id == '' || ($v->group_id != '' && $v->group_id == ctx()->getData()["group_id"])) &&
|
||||
($v->operator_id == '' || ($v->operator_id != '' && $v->operator_id == ctx()->getData()["operator_id"]));
|
||||
});
|
||||
$dispatcher->dispatchEvents(ctx()->getData());
|
||||
return;
|
||||
case "request":
|
||||
$dispatcher = new EventDispatcher(CQRequest::class);
|
||||
$dispatcher->setRuleFunction(function (CQRequest $v) {
|
||||
return ($v->request_type == '' || ($v->request_type != '' && $v->request_type == ctx()->getData()['request_type'])) &&
|
||||
($v->sub_type == '' || ($v->sub_type != '' && $v->sub_type == ctx()->getData()['sub_type'])) &&
|
||||
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getData()["user_id"])) &&
|
||||
($v->comment == '' || ($v->comment != '' && $v->comment == ctx()->getData()['comment']));
|
||||
});
|
||||
$dispatcher->dispatchEvents(ctx()->getData());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function dispatchAPIResponse($req) {
|
||||
$status = $req["status"];
|
||||
$retcode = $req["retcode"];
|
||||
$data = $req["data"];
|
||||
if (isset($req["echo"]) && is_numeric($req["echo"])) {
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
if (isset($r[$req["echo"]])) {
|
||||
$origin = $r[$req["echo"]];
|
||||
$self_id = $origin["self_id"];
|
||||
$response = [
|
||||
"status" => $status,
|
||||
"retcode" => $retcode,
|
||||
"data" => $data,
|
||||
"self_id" => $self_id,
|
||||
"echo" => $req["echo"]
|
||||
];
|
||||
set_coroutine_params(["cq_response" => $response]);
|
||||
$dispatcher = new EventDispatcher(CQAPIResponse::class);
|
||||
$dispatcher->setRuleFunction(function (CQAPIResponse $response) {
|
||||
return $response->retcode == ctx()->getCQResponse()["retcode"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($response);
|
||||
|
||||
$origin_ctx = ctx()->copy();
|
||||
set_coroutine_params($origin_ctx);
|
||||
if (($origin["coroutine"] ?? false) !== false) {
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
$r[$req["echo"]]["result"] = $response;
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
Coroutine::resume($origin['coroutine']);
|
||||
}
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
unset($r[$req["echo"]]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
204
src/ZM/Store/LightCache.php
Normal file
204
src/ZM/Store/LightCache.php
Normal file
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class LightCache
|
||||
{
|
||||
/** @var Table|null */
|
||||
private static $kv_table = null;
|
||||
/** @var Table|null */
|
||||
private static $kv_lock = null;
|
||||
|
||||
private static $config = [];
|
||||
|
||||
public static $last_error = '';
|
||||
|
||||
public static function init($config) {
|
||||
self::$config = $config;
|
||||
self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]);
|
||||
self::$kv_table->column("value", Table::TYPE_STRING, $config["max_strlen"]);
|
||||
self::$kv_table->column("expire", Table::TYPE_INT);
|
||||
self::$kv_table->column("data_type", Table::TYPE_STRING, 8);
|
||||
$result = self::$kv_table->create();
|
||||
self::$kv_lock = new Table($config["size"], $config["hash_conflict_proportion"]);
|
||||
$result = $result && self::$kv_lock->create();
|
||||
if ($result === true && isset($config["persistence_path"])) {
|
||||
if (file_exists($config["persistence_path"])) {
|
||||
$r = json_decode(file_get_contents($config["persistence_path"]), true);
|
||||
if ($r === null) $r = [];
|
||||
foreach ($r as $k => $v) {
|
||||
$write = self::set($k, $v, -2);
|
||||
Console::verbose("Writing LightCache: " . $k);
|
||||
if ($write === false) {
|
||||
self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($result === false) {
|
||||
self::$last_error = '系统内存不足,申请失败';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return null|string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function get(string $key) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key);
|
||||
return $r === false ? null : self::parseGet($r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getExpire(string $key) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key, "expire");
|
||||
return $r === false ? null : $r - time();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param string|array|int $value
|
||||
* @param int $expire
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function set(string $key, $value, int $expire = -1) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
$data_type = "json";
|
||||
} elseif (is_string($value)) {
|
||||
$data_type = "";
|
||||
} elseif (is_int($value)) {
|
||||
$data_type = "int";
|
||||
} elseif (is_bool($value)) {
|
||||
$data_type = "bool";
|
||||
$value = json_encode($value);
|
||||
}else {
|
||||
throw new Exception("Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
return self::$kv_table->set($key, [
|
||||
"value" => $value,
|
||||
"expire" => $expire >= 0 ? $expire + time() : $expire,
|
||||
"data_type" => $data_type
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param $value
|
||||
* @return bool|mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function update(string $key, $value) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache.");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
$data_type = "json";
|
||||
} elseif (is_string($value)) {
|
||||
$data_type = "";
|
||||
} elseif (is_int($value)) {
|
||||
$data_type = "int";
|
||||
} else {
|
||||
throw new Exception("Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
if(self::$kv_table->get($key) === false) return false;
|
||||
return self::$kv_table->set($key, [
|
||||
"value" => $value,
|
||||
"data_type" => $data_type
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function getMemoryUsage() {
|
||||
return self::$kv_table->getMemorySize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function isset(string $key) {
|
||||
return self::get($key) !== null;
|
||||
}
|
||||
|
||||
public static function unset(string $key) {
|
||||
return self::$kv_table->del($key);
|
||||
}
|
||||
|
||||
public static function getAll() {
|
||||
$r = [];
|
||||
$del = [];
|
||||
foreach (self::$kv_table as $k => $v) {
|
||||
if ($v["expire"] <= time() && $v["expire"] >= 0) {
|
||||
$del[] = $k;
|
||||
continue;
|
||||
}
|
||||
$r[$k] = self::parseGet($v);
|
||||
}
|
||||
foreach ($del as $v) {
|
||||
self::unset($v);
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
public static function savePersistence() {
|
||||
if(self::$kv_table === null) return;
|
||||
$r = [];
|
||||
foreach (self::$kv_table as $k => $v) {
|
||||
if ($v["expire"] === -2) {
|
||||
Console::verbose("Saving " . $k);
|
||||
$r[$k] = self::parseGet($v);
|
||||
}
|
||||
}
|
||||
$r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256));
|
||||
if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!");
|
||||
}
|
||||
|
||||
private static function checkExpire($key) {
|
||||
if (($expire = self::$kv_table->get($key, "expire")) >= 0) {
|
||||
if ($expire <= time()) {
|
||||
self::$kv_table->del($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function parseGet($r) {
|
||||
switch ($r["data_type"]) {
|
||||
case "json":
|
||||
case "int":
|
||||
case "bool":
|
||||
return json_decode($r["value"], true);
|
||||
case "":
|
||||
default:
|
||||
return $r["value"];
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/ZM/Store/LightCacheInside.php
Normal file
55
src/ZM/Store/LightCacheInside.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
|
||||
class LightCacheInside
|
||||
{
|
||||
/** @var Table[]|null */
|
||||
private static $kv_table = [];
|
||||
|
||||
public static $last_error = '';
|
||||
|
||||
public static function init() {
|
||||
self::$kv_table["wait_api"] = new Table(2, 0);
|
||||
self::$kv_table["wait_api"]->column("value", Table::TYPE_STRING, 65536);
|
||||
$result = self::$kv_table["wait_api"]->create();
|
||||
if ($result === false) {
|
||||
self::$last_error = '系统内存不足,申请失败';
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function get(string $table, string $key) {
|
||||
if (!isset(self::$kv_table[$table])) throw new Exception("not initialized LightCache");
|
||||
$r = self::$kv_table[$table]->get($key);
|
||||
return $r === false ? null : json_decode($r["value"], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
* @param string $key
|
||||
* @param string|array|int $value
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function set(string $table, string $key, $value) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
try {
|
||||
return self::$kv_table[$table]->set($key, [
|
||||
"value" => json_encode($value, 256)
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function unset(string $table, string $key) {
|
||||
return self::$kv_table[$table]->del($key);
|
||||
}
|
||||
}
|
||||
44
src/ZM/Store/Lock/SpinLock.php
Normal file
44
src/ZM/Store/Lock/SpinLock.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store\Lock;
|
||||
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\System;
|
||||
use Swoole\Table;
|
||||
|
||||
class SpinLock
|
||||
{
|
||||
/** @var null|Table */
|
||||
private static $kv_lock = null;
|
||||
|
||||
private static $delay = 1;
|
||||
|
||||
public static function init($key_cnt, $delay = 1)
|
||||
{
|
||||
self::$kv_lock = new Table($key_cnt, 0.7);
|
||||
self::$delay = $delay;
|
||||
self::$kv_lock->column('lock_num', Table::TYPE_INT, 8);
|
||||
return self::$kv_lock->create();
|
||||
}
|
||||
|
||||
public static function lock(string $key)
|
||||
{
|
||||
while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { //此资源已经被锁上了
|
||||
if(Coroutine::getCid() != -1) System::sleep(self::$delay / 1000);
|
||||
else usleep(self::$delay * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public static function tryLock(string $key) {
|
||||
if (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function unlock(string $key) {
|
||||
return self::$kv_lock->set($key, ['lock_num' => 0]);
|
||||
}
|
||||
}
|
||||
13
src/ZM/Store/MySQL/SqlPoolStorage.php
Normal file
13
src/ZM/Store/MySQL/SqlPoolStorage.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store\MySQL;
|
||||
|
||||
|
||||
use Swoole\Database\PDOPool;
|
||||
|
||||
class SqlPoolStorage
|
||||
{
|
||||
/** @var PDOPool */
|
||||
public static $sql_pool = null;
|
||||
}
|
||||
47
src/ZM/Store/Redis/ZMRedis.php
Normal file
47
src/ZM/Store/Redis/ZMRedis.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\Store\Redis;
|
||||
|
||||
use Redis;
|
||||
use ZM\Exception\NotInitializedException;
|
||||
|
||||
class ZMRedis
|
||||
{
|
||||
private $conn;
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @return mixed
|
||||
* @throws NotInitializedException
|
||||
*/
|
||||
public static function call(callable $callable) {
|
||||
if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
|
||||
$r = ZMRedisPool::$pool->get();
|
||||
$result = $callable($r);
|
||||
if (isset($r->wasted)) ZMRedisPool::$pool->put(null);
|
||||
else ZMRedisPool::$pool->put($r);
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* ZMRedis constructor.
|
||||
* @throws NotInitializedException
|
||||
*/
|
||||
public function __construct() {
|
||||
if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
|
||||
$this->conn = ZMRedisPool::$pool->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Redis
|
||||
*/
|
||||
public function get() {
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if (isset($this->conn->wasted)) ZMRedisPool::$pool->put(null);
|
||||
else ZMRedisPool::$pool->put($this->conn);
|
||||
}
|
||||
}
|
||||
37
src/ZM/Store/Redis/ZMRedisPool.php
Normal file
37
src/ZM/Store/Redis/ZMRedisPool.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\Store\Redis;
|
||||
|
||||
|
||||
use RedisException;
|
||||
use Swoole\Database\RedisConfig;
|
||||
use Swoole\Database\RedisPool;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class ZMRedisPool
|
||||
{
|
||||
/** @var null|RedisPool */
|
||||
public static $pool = null;
|
||||
|
||||
public static function init($config) {
|
||||
self::$pool = new RedisPool((new RedisConfig())
|
||||
->withHost($config['host'])
|
||||
->withPort($config['port'])
|
||||
->withAuth($config['auth'])
|
||||
->withDbIndex($config['db_index'])
|
||||
->withTimeout($config['timeout'] ?? 1)
|
||||
);
|
||||
try {
|
||||
$r = self::$pool->get()->ping('123');
|
||||
if(strpos(strtolower($r), "123") !== false) {
|
||||
Console::debug("成功连接redis连接池!");
|
||||
} else {
|
||||
var_dump($r);
|
||||
}
|
||||
} catch (RedisException $e) {
|
||||
Console::error("Redis init failed! ".$e->getMessage());
|
||||
self::$pool = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/ZM/Store/ZMAtomic.php
Normal file
38
src/ZM/Store/ZMAtomic.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store;
|
||||
|
||||
|
||||
use Swoole\Atomic;
|
||||
use ZM\Config\ZMConfig;
|
||||
|
||||
class ZMAtomic
|
||||
{
|
||||
/** @var Atomic[] */
|
||||
public static $atomics;
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @return Atomic|null
|
||||
*/
|
||||
public static function get($name) {
|
||||
return self::$atomics[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化atomic计数器
|
||||
*/
|
||||
public static function init() {
|
||||
foreach (ZMConfig::get("global", "init_atomics") as $k => $v) {
|
||||
self::$atomics[$k] = new Atomic($v);
|
||||
}
|
||||
self::$atomics["stop_signal"] = new Atomic(0);
|
||||
self::$atomics["wait_msg_id"] = new Atomic(0);
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
self::$atomics["_tmp_" . $i] = new Atomic(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user