initial commit

This commit is contained in:
whale 2020-03-02 16:14:20 +08:00
parent 517082d159
commit 769b87af6a
132 changed files with 5424 additions and 2866 deletions

17
.gitignore vendored Executable file → Normal file
View File

@ -1,11 +1,6 @@
# Created by .ignore support plugin (hsz.mobi)
### Example user template template
### Example user template
# IntelliJ project files
.idea
out
gen
config/
cq_data/
composer.lock
.idea/
/src/test/
/src/webconsole/config/
/vendor/
zm.json
/zm_data/

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Blue Whale Network.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

142
README.md Executable file → Normal file
View File

@ -1,6 +1,4 @@
# CQBot-swoole
## 此分支停止维护!
# zhamao-framework
[![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]()
[![license](https://img.shields.io/badge/license-MIT-blue.svg)]()
@ -9,133 +7,35 @@
一个异步、多平台兼容的 **聊天机器人** 框架。
**当前正在重构框架可能会有很大的变化旧框架将创建old分支重构完成后新款框架为master分支。**
**框架同时将更名为zhamao-framework(炸毛框架),与旧版本兼容性不并存,届时需要根据文档进行模块的升级。**
![](resources/images/logo.png)
## CQBot-swoole 文档
本项目的文档正在努力编写中:[https://cqbot.crazywhale.org/](https://cqbot.crazywhale.org/)
## 简介
zhamao-framework 是一个基于 酷Q 的 PHP 机器人框架,它会对 QQ 机器人收到的消息进行解析处理,并以模块化的形式进行开发,来完成机器人的自然语言对话等功能。
## 什么是Swoole
PHP原生对多线程、多进程、异步等特性支持不是很好有了Swoole你可以非常简单自由地写出优雅的高性能服务器。
除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。
本项目原生支持多机器人连接故选择了反向Websocket连接方式。同时更适用于高并发、多机器人同时连接以及对接**微信公众号**和**web前端**等场景。
[Swoole官网](https://www.swoole.com/)
## 框架简介
cqbot-swoole是一个聊天机器人框架同时兼容酷Q需安装[cqhttp插件](https://cqhttp.cc)微信公众号支持多QQ账号对接。
## 说在前面
本框架目前还有一些缺陷说在前面的原因是如果你有更好的想法或发现的问题可以提issue或联系作者。
> 如果你对swoole、PHP研究较深推荐尝试学习框架的源码和运行原理后自己动手写一个。也欢迎star本项目给予支持
## 文档
本项目文档正在努力编写中:[https://framework.zhamao.xin/](https://framework.zhamao.xin/)
## 特点
- 多账号单后端式框架
- 采用模块式编写,功能之间独立性高,可分别开关各个模块和设置响应优先级
- 全局缓存,随处使用
- 协程开发,传统同步写法实现高并发
- 除swoole外不依赖composer其他项目
- 自带HTTP、websocket服务器可对接其他服务
- 支持协程MySQL
- 支持多账号
- 灵活的注解事件绑定机制
- 采用模块化编写,功能之间高内聚低耦合
- 常驻内存,全局缓存变量随处使用
- 自带 MySQL 查询器、数据库连接池等数据库连接方案
- 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口
## 环境部署
## 从 cqbot-swoole 升级
目前新的框架采用了全新的注解机制,所以旧版的框架上写的模块到新框架需要重新编写。当然为了减少工作量,新的框架也最大限度地保留了旧版框架编写的风格,一般情况下根据新版框架的文档仅需修改少量地方即可完成重写。
### 酷Q和HTTP API插件
由于框架是独立于酷Q运行的故你可以在多台主机上部署酷Q的docker。
旧版框架并入了 `old` 分支,如果想继续使用旧版框架请移步分支。升级过程中如果遇到问题可以找作者。
如果你是新用户或重新安装含有HTTPAPI插件的**酷Q-Docker**的话可以在你需要部署酷Q的Linux主机下使用下面的脚本快速构建酷Q环境此脚本会引导进行相关的cqhttp插件设置。每台部署酷Q的主机均可直接使用下方的命令服务器需要提前安装Docker
```shell
#第一次部署酷Q-httpapi docker运行下面的代码
bash -c "$(wget https://raw.githubusercontent.com/crazywhalecc/cqbot-swoole/master/start-coolq.sh -O -)"
#以后每次启动/停止/重启酷Q容器执行的命令
docker start coolq
docker stop coolq
docker restart coolq
#以上指令非root用户可能需要sudo
```
### 微信公众号
很快将兼容微信公众平台,敬请期待。
## 框架部署
### 手动安装到Linux主机上
``` shell
# 安装PHPubuntu/debian
apt-get install software-properties-common
add-apt-repository ppa:ondrej/php
apt-get update
apt-get install php7.2 php7.2-dev php7.2 php7.2-mbstring php7.2-json php7.2 php-pear
#安装PHPCentOS
rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm
yum makecache fast
yum install php72w-devel.x86_64 php72w-mbstring.x86_64 php72w-pear.noarch gcc gcc-c++ -y
# 安装Swoole
pecl install swoole
echo "extension=swoole.so" >> $(php -i | grep "Loaded Configuration File" | awk '{print $5}')
# 部署框架
git clone https://github.com/crazywhalecc/cqbot-swoole.git
# 以上指令可能需要sudo执行
```
### 使用Docker快速构建并启动
``` shell
sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole
# 可以将命令添加为alias方便以后快速启动
echo "alias cqbot='sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole'" >> ~/.bash_profile
source ~/.bash_profile
cqbot
```
## 启动
#### 直接安装后启动框架
```shell
cd cqbot-swoole/
php start.php
```
#### 在screen中运行框架
```shell
screen -R cqbot
cd cqbot-swoole/
php start.php
# Ctrl A + D 将screen放到后台运行
```
#### 使用Docker在screen中运行框架
```shell
screen -R cqbot
sudo docker run -it --rm --net=host --name cqbot -v $(pwd)/cqbot/:/root/ jesse2061/cqbot-swoole
# Ctrl A + D 将screen放到后台运行
```
## MacOS与Windows兼容性
#### MacOS下运行cqbot-swoole
mac下运行和Linux整体相同使用brew安装好PHP后通过源码编译`swoole`组件安装或使用docker。
> Docker for mac 运行需要手动指定端口`-p 20000:20000`,不能使用`--net=host`网络模式。
#### Windows下运行cqbot-swoole
因为swoole使用了Linux的特性故**不推荐**在Windows电脑或服务器使用Windows可以使用Docker运行使用 `cygwin` 环境或 `WSL` 环境。
> 不推荐原因有不能使用`reload`指令进行重启服务和不能使用全部的swoole特性。
## 贡献
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护耗费精力较大所以非常欢迎对框架的贡献。
## 关于
框架和 SDK 是 炸毛机器人 项目的核心框架开源部分。炸毛机器人3276124472是作者写的一个高性能机器人曾获全国计算机设计大赛一等奖。
框架和SDK部分代码直接从 [炸毛机器人](https://github.com/zhamao-robot/) 中移植而来炸毛机器人3290004669是作者写的一个高性能的机器人曾获全国计算机设计大赛一等奖
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ627577391或提交 Issue 进行疑难解答。
欢迎随时在HTTP-API插件群提问当然更好的话可以加作者QQ627577391或提交issue进行疑难解答。
本项目在有更新内容时请及时关注GitHub的动态更新前请将自己的**模块**代码做好备份。
本项目在更行内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。

44
bin/start Executable file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env php
<?php
use Framework\FrameworkLoader;
use Scheduler\Scheduler;
require __DIR__ . '/../src/Framework/FrameworkLoader.php';
require __DIR__ . '/../src/Scheduler/Scheduler.php';
Swoole\Coroutine::set([
'max_coroutine' => 30000,
]);
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";
echo "You need to run \"composer update\" at root of zhamao-framework!\n";
echo "Or if you are using docker or composer installed, just run \"sh bin/update-composer\"\n";
echo "In China, if your composer downloading slowly, you can get latest vendor.tar.gz from HERE:\n";
echo "https://dl2.zhamao.xin/zhamao-framework/vendor.tar.gz\n";
die;
}
$loader = new FrameworkLoader($argv);
break;
default:
echo "Unknown option \"{$argv[1]}\"!\n";
break;
}

3
bin/update-composer Normal file
View File

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

View File

@ -1,19 +1,31 @@
{
"name": "zhamao/framework",
"description": "high-performance intelligent assistant",
"minimum-stability": "stable",
"license": "MIT",
"require": {
"php": ">=7.0.0",
"ext-mbstring": "^7.1",
"ext-json": "*",
"ext-iconv": "*",
"ext-mysqli": "*",
"ext-dom": "20031129",
"ext-gd": "^7.1",
"ext-curl": "^7.1",
"ext-openssl": "^7.1"
"license": "proprietary",
"authors": [
{
"name": "whale",
"email": "crazysnowcc@gmail.com"
},
"require-dev": {
"eaglewu/swoole-ide-helper": "dev-master"
{
"name": "swift",
"email": "hugo_swift@yahoo.com"
}
],
"require": {
"swoole/ide-helper": "@dev",
"ext-mbstring": "*",
"swlib/saber": "^1.0",
"doctrine/annotations": "^1.8",
"ext-json": "*",
"ext-posix": "*",
"ext-ctype": "*"
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://mirrors.aliyun.com/composer/"
}
}
}

395
composer.lock generated Normal file
View File

@ -0,0 +1,395 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fb2cdfc2c51cc4f5c2758916f7a3b628",
"packages": [
{
"name": "doctrine/annotations",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/annotations.git",
"reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/annotations/zipball/904dca4eb10715b92569fbcd79e201d5c349b6bc",
"reference": "904dca4eb10715b92569fbcd79e201d5c349b6bc",
"shasum": ""
},
"require": {
"doctrine/lexer": "1.*",
"php": "^7.1"
},
"require-dev": {
"doctrine/cache": "1.*",
"phpunit/phpunit": "^7.5"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.7.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "Docblock Annotations Parser",
"homepage": "http://www.doctrine-project.org",
"keywords": [
"annotations",
"docblock",
"parser"
],
"time": "2019-10-01T18:55:10+00:00"
},
{
"name": "doctrine/lexer",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/lexer.git",
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/lexer/zipball/5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
"reference": "5242d66dbeb21a30dd8a3e66bf7a73b66e05e1f6",
"shasum": ""
},
"require": {
"php": "^7.2"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11.8",
"phpunit/phpunit": "^8.2"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"autoload": {
"psr-4": {
"Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"keywords": [
"annotations",
"docblock",
"lexer",
"parser",
"php"
],
"time": "2019-10-30T14:39:59+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-message.git",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
"reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP messages",
"homepage": "https://github.com/php-fig/http-message",
"keywords": [
"http",
"http-message",
"psr",
"psr-7",
"request",
"response"
],
"time": "2016-08-06T14:39:51+00:00"
},
{
"name": "swlib/http",
"version": "v1.0.5",
"source": {
"type": "git",
"url": "https://github.com/swlib/http.git",
"reference": "d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swlib/http/zipball/d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060",
"reference": "d8ad0b0aed67ab9d1b18a6ce87c40813c0e00060",
"shasum": ""
},
"require": {
"php": ">=7.0",
"psr/http-message": "~1.0"
},
"require-dev": {
"eaglewu/swoole-ide-helper": "dev-master",
"phpunit/phpunit": "~7"
},
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Swlib\\Http\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "twosee",
"email": "twose@qq.com"
}
],
"description": "Swlib-HTTP base class repository, PSR implementation",
"keywords": [
"http",
"php",
"psr7",
"swoole"
],
"time": "2019-09-24T06:38:56+00:00"
},
{
"name": "swlib/saber",
"version": "1.0.10",
"source": {
"type": "git",
"url": "https://github.com/swlib/saber.git",
"reference": "10b3a1b4e30edce6de583c44cb7d2185f33a8a1f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swlib/saber/zipball/10b3a1b4e30edce6de583c44cb7d2185f33a8a1f",
"reference": "10b3a1b4e30edce6de583c44cb7d2185f33a8a1f",
"shasum": ""
},
"require": {
"php": ">=7.1",
"swlib/http": "1.0.5",
"swlib/util": "1.0.0"
},
"require-dev": {
"eaglewu/swoole-ide-helper": "dev-master",
"phpunit/phpunit": "~7"
},
"type": "library",
"autoload": {
"classmap": [
"src/Saber.php",
"src/SaberGM.php"
],
"psr-4": {
"Swlib\\Saber\\": "src"
},
"files": [
"src/include/functions.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "twosee",
"email": "twose@qq.com"
}
],
"description": "Swoole coroutine HTTP client",
"keywords": [
"ajax",
"axios",
"client",
"coroutine",
"curl",
"http",
"php",
"psr7",
"requests",
"swoole"
],
"time": "2019-09-26T08:29:29+00:00"
},
{
"name": "swlib/util",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/swlib/util.git",
"reference": "f9aaa9028ea16a489a46b9ad09f0e844997cffe7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swlib/util/zipball/f9aaa9028ea16a489a46b9ad09f0e844997cffe7",
"reference": "f9aaa9028ea16a489a46b9ad09f0e844997cffe7",
"shasum": ""
},
"require": {
"php": ">=7.0"
},
"require-dev": {
"eaglewu/swoole-ide-helper": "dev-master",
"phpunit/phpunit": "~7"
},
"type": "library",
"autoload": {
"psr-4": {
"Swlib\\Util\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "twosee",
"email": "twose@qq.com"
}
],
"description": "Swlib Toolkit",
"keywords": [
"php",
"swlib",
"swoole",
"util"
],
"time": "2018-07-23T04:36:19+00:00"
},
{
"name": "swoole/ide-helper",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/swoole/ide-helper.git",
"reference": "a7bae643171ff4176ff37868a40276819e5789aa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/swoole/ide-helper/zipball/a7bae643171ff4176ff37868a40276819e5789aa",
"reference": "a7bae643171ff4176ff37868a40276819e5789aa",
"shasum": ""
},
"require-dev": {
"guzzlehttp/guzzle": "~6.5.0",
"squizlabs/php_codesniffer": "~3.5.0",
"symfony/filesystem": "~4.3.0",
"zendframework/zend-code": "~3.3.0"
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Team Swoole",
"email": "team@swoole.com"
}
],
"description": "IDE help files for Swoole.",
"time": "2020-01-15T08:00:05+00:00"
}
],
"packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"swoole/ide-helper": 20
},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"ext-mbstring": "*",
"ext-json": "*",
"ext-posix": "*"
},
"platform-dev": []
}

64
config/global.php Normal file
View File

@ -0,0 +1,64 @@
<?php
global $config;
/** bind host */
$config['host'] = '0.0.0.0';
/** bind port */
$config['port'] = 20001;
/** 存放框架内文件数据的目录 */
$config['zm_data'] = WORKING_DIR.'/zm_data/';
/** 存放各个模块配置文件的目录 */
$config['config_dir'] = $config['zm_data'].'config/';
/** 存放崩溃和运行日志的目录 */
$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
];
/** MySQL数据库连接信息host留空则启动时不创建sql连接池 */
$config['sql_config'] = [
'sql_host' => '',
'sql_port' => 3306,
'sql_username' => 'name',
'sql_database' => 'db_name',
'sql_password' => '',
'sql_enable_cache' => true,
'sql_reset_cache' => '0300'
];
/** CQHTTP连接约定的token */
$config["access_token"] = "";
/** HTTP服务器固定请求头的返回 */
$config['http_header'] = [
'X-Powered-By' => 'zhamao-framework',
'Content-Type' => 'text/html; charset=utf-8'
];
/** HTTP服务器在指定状态码下回复的页面默认 */
$config['http_default_code_page'] = [
'404' => '404.html'
];
/** zhamao-framework在框架启动时初始化的atomic们 */
$config['init_atomics'] = [
'in_count' => 0, //消息接收message的统计数量
'out_count' => 0, //消息发送调用send_*_msg的统计数量
'reload_time' => 0, //调用reload功能统计数量
'wait_msg_id' => 0, //协程挂起id自增
'info_level' => 0, //终端显示的log等级
];
/** 自动保存的缓存保存时间(秒) */
$config['auto_save_interval'] = 900;
return $config;

View File

@ -1,20 +0,0 @@
{
"robot_name": "CQBot",
"cqbot_version": "1.0.0",
"config_dir": "cq_data\/config\/",
"crash_dir": "cq_data\/crash\/",
"user_dir": "cq_data\/users\/",
"cq_data": "cq_data\/",
"admin_group": "",
"access_token": "",
"info_level": 0,
"admin": [],
"save_user_data": true,
"swoole_host": "",
"swoole_port": "",
"swoole_worker_num": 1,
"swoole_dispatch_mode": 2,
"swoole_log_file": "cq_data\/crash\/swoole.log",
"swoole_use_tick": true,
"swoole_tick_interval": 1000
}

View File

@ -1,19 +0,0 @@
FROM php:7.2
WORKDIR /root
RUN echo "Asia/Shanghai" > /etc/timezone
RUN dpkg-reconfigure -f noninteractive tzdata
ENV LANG C.UTF-8
RUN apt-get update && apt-get install curl libxml2 libzip-dev openssl git -y
# Install php extensions
RUN docker-php-ext-install zip
RUN docker-php-ext-install mysqli
RUN docker-php-ext-install iconv
RUN docker-php-ext-install mbstring
RUN pecl install swoole
RUN docker-php-ext-enable swoole
VOLUME ["/root/"]
CMD if [ ! -d "CQBot-swoole" ]; then git clone https://github.com/crazywhalecc/CQBot-swoole.git; fi && cd CQBot-swoole/ && bash -c "php start.php"

11
resources/html/404.html Normal file
View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sorry</title>
</head>
<body>
Nothing here.<br>
Please check your url. (404)
</body>
</html>

BIN
resources/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 KiB

View File

@ -0,0 +1,19 @@
<?php
namespace Custom\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\Interfaces\CustomAnnotation;
/**
* Class CustomAnnotation
* @Annotation
* @Target("ALL")
* @package Custom\Annotation
*/
class Example implements CustomAnnotation
{
/** @var string */
public $str;
}

View File

@ -0,0 +1,14 @@
<?php
namespace Custom\Connection;
use ZM\Connection\WSConnection;
class CustomConnection extends WSConnection
{
public function getType() {
return "custom";
}
}

212
src/Framework/Console.php Executable file
View File

@ -0,0 +1,212 @@
<?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 "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 "pink":
return "\x1b[38;5;207m" . $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 ") . "ERROR] ";
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") . " WARN] ";
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, "yellow") . "\n");
}
static function info($obj, $head = null) {
if ($head === null) $head = date("[H:i:s ") . "INFO] ";
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, "lightblue") . "\n");
}
static function log($obj, $color = "") {
if (!is_string($obj)) var_dump($obj);
else echo(self::setColor($obj, $color) . "\n");
}
static function msg($obj, $self_id = "") {
if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) {
if (!isset($obj["post_type"])) {
switch ($obj["action"]) {
case "send_private_msg":
$msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue");
$msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue");
$msg .= Console::setColor(" > ", "gray");
$msg .= $obj["params"]["message"];
Console::log($msg);
break;
case "send_group_msg":
//TODO: 写新的控制台消息API消息处理
Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_discuss_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_msg":
$obj["action"] = "send_" . $obj["message_type"] . "_msg";
self::msg($obj);
break;
case "send_wechat_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
default:
break;
}
} else {
if ($obj["post_type"] == "message") {
switch ($obj["message_type"]) {
case "group":
//
//TODO: 写新的控制台消息event处理
case "private":
case "discuss":
case "wechat":
}
}
}
}
}
static function stackTrace(){
$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(){
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 '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 '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);
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Framework;
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 Server */
private $server;
public function __construct($args = []) {
if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!");
self::$instance = $this;
chdir(__DIR__ . '/../..');
define('WORKING_DIR', getcwd());
$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");
$this->server->set(self::$settings->get("swoole"));
$this->server->on("WorkerStart", [$this, "onWorkerStart"]);
$this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); });
$this->server->on("request", function ($request, $response) {
$response = new Response($response);
EventHandler::callSwooleEvent("request", $request, $response);
});
$this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); });
$this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); });
ZMBuf::initAtomic();
Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port"));
$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);
}
}

View File

@ -0,0 +1,33 @@
<?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() {
/** @noinspection PhpIncludeInspection */
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;
}
}

View File

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

18
src/Framework/Logger.php Normal file
View File

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

View File

@ -0,0 +1,264 @@
<?php
namespace Framework;
use Co;
use Exception;
use Swoole\Coroutine;
use swoole\server;
class RemoteShell
{
const STX = "DEBUG";
private static $contexts = array();
static $oriPipeMessageCallback = null;
/**
* @var server
*/
static $serv;
static $menu = array(
"p|print [variant]\t打印一个PHP变量的值",
"e|exec [code]\t执行一段PHP代码",
"w|worker [id]\t切换Worker进程",
"l|list\t打印服务器所有连接的fd",
"s|stats\t打印服务器状态",
"c|coros\t打印协程列表",
"b|bt\t打印协程调用栈",
"i|info [fd]\t显示某个连接的信息",
"h|help\t显示帮助界面",
"q|quit\t退出终端",
);
const PAGESIZE = 20;
/**
* @param $serv server
* @param string $host
* @param int $port
* @throws Exception
* @throws Exception
*/
static function listen($serv, $host = "127.0.0.1", $port = 9599) {
Console::warning("正在监听".$host.":".strval($port)."的调试接口,请注意安全");
$port = $serv->listen($host, $port, SWOOLE_SOCK_TCP);
if (!$port) {
throw new Exception("listen fail.");
}
$port->set(array(
"open_eof_split" => true,
'package_eof' => "\r\n",
));
$port->on("Connect", array(__CLASS__, 'onConnect'));
$port->on("Close", array(__CLASS__, 'onClose'));
$port->on("Receive", array(__CLASS__, 'onReceive'));
if (method_exists($serv, 'getCallback')) {
self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage');
}
$serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage'));
self::$serv = $serv;
}
static function onConnect($serv, $fd, $reactor_id) {
self::$contexts[$fd]['worker_id'] = $serv->worker_id;
self::output($fd, implode("\r\n", self::$menu));
}
static function output($fd, $msg) {
if (!isset(self::$contexts[$fd]['worker_id'])) {
$msg .= "\r\nworker#" . self::$serv->worker_id . "$ ";
} else {
$msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ ";
}
self::$serv->send($fd, $msg);
}
static function onClose($serv, $fd, $reactor_id) {
unset(self::$contexts[$fd]);
}
static function onPipeMessage($serv, $src_worker_id, $message) {
//不是 debug 消息
if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) {
if (self::$oriPipeMessageCallback == null) {
trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING);
return true;
}
return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message);
} else {
$request = unserialize(substr($message, strlen(self::STX)));
self::call($request['fd'], $request['func'], $request['args']);
}
return true ;
}
static protected function call($fd, $func, $args) {
ob_start();
call_user_func_array($func, $args);
self::output($fd, ob_get_clean());
}
static protected function exec($fd, $func, $args) {
//不在当前Worker进程
if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) {
self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']);
} else {
self::call($fd, $func, $args);
}
}
static function getCoros() {
var_export(iterator_to_array(Coroutine::listCoroutines()));
}
static function getBackTrace($_cid) {
$info = Co::getBackTrace($_cid);
if (!$info) {
echo "coroutine $_cid not found.";
} else {
echo get_debug_print_backtrace($info);
}
}
static function printVariant($var) {
$var = ltrim($var, '$ ');
var_dump($var);
var_dump($$var);
}
static function evalCode($code) {
eval($code . ';');
}
/**
* @param $serv server
* @param $fd
* @param $reactor_id
* @param $data
*/
static function onReceive($serv, $fd, $reactor_id, $data) {
$args = explode(" ", $data, 2);
$cmd = trim($args[0]);
unset($args[0]);
switch ($cmd) {
case 'w':
case 'worker':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$dstWorkerId = intval($args[1]);
self::$contexts[$fd]['worker_id'] = $dstWorkerId;
self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]");
break;
case 'e':
case 'exec':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$var = trim($args[1]);
self::exec($fd, 'self::evalCode', [$var]);
break;
case 'p':
case 'print':
$var = trim($args[1]);
self::exec($fd, 'self::printVariant', [$var]);
break;
case 'h':
case 'help':
self::output($fd, implode("\r\n", self::$menu));
break;
case 's':
case 'stats':
$stats = $serv->stats();
self::output($fd, var_export($stats, true));
break;
case 'c':
case 'coros':
self::exec($fd, 'self::getCoros', []);
break;
/**
* 查看协程堆栈
*/
case 'bt':
case 'b':
case 'backtrace':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_cid = intval($args[1]);
self::exec($fd, 'self::getBackTrace', [$_cid]);
break;
case 'i':
case 'info':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_fd = intval($args[1]);
$info = $serv->getClientInfo($_fd);
if (!$info) {
self::output($fd, "connection $_fd not found.");
} else {
self::output($fd, var_export($info, true));
}
break;
case 'l':
case 'list':
$tmp = array();
foreach ($serv->connections as $fd) {
$tmp[] = $fd;
if (count($tmp) > self::PAGESIZE) {
self::output($fd, json_encode($tmp));
$tmp = array();
}
}
if (count($tmp) > 0) {
self::output($fd, json_encode($tmp));
}
break;
case 'q':
case 'quit':
$serv->close($fd);
break;
default:
self::output($fd, "unknow command[$cmd]");
break;
}
}
}
function get_debug_print_backtrace($traces) {
$ret = array();
foreach ($traces as $i => $call) {
$object = '';
if (isset($call['class'])) {
$object = $call['class'] . $call['type'];
if (is_array($call['args'])) {
foreach ($call['args'] as &$arg) {
get_arg($arg);
}
}
}
$ret[] = '#' . str_pad($i, 3, ' ')
. $object . $call['function'] . '(' . implode(', ', $call['args'])
. ') called at [' . $call['file'] . ':' . $call['line'] . ']';
}
return implode("\n", $ret);
}
function get_arg(&$arg) {
if (is_object($arg)) {
$arr = (array)$arg;
$args = array();
foreach ($arr as $key => $value) {
if (strpos($key, chr(0)) !== false) {
$key = ''; // Private variable found
}
$args[] = '[' . $key . '] => ' . get_arg($value);
}
$arg = get_class($arg) . ' Object (' . implode(',', $args) . ')';
}
}

95
src/Framework/ZMBuf.php Executable file
View File

@ -0,0 +1,95 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/2/25
* Time: 下午11:11
*/
namespace Framework;
use Swoole\Atomic;
use swoole_atomic;
use ZM\Annotation\MappingNode;
use ZM\connection\WSConnection;
use ZM\Utils\Scheduler;
use ZM\Utils\SQLPool;
class ZMBuf
{
//读写的缓存数据需要在worker_num = 1下才能正常使用
/** @var mixed[] ZMBuf的data */
private static $cache = [];
/** @var WSConnection[] */
static $connect = [];//储存连接实例的数组
//Scheduler计划任务连接实例只可以在单worker_num时使用
/** @var Scheduler|null */
static $scheduler = null;
//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 MappingNode 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等级的原子计数
/** @var swoole_atomic $reload_time */
public static $events = [];
/** @var Atomic[] */
public static $atomics;
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); }
public static function resetCache() {
self::$cache = [];
self::$connect = [];
self::$time_nlp = null;
}
/**
* 初始化atomic计数器
*/
public static function initAtomic() {
foreach(ZMBuf::globals("init_atomics") as $k => $v) {
self::$atomics[$k] = new Atomic($v);
}
}
}

View File

@ -0,0 +1,146 @@
<?php
function classLoader($p) {
$filepath = getClassPath($p);
if ($filepath === null)
echo "F:Warning: get class path wrongs.$p\n";
//else echo "F:DBG: Found " . $p . "\n";
try {
require_once $filepath;
} catch (Exception $e) {
echo "Error when finding class: ".$p.PHP_EOL;
die;
}
}
function getClassPath($class_name) {
$dir = str_replace("\\", "/", $class_name);
$dir = WORKING_DIR . "/src/" . $dir . ".php";
//echo "@@@".$dir.PHP_EOL;
$dir = str_replace("\\", "/", $dir);
if (file_exists($dir)) return $dir;
else return null;
}
/**
* 使用自己定义的万san能分割函数
* @param $msg
* @param bool $ban_comma
* @return array
*/
function explodeMsg($msg, $ban_comma = false) {
$msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg);
$msg = str_replace("\t", "\n", $msg);
}
$msgs = explode("\n", $msg);
$ls = [];
foreach ($msgs as $k => $v) {
if (trim($v) == "") continue;
$ls[] = trim($v);
}
return $ls;
}
function unicode_decode($str) {
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str);
}
/**
* 获取模块文件夹下的每个类文件的类名称
* @param $dir
* @param string $indoor_name
* @return array
*/
function getAllClasses($dir, $indoor_name) {
$list = scandir($dir);
$classes = [];
unset($list[0], $list[1]);
foreach ($list as $v) {
//echo "Finding " . $dir . $v . PHP_EOL;
//echo "At " . $indoor_name . PHP_EOL;
if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v));
elseif (mb_substr($v, -4) == ".php") {
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}
}
return $classes;
}
function matchPattern($pattern, $context) {
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
return false;
if (mb_substr($pattern, 0, 1) == mb_substr($context, 0, 1))
return matchPattern(mb_substr($pattern, 1), mb_substr($context, 1));
if (mb_substr($pattern, 0, 1) == "*")
return matchPattern(mb_substr($pattern, 1), $context) || matchPattern($pattern, mb_substr($context, 1));
return false;
}
function split_explode($del, $str, $divide_en = false) {
$str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if (
is_numeric(mb_substr($str[0], $i, 1)) &&
(
!is_numeric(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' ' &&
ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false
)
) {
$str[0] = mb_substr($str[0], 0, $i) . " " . mb_substr($str[0], $i);
} elseif (
$divide_en &&
ctype_alnum(mb_substr($str[0], $i, 1)) &&
!ctype_alnum(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' '
) {
$str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i);
}
}
$str = implode($del, $str);
//echo $str."\n";
$ls = [];
foreach (explode($del, $str) as $k => $v) {
if (trim($v) == "") continue;
$ls[] = $v;
}
//var_dump($ls);
return $ls == [] ? [""] : $ls;
}
function matchArgs($pattern, $context) {
$result = [];
if (matchPattern($pattern, $context)) {
if (mb_strpos($pattern, "*") === false) return [];
$exp = explode("*", $pattern);
$i = 0;
foreach ($exp as $k => $v) {
//echo "[MATCH$k] " . $v . PHP_EOL;
if ($v == "" && $k == 0) continue;
elseif ($v == "" && $k == count($exp) - 1) {
$context = $context . "^EOL";
$v = "^EOL";
}
$cur_var = "";
//echo mb_substr($context, $i) . "|" . $v . PHP_EOL;
$ori = $i;
while (($a = mb_substr($context, $i, mb_strlen($v))) != $v && $a != "") {
$cur_var .= mb_substr($context, $i, 1);
++$i;
}
if ($i != $ori || $k == 1 || $k == count($exp) - 1) {
//echo $cur_var . PHP_EOL;
$result[] = $cur_var;
}
$i += mb_strlen($v);
}
return $result;
} else return false;
}

View File

@ -0,0 +1,34 @@
<?php
namespace Module\Example;
use Framework\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Module\Closed;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\CQConnection;
use ZM\ModBase;
/**
* Class Hello
* @package Module\Example
* @Closed()
*/
class Hello extends ModBase
{
/**
* @SwooleEventAt("open",rule="connectType:qq")
* @param $conn
*/
public function onConnect(CQConnection $conn){
Console::info("机器人 ".$conn->getQQ()." 已连接!");
}
/**
* @CQCommand("你好")
*/
public function hello(){
return "你好啊,我是由炸毛框架构建的机器人!";
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace Module\TestMod;
use Framework\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQNotice;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\CQConnection;
use ZM\ModBase;
/**
* Class CQTest
* @package Module\TestMod
*/
class CQTest extends ModBase
{
/**
* @SwooleEventAt(type="open",rule="connectType:qq")
* @param CQConnection $conn
*/
public function onRobotConnect($conn){
Console::info("QQ robot: ".$conn->getQQ()." connected.");
}
/**
* @CQCommand("多命令a")
* @CQCommand(regexMatch="*是什么")
* @param $arg
* @return string
* @throws \ZM\Exception\DbException
*/
public function hello($arg) {
return "我也不知道".$arg[0]."是什么呀";
}
/**
* @CQNotice(notice_type="group_admin")
*/
public function onNotice(){
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Module\TestMod;
use Framework\Console;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\WSConnection;
use ZM\ModBase;
/**
* Class Hola
* @package Module\TestMod
* @noinspection PhpUnused
*/
class Hola extends ModBase
{
/**
* @SwooleEventAt(type="open",rule="connectType:unknown")
* @param WSConnection $conn
*/
public function onUnknownConnect() {
Console::warning("Unknown websocket has been shutdown.");
Console::stackTrace();
$this->connection->close();
}
}

View File

@ -0,0 +1,29 @@
<?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计时器内的处理逻辑
}
}

140
src/Scheduler/Scheduler.php Normal file
View File

@ -0,0 +1,140 @@
<?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";
}
}
}

View File

@ -1,10 +1,11 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/8/27
* Time: 7:17 PM
*/
namespace ZM\API;
use Framework\Console;
use ZM\Utils\ZMUtil;
class CQ
{
@ -147,7 +148,7 @@ class CQ
case "qq":
case "163":
case "xiami":
return "[CQ:music,id=" . $id_or_url . "]";
return "[CQ:music,type=$type,id=$id_or_url]";
case "custom":
if ($title === null || $audio === null) {
Console::error("传入CQ码实例的标题和音频链接不能为空");
@ -180,18 +181,6 @@ class CQ
return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]";
}
/**
* 将字符串中的CQ码敏感符号进行转义
* @param $str
* @return mixed
*/
public static function encode($str) {
$str = str_replace("&", "&amp;", $str);
$str = str_replace("[", "&#91;", $str);
$str = str_replace("]", "&#93;", $str);
return $str;
}
/**
* 反转义字符串中的CQ码敏感符号
* @param $str
@ -210,22 +199,22 @@ class CQ
return $str;
}
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];
/**
* 转义CQ码
* @param $msg
* @return mixed
*/
public static function escape($msg) {
$msg = str_replace("&", "&amp;", $msg);
$msg = str_replace("[", "&#91;", $msg);
$msg = str_replace("]", "&#93;", $msg);
return $msg;
}
public static function removeCQ($msg) {
while (($cq = ZMUtil::getCQ($msg)) !== null) {
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
}
return $msg;
}
}

249
src/ZM/API/CQAPI.php Normal file
View File

@ -0,0 +1,249 @@
<?php
namespace ZM\API;
use Co;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\WSConnection;
/**
* @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
{
public static function quick_reply(WSConnection $conn, $data, $msg, $yield = null) {
switch ($data["message_type"]) {
case "group":
return self::send_group_msg($conn, ["group_id" => $data["group_id"], "message" => $msg], $yield);
case "private":
return self::send_private_msg($conn, ["user_id" => $data["user_id"], "message" => $msg], $yield);
case "discuss":
return self::send_discuss_msg($conn, ["discuss_id" => $data["discuss_id"], "message" => $msg], $yield);
}
return null;
}
public static function __callStatic($name, $arg) {
$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::error("Unknown API " . $name);
return false;
}
$reply = ["action" => $find];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
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::error("发送错误,机器人连接不存在!");
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 $reply
* @param |null $function
* @return bool
*/
private 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);
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);
if ($function === true) {
Co::suspend();
$data = ZMBuf::get("sent_api")[$api_id];
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
return isset($data['result']) ? $data['result'] : null;
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 999,
"data" => null,
"self_id" => $connection->getQQ()
];
$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;
return false;
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace ZM\Annotation;
use Closure;
abstract class AnnotationBase
{
public $method;
public $class;
public function __toString() {
$str = __CLASS__ . ": ";
foreach ($this as $k => $v) {
$str .= "\n\t" . $k . " => ";
if (is_string($v)) $str .= "\"$v\"";
elseif (is_numeric($v)) $str .= $v;
elseif (is_bool($v)) $str .= ($v ? "TRUE" : "FALSE");
elseif (is_array($v)) $str .= json_encode($v, JSON_UNESCAPED_UNICODE);
elseif ($v instanceof Closure) $str .= "@AnonymousFunction";
elseif (is_null($v)) $str .= "NULL";
else $str .= "@Unknown";
}
return $str;
}
}

View File

@ -0,0 +1,223 @@
<?php
namespace ZM\Annotation;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\AnnotationReader;
use Framework\ZMBuf;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\CQ\{CQAfter, CQBefore, CQCommand, CQMessage, CQMetaEvent, CQNotice, CQRequest};
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Interfaces\CustomAnnotation;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed;
use ZM\Annotation\Module\SaveBuffer;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Annotation\Interfaces\Rule;
use ZM\Connection\WSConnection;
use ZM\Utils\DataProvider;
class AnnotationParser
{
/**
* 注册各个模块类的注解和模块level的排序
* @throws ReflectionException
* @throws AnnotationException
*/
public static function registerMods() {
self::loadAnnotationClasses();
$all_class = getAllClasses(WORKING_DIR . "/src/Module/", "Module");
$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);
foreach ($class_annotations as $vs) {
if ($vs instanceof Closed) {
continue 2;
} elseif ($vs instanceof Controller) {
$class_prefix = $vs->prefix;
} elseif ($vs instanceof SaveBuffer) {
DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder);
}
}
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);
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;
}
}
}
//给支持level的排个序
foreach (ZMBuf::$events as $class_name => $v) {
if ((new $class_name()) instanceof Level) {
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;
}
}
}
}
}
}
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;
}
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;
}
}
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) {
$prefix_exp = explode("/", $prefix);
$route_exp = explode("/", $vss->route);
foreach ($prefix_exp as $k => $v) {
if ($v == "" || $v == ".." || $v == ".") {
unset($prefix_exp[$k]);
}
}
foreach ($route_exp as $k => $v) {
if ($v == "" || $v == ".." || $v == ".") {
unset($route_exp[$k]);
}
}
$a = ZMBuf::$req_mapping_node;
$p = $a;
if ($prefix_exp == [] && $route_exp == []) {
$p->setMethod($method->getName());
$p->setClass($class->getName());
$p->setRequestMethod($vss->request_method);
return;
}
while (($shift = array_shift($prefix_exp)) !== null) {
$p->addRoute($shift, new MappingNode($shift));
$p = $p->getRoute($shift);
}
while (($shift = array_shift($route_exp)) !== null) {
if (mb_substr($shift, 0, 1) == "{" && mb_substr($shift, -1, 1) == "}") {
$p->removeAllRoute();
}
$p->addRoute($shift, new MappingNode($shift));
$p = $p->getRoute($shift);
}
$p->setMethod($method->getName());
$p->setClass($class->getName());
$p->setRequestMethod($vss->request_method);
}
private static function loadAnnotationClasses() {
$class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
$class = getAllClasses(WORKING_DIR . "/src/Custom/Annotation/", "Custom\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
}
}

View File

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

View File

@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQBefore
* @Annotation
* @Target("METHOD")
* @package ZM\Annotation\CQ
*/
class CQBefore 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): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQCommand
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQCommand extends AnnotationBase implements Level
{
/** @var string */
public $match = "";
/** @var string */
public $regexMatch = "";
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int { return $this->level; }
/**
* @param int $level
*/
public function setLevel(int $level) { $this->level = $level; }
}

View File

@ -0,0 +1,41 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQMessage
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQMessage extends AnnotationBase implements Level
{
/**
* @var string
*/
public $message_type = "";
/** @var int */
public $user_id = 0;
/** @var int */
public $group_id = 0;
/** @var int */
public $discuss_id = 0;
/** @var string */
public $message = "";
/** @var string */
public $raw_message = "";
/** @var int */
public $level = 20;
public function getLevel() { return $this->level; }
public function setLevel(int $level) {
$this->level = $level;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQMetaEvent
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQMetaEvent extends AnnotationBase implements Level
{
/**
* @var string
* @Required()
*/
public $meta_event_type = '';
/** @var string */
public $sub_type = '';
/** @var int */
public $level;
/**
* @return mixed
*/
public function getLevel() { return $this->level; }
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQNotice
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQNotice extends AnnotationBase implements Level
{
/** @var string */
public $notice_type = "";
/** @var string */
public $sub_type = "";
/** @var int */
public $group_id = 0;
/** @var int */
public $operator_id = 0;
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQRequest
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQRequest extends AnnotationBase implements Level
{
/** @var string */
public $request_type = "";
/** @var string */
public $sub_type = "";
/** @var int */
public $user_id = 0;
/** @var string */
public $comment = "";
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Controller
* @Annotation
* @Target("CLASS")
* @package ZM\Annotation\Http
*/
class Controller extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $prefix = '';
}

View File

@ -0,0 +1,39 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class RequestMapping
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Http
*/
class RequestMapping extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $route = '';
/**
* @var string
*/
public $name = '';
/**
* @var array
*/
public $request_method = [RequestMethod::GET, RequestMethod::POST];
/**
* Routing path params binding. eg. {"id"="\d+"}
* @var array
*/
public $params = [];
}

View File

@ -0,0 +1,30 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use ZM\Annotation\AnnotationBase;
/**
* Class RequestMethod
* @Annotation
*
* @package ZM\Annotation\Http
*/
class RequestMethod extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $method = self::GET;
public const GET = 'GET';
public const POST = 'POST';
public const PUT = 'PUT';
public const PATCH = 'PATCH';
public const DELETE = 'DELETE';
public const OPTIONS = 'OPTIONS';
public const HEAD = 'HEAD';
}

View File

@ -0,0 +1,10 @@
<?php
namespace ZM\Annotation\Interfaces;
interface CustomAnnotation
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace ZM\Annotation\Interfaces;
interface Level
{
public function getLevel();
public function setLevel(int $level);
}

View File

@ -0,0 +1,10 @@
<?php
namespace ZM\Annotation\Interfaces;
interface Rule
{
public function getRule();
}

View File

@ -0,0 +1,103 @@
<?php
namespace ZM\Annotation;
use Closure;
class MappingNode
{
private $node;
/** @var MappingNode[] */
private $route = [];
private $method = null;
private $class = null;
private $request_method = [];
/** @var Closure|null */
private $rule = null;
public function __construct(string $node_name) { $this->node = $node_name; }
public function addRoute(string $route_name, MappingNode $route_node) { $this->route[$route_name] = $route_node; }
/**
* @param string $shift
* @return MappingNode|null
*/
public function getRoute(string $shift) {
return $this->route[$shift] ?? null;
}
public function getRealRoute(string $shift, array &$bind_params) {
if (mb_substr(key($this->route), 0, 1) == "{" && mb_substr(key($this->route), -1, 1) == "}") {
$param_name = mb_substr(current($this->route)->getNodeName(), 1, -1);
$bind_params[$param_name] = $shift;
return current($this->route);
} else return $this->route[$shift] ?? null;
}
public function setMethod($method) { $this->method = $method; }
public function setClass($class) { $this->class = $class; }
public function setRequestMethod($method) {
if (is_string($method)) $this->request_method = [$method];
else $this->request_method = $method;
}
public function getNodeName() { return $this->node; }
public function getRule() { return $this->rule; }
public function setRule(Closure $rule): void { $this->rule = $rule; }
public function removeAllRoute() { $this->route = []; }
/**
* @return null
*/
public function getMethod() {
return $this->method;
}
/**
* @return array
*/
public function getRequestMethod(): array {
return $this->request_method;
}
/**
* @return null
*/
public function getClass() {
return $this->class;
}
/**
* @return string
*/
public function getNode(): string {
return $this->node;
}
public function __toString() {
$str = "[" . $this->node . "] => ";
if ($this->class != "" && $this->class != null)
$str .= "\n\t" . $this->class . "->" . $this->method . ": " . implode(", ", $this->request_method);
$str .= "\n\t[Route] => [";
foreach ($this->route as $k => $v) {
$r = $v;
$r = explode("\n", $r);
foreach ($r as $ks => $vs) {
$r[$ks] = "\t" . $r[$ks];
}
$r = implode("\n", $r);
$str .= "\n\t" . $r;
}
$str .= "\n]";
return $str;
}
}

View File

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

View File

@ -0,0 +1,25 @@
<?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|null $sub_folder */
public $sub_folder = null;
}

View File

@ -0,0 +1,76 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Interfaces\Rule;
/**
* Class SwooleEventAfter
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Swoole
*/
class SwooleEventAfter extends AnnotationBase implements Rule, Level
{
/**
* @var string
* @Required
*/
public $type;
/** @var string */
public $rule = "";
/** @var int */
public $level = 20;
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void {
$this->type = $type;
}
/**
* @return string
*/
public function getRule(): string {
return $this->rule;
}
/**
* @param string $rule
*/
public function setRule(string $rule): void {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Interfaces\Rule;
/**
* Class SwooleEventAt
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Swoole
*/
class SwooleEventAt extends AnnotationBase implements Rule, Level
{
/**
* @var string
* @Required
*/
public $type;
/** @var string */
public $rule = "";
/** @var int */
public $level = 20;
public $callback = null;
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void {
$this->type = $type;
}
/**
* @return string
*/
public function getRule(): string {
return $this->rule;
}
/**
* @param string $rule
*/
public function setRule(string $rule): void {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@ -0,0 +1,23 @@
<?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";
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace ZM\Connection;
use Framework\ZMBuf;
class ConnectionManager
{
/**
* 通过server的fd获取WSConnection实例化对象
* @param int $fd
* @return WSConnection
*/
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[]
*/
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;
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,52 @@
<?php
namespace ZM\Connection;
use Framework\Console;
use Framework\Logger;
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) Logger::writeSwooleLog("API push failed. Data: " . $data);
Console::error("websocket数据未成功推送长度" . strlen($data));
return false;
}
return true;
}
}

120
src/ZM/DB/DB.php Normal file
View File

@ -0,0 +1,120 @@
<?php
namespace ZM\DB;
use framework\Console;
use framework\ZMBuf;
use Swoole\Coroutine;
use Swoole\Coroutine\MySQL\Statement;
use ZM\Exception\DbException;
class DB
{
private static $table_list = [];
public static function initTableList() {
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []);
foreach ($result as $v) {
self::$table_list[] = $v['TABLE_NAME'];
}
}
/**
* @param $table_name
* @param bool $enable_cache
* @return Table
* @throws DbException
*/
public static function table($table_name, $enable_cache = null) {
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){
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");
}
}
return Table::getTableInstance($table_name);
}
public static function statement($line) {
self::rawQuery($line, []);
}
public static function unprepared($line) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
}
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
throw new DbException("无法连接SQL" . $line);
}
$result = $conn->query($line) === false ? false : ($conn->errno != 0 ? false : true);
ZMBuf::$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::error($e->getMessage());
return false;
}
}
public static function rawQuery(string $line, $params) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
}
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
throw new DbException("无法连接SQL" . $line);
}
$ps = $conn->prepare($line);
if ($ps === false) {
$conn->close();
ZMBuf::$sql_pool->connect_cnt -= 1;
throw new DbException("SQL语句查询错误" . $line . ",错误信息:" . $conn->error);
} else {
if (!($ps instanceof Statement)) {
throw new DbException("语句查询错误!" . $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);
//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;
}
} 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);
}
Console::error($e->getMessage());
return false;
}
}
}

28
src/ZM/DB/DeleteBody.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace ZM\DB;
class DeleteBody
{
use WhereBody;
/**
* @var Table
*/
private $table;
/**
* DeleteBody constructor.
* @param Table $table
*/
public function __construct(Table $table) {
$this->table = $table;
}
public function save() {
list($sql, $param) = $this->getWhereSQL();
return DB::rawQuery("DELETE FROM " . $this->table->getTableName() . " WHERE " . $sql, $param);
}
}

28
src/ZM/DB/InsertBody.php Normal file
View File

@ -0,0 +1,28 @@
<?php
namespace ZM\DB;
class InsertBody
{
/**
* @var Table
*/
private $table;
private $row;
/**
* InsertBody constructor.
* @param Table $table
* @param $row
*/
public function __construct(Table $table, $row) {
$this->table = $table;
$this->row = $row;
}
public function save() {
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, 5, '?')).')', $this->row);
}
}

88
src/ZM/DB/SelectBody.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace ZM\DB;
use Framework\Console;
class SelectBody
{
use WhereBody;
/** @var Table */
private $table;
private $select_thing;
private $result = null;
public function __construct($table, $select_thing) {
$this->table = $table;
$this->select_thing = $select_thing;
}
public function get() { return $this->fetchAll(); }
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;
}
return $this->getResult();
}
public function fetchFirst() {
return $this->fetchAll()[0] ?? null;
}
public function value() {
$r = $this->fetchFirst();
if ($r === null) return null;
return current($r);
}
public function execute() {
$str = $this->queryPrepare();
$this->result = DB::rawQuery($str[0], $str[1]);
}
public function getResult() { return $this->result; }
public function equals(SelectBody $body) {
if ($this->select_thing != $body->getSelectThing()) return false;
elseif ($this->where_thing == $body->getWhereThing()) return false;
else return true;
}
/**
* @return mixed
*/
public function getSelectThing() { return $this->select_thing; }
/**
* @return array
*/
public function getWhereThing() { return $this->where_thing; }
private function queryPrepare() {
$msg = "SELECT " . implode(", ", $this->select_thing) . " FROM " . $this->table->getTableName();
$sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '=');
if ($sql[0] != '') {
$msg .= " WHERE " . $sql[0];
$array = $sql[1];
$sql = $this->table->paintWhereSQL($this->where_thing['!='] ?? [], '!=');
if ($sql[0] != '') $msg .= " AND " . $sql[0];
$array = array_merge($array, $sql[1]);
}
return [$msg, $array ?? []];
}
}

79
src/ZM/DB/Table.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace ZM\DB;
class Table
{
private $table_name;
/** @var SelectBody[] */
public $cache = [];
private static $table_instance = [];
private $enable_cache;
public function __construct($table_name, $enable_cache) {
$this->enable_cache = $enable_cache;
$this->table_name = $table_name;
self::$table_instance[$table_name] = $this;
}
public static function getTableInstance($table_name) {
if (isset(self::$table_instance[$table_name])) return self::$table_instance[$table_name];
else return null;
}
public function select($what = []) {
return new SelectBody($this, $what == [] ? ["*"] : $what);
}
public function where($column, $operation_or_value, $value = null){
return (new SelectBody($this, ["*"]))->where($column, $operation_or_value, $value);
}
public function insert($row) {
$this->cache = [];
return new InsertBody($this, $row);
}
public function update(array $set_value) {
$this->cache = [];
return new UpdateBody($this, $set_value);
}
public function delete() {
$this->cache = [];
return new DeleteBody($this);
}
public function statement($line){
$this->cache = [];
//TODO: 无返回的statement语句
}
public function paintWhereSQL($rule, $operator) {
if ($rule == []) return ["", []];
$msg = "";
$param = [];
foreach ($rule as $k => $v) {
if ($msg == "") {
$msg .= $k . " $operator ? ";
} else {
$msg .= "AND " . $k . " $operator ?";
}
$param[] = $v;
}
return [$msg, $param];
}
/**
* @return mixed
*/
public function getTableName() { return $this->table_name; }
public function isCacheEnabled() { return $this->enable_cache; }
}

50
src/ZM/DB/UpdateBody.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace ZM\DB;
use ZM\Exception\DbException;
class UpdateBody
{
use WhereBody;
/**
* @var Table
*/
private $table;
/**
* @var array
*/
private $set_value;
/**
* UpdateBody constructor.
* @param Table $table
* @param array $set_value
*/
public function __construct(Table $table, array $set_value) {
$this->table = $table;
$this->set_value = $set_value;
}
/**
* @throws DbException
*/
public function save(){
$arr = [];
$msg = [];
foreach($this->set_value as $k => $v) {
$msg []= $k .' = ?';
$arr[]=$v;
}
if(($msg ?? []) == []) throw new DbException('update value sets can not be empty!');
$line = 'UPDATE '.$this->table->getTableName().' SET '.implode(', ', $msg);
if($this->where_thing != []) {
list($sql, $param) = $this->getWhereSQL();
$arr = array_merge($arr, $param);
$line .= ' WHERE '.$sql;
}
return DB::rawQuery($line, $arr);
}
}

33
src/ZM/DB/WhereBody.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace ZM\DB;
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;
else $this->where_thing['='][$column] = $operation_or_value;
return $this;
}
protected function getWhereSQL(){
$param = [];
$msg = '';
foreach($this->where_thing as $k => $v) {
foreach($v as $ks => $vs) {
if($param != []) {
$msg .= ' AND '.$ks ." $k ?";
} else {
$msg .= "$ks $k ?";
}
$param []=$vs;
}
}
return [$msg, $param];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class CourseCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace ZM\DBCache;
interface DBCache
{
public static function reset();
}

View File

@ -0,0 +1,15 @@
<?php
namespace ZM\DBCache;
class DBCacheManager
{
public static function freeAllCache(){
DHUerCache::reset();
UserCache::reset();
GroupCache::reset();
CourseCache::reset();
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace ZM\DBCache;
class DHUerCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class GroupCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class UserCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@ -0,0 +1,149 @@
<?php
namespace ZM\Event\CQ;
use Co;
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\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class MessageEvent
{
private $function_call = false;
private $data;
private $circle;
/**
* @var \ZM\Event\Swoole\MessageEvent
*/
private $swoole_event;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQMessage::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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][CQMessage::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace ZM\Event\CQ;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Connection\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class MetaEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQMetaEvent::class] ?? [] as $v) {
$c = $v->class;
/** @var CQMetaEvent $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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());
}
}
}

View File

@ -0,0 +1,88 @@
<?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\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class NoticeEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQNotice::class] ?? [] as $v) {
$c = $v->class;
/** @var CQNotice $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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][CQNotice::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_NOTICE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
}

View File

@ -0,0 +1,89 @@
<?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\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class RequestEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQRequest::class] ?? [] as $v) {
$c = $v->class;
/** @var CQRequest $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], 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][CQRequest::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_REQUEST);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
}

11
src/ZM/Event/Event.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace ZM\Event;
interface Event
{
const SWOOLE = 1;
const CQ = 2;
}

View File

@ -0,0 +1,115 @@
<?php
namespace ZM\Event;
use Co;
use Exception;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent};
use ZM\Http\Response;
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 {
(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;
}
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);
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, MessageEvent $event, $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, $event, $level);
if ($event->onBefore()) $event->onActivate();
$event->onAfter();
return $event->hasReply();
break;
case "notice":
$event = new CQ\NoticeEvent($event_data, $event, $level);
if($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "request":
$event = new CQ\RequestEvent($event_data, $event, $level);
if($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "meta_event":
$event = new CQ\MetaEvent($event_data, $event, $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"]);
}
}
}

View File

@ -0,0 +1,94 @@
<?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();
try {
if (ConnectionManager::get($this->frame->fd)->getType() == "qq") {
$data = json_decode($this->frame->data, true);
if (isset($data["post_type"]))
EventHandler::callCQEvent($data, $this, 0);
else
EventHandler::callCQResponse($data);
}
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
$conn = ConnectionManager::get($this->frame->fd);
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
call_user_func_array([$class, $v->method], [$conn]);
if ($class->block_continue) break;
}
}
} catch (Exception $e) {
Console::error("出现错误: " . $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;
}
}

View File

@ -0,0 +1,132 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Framework\ZMBuf;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
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;
}
/**
* @inheritDoc
*/
public function onActivate() {
ZMUtil::checkWait();
foreach (ZMBuf::globals("http_header") as $k => $v) {
$this->response->setHeader($k, $v);
}
$uri = $this->request->server["request_uri"];
if ($uri != "/") {
$uri = explode("/", $uri);
$uri = array_diff($uri, ["..", "", "."]);
$node = ZMBuf::$req_mapping_node;
$params = [];
while (true) {
$r = array_shift($uri);
if ($r === null) break;
if (($node2 = $node->getRealRoute($r, $params)) !== null) {
$node = $node2;
continue;
} else {
$this->responseStatus(404);
return $this;
}
}
if ($node->getRule() === null || call_user_func($node->getRule(), $this->request) === true) { //判断规则是否存在,如果有规则则走一遍规则
if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面
$c_name = $node->getClass();
/** @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->getMethod()], [$params]);
if (is_string($r) && !$this->response->isEnd()) $this->response->end($r);
if ($class->block_continue) return $this;
if ($this->response->isEnd()) return $this;
}
}
} else {
if (($node = ZMBuf::$req_mapping_node)->getMethod() !== null) {
if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面
$c_name = $node->getClass();
/** @var ModBase $class */
$class = new $c_name(["request" => $this->request, "response" => $this->response], ModHandleType::SWOOLE_REQUEST);
$r = call_user_func_array([$class, $node->getMethod()], []);
if (is_string($r) && !$this->response->isEnd()) $this->response->end($r);
if ($class->block_continue) return $this;
if ($this->response->isEnd()) return $this;
}
}
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 ($v->rule) {
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;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace ZM\Event\Swoole;
use ZM\Event\Event;
interface SwooleEvent extends Event
{
/**
* @return SwooleEvent
*/
public function onActivate();
/**
* @return SwooleEvent
*/
public function onAfter();
}

View File

@ -0,0 +1,61 @@
<?php
namespace ZM\Event\Swoole;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
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();
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;
}
}

View File

@ -0,0 +1,93 @@
<?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;
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;
}
}

View File

@ -0,0 +1,137 @@
<?php
namespace ZM\Event\Swoole;
use Doctrine\Common\Annotations\AnnotationException;
use ReflectionException;
use Swoole\Coroutine;
use Swoole\Timer;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\MappingNode;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Connection\ConnectionManager;
use ZM\DB\DB;
use ZM\DBCache\DBCacheManager;
use Framework\Console;
use Framework\GlobalConfig;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\DataProvider;
use ZM\Utils\SQLPool;
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
*/
public function onActivate(): WorkerStartEvent {
Console::info("Worker启动中");
ZMBuf::resetCache(); //清空变量缓存
ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
DBCacheManager::freeAllCache(); // 清空数据库缓存
$this->resetConnections();//释放所有与framework的连接
//设置炸毛buf中储存的对象
ZMBuf::$globals = new GlobalConfig();
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);
ZMBuf::$req_mapping_node = new MappingNode("/");
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[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
*/
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资源中");
/** @noinspection PhpIncludeInspection */
require_once WORKING_DIR . "/vendor/autoload.php";
//加载各个模块的注解类,以及反射
Console::info("检索Module中");
AnnotationParser::registerMods();
//加载Custom目录下的自定义的内部类
ConnectionManager::registerCustomClass();
}
private function setAutosaveTimer($globals) {
DataProvider::$buffer_list = [];
Timer::tick($globals * 1000, function() {
DataProvider::saveBuffer();
});
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace ZM\Exception;
use Exception;
class DbException extends Exception
{
}

View File

@ -0,0 +1,12 @@
<?php
namespace ZM\Exception;
use Exception;
class InvalidArgumentException extends Exception
{
}

View File

@ -0,0 +1,20 @@
<?php
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;
}
}

229
src/ZM/Http/Response.php Normal file
View File

@ -0,0 +1,229 @@
<?php
namespace ZM\Http;
class Response
{
public $fd = 0;
public $socket = null;
public $header = null;
public $cookie = null;
public $trailer = null;
/**
* @var \Swoole\Http\Response
*/
private $response;
private $is_end = false;
public function __construct(\Swoole\Http\Response $response) {
$this->response = $response;
$this->fd = $response->fd;
$this->socket = $response->socket;
$this->header = $response->header;
$this->cookie = $response->cookie;
if (isset($response->trailer))
$this->trailer = $response->trailer;
}
/**
* @return mixed
*/
public function initHeader() {
return $this->response->initHeader();
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function cookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function setCookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function rawcookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $http_code
* @param $reason
* @return mixed
*/
public function status($http_code, $reason = null) {
return $this->response->status($http_code, $reason);
}
/**
* @param $http_code
* @param $reason
* @return mixed
*/
public function setStatusCode($http_code, $reason = null) {
return $this->response->setStatusCode($http_code, $reason);
}
/**
* @param $key
* @param $value
* @param $ucwords
* @return mixed
*/
public function header($key, $value, $ucwords = null) {
return $this->response->header($key, $value, $ucwords);
}
/**
* @param $key
* @param $value
* @param $ucwords
* @return mixed
*/
public function setHeader($key, $value, $ucwords = null) {
return $this->response->setHeader($key, $value, $ucwords);
}
/**
* @param $key
* @param $value
* @return mixed
*/
public function trailer($key, $value) {
return $this->response->trailer($key, $value);
}
/**
* @return mixed
*/
public function ping() {
return $this->response->ping();
}
/**
* @param $content
* @return mixed
*/
public function write($content) {
return $this->response->write($content);
}
/**
* @param $content
* @return mixed
*/
public function end($content = null) {
$this->is_end = true;
return $this->response->end($content);
}
public function isEnd() { return $this->is_end; }
/**
* @param $filename
* @param $offset
* @param $length
* @return mixed
*/
public function sendfile($filename, $offset = null, $length = null) {
return $this->response->sendfile($filename, $offset, $length);
}
/**
* @param $location
* @param $http_code
* @return mixed
*/
public function redirect($location, $http_code = null) {
return $this->redirect($location, $http_code);
}
/**
* @return mixed
*/
public function detach() {
return $this->response->detach();
}
/**
* @param $fd
* @return mixed
*/
public static function create($fd) {
return \Swoole\Http\Response::create($fd);
}
/**
* @return mixed
*/
public function upgrade() {
return $this->response->upgrade();
}
/**
* @param $data
* @param null $opcode
* @param null $flags
* @return mixed
*/
public function push($data, $opcode = null, $flags = null) {
return $this->response->push($data, $opcode, $flags);
}
/**
* @return mixed
*/
public function recv() {
return $this->response->recv();
}
/**
* @return mixed
*/
public function close() {
return $this->response->close();
}
public function __destruct() {
}
}

165
src/ZM/ModBase.php Normal file
View File

@ -0,0 +1,165 @@
<?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; }
}

20
src/ZM/ModHandleType.php Normal file
View File

@ -0,0 +1,20 @@
<?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;
}

View File

@ -0,0 +1,46 @@
<?php
namespace ZM\Utils;
use Co;
use Framework\Console;
use Framework\ZMBuf;
class DataProvider
{
public static $buffer_list = [];
public static function getResourceFolder() {
return WORKING_DIR . '/resources/';
}
public static function addSaveBuffer($buf_name, $sub_folder = null) {
$name = ($sub_folder ?? "") . "/" . $buf_name . ".json";
self::$buffer_list[$buf_name] = $name;
ZMBuf::set($buf_name, self::getJsonData($name));
}
public static function saveBuffer() {
$head = Console::setColor(date("[H:i:s ") . "INFO] Saving buffer......", "lightblue");
echo $head;
foreach(self::$buffer_list as $k => $v) {
self::setJsonData($v, ZMBuf::get($k));
}
echo Console::setColor("saved", "lightblue").PHP_EOL;
}
private static function getJsonData(string $string) {
if(!file_exists(self::getDataFolder().$string)) return [];
return json_decode(Co::readFile(self::getDataFolder().$string), true);
}
private static function setJsonData($filename, array $args) {
Co::writeFile(self::getDataFolder() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING));
}
private static function getDataFolder() {
return CONFIG_DIR;
}
}

101
src/ZM/Utils/SQLPool.php Normal file
View File

@ -0,0 +1,101 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2019/1/5
* Time: 4:48 PM
*/
namespace ZM\Utils;
use framework\Console;
use framework\ZMBuf;
use SplQueue;
use Swoole\Coroutine;
use Swoole\Coroutine\Mysql;
class SQLPool
{
protected $available = true;
protected $pool;
private $info;
public $co_list = [];
public $connect_cnt = 0;
public function __construct() {
$this->pool = new SplQueue;
$this->info = [
"host" => ZMBuf::globals("sql_config")["sql_host"],
"port" => ZMBuf::globals("sql_config")["sql_port"],
"user" => ZMBuf::globals("sql_config")["sql_username"],
"password" => ZMBuf::globals("sql_config")["sql_password"],
"database" => ZMBuf::globals("sql_config")["sql_database"]
];
}
/**
* 将利用过的连接入队
* @param $mysql
*/
public function put($mysql) {
$this->pool->push($mysql);
if (($a = array_shift($this->co_list)) !== null) {
Coroutine::resume($a);
}
}
/**
* 获取队中的连接,如果不存在则创建新的
* @param bool $no_new_conn
* @return bool|mixed|Mysql
*/
public function get($no_new_conn = false) {
if (count($this->pool) == 0 && $this->connect_cnt <= 70) {
if($no_new_conn) return false;
$this->connect_cnt += 1;
$r = $this->newConnect();
if ($r !== false) {
return $r;
} else {
$this->connect_cnt -= 1;
return false;
}
} elseif (count($this->pool) > 0) {
$con = $this->pool->pop();
if ($con->connected !== false) return $con;
} elseif ($this->connect_cnt > 70) {
$this->co_list[]=Coroutine::getuid();
Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 ".Coroutine::stats()["coroutine_num"]);
Coroutine::suspend();
return $this->get($no_new_conn);
}
return false;
}
public function getCount() {
return $this->pool->count();
}
public function destruct() {
// 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出
$this->available = false;
while (!$this->pool->isEmpty()) {
$this->pool->pop();
}
}
private function newConnect() {
//无空闲连接,创建新连接
$mysql = new Mysql();
Console::info("创建SQL连接中当前有" . $this->connect_cnt . "个连接");
$res = $mysql->connect($this->info);
if ($res == false) {
echo $mysql->error . PHP_EOL;
return false;
} else {
return $mysql;
}
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace ZM\Utils;
class ScheduleManager
{
//TODO: 写framework部分的schedule控制代码
}

71
src/ZM/Utils/ZMUtil.php Normal file
View File

@ -0,0 +1,71 @@
<?php
namespace ZM\Utils;
use Co;
use framework\Console;
use Framework\ZMBuf;
class ZMUtil
{
/**
* 检查workerStart是否运行结束
*/
public static function checkWait() {
if(ZMBuf::isset("wait_start")) {
ZMBuf::append("wait_start", Co::getCid());
Co::suspend();
}
}
public static function stop() {
Console::info(Console::setColor("Stopping server...", "red"));
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
DataProvider::saveBuffer();
ZMBuf::$server->shutdown();
ZMBuf::$server->stop();
}
public static function getHttpCodePage(int $http_code) {
if(isset(ZMBuf::globals("http_default_code_page")[$http_code])) {
return Co::readFile(DataProvider::getResourceFolder()."html/".ZMBuf::globals("http_default_code_page")[$http_code]);
} else return null;
}
public static function reload() {
Console::info(Console::setColor("Reloading server...", "gold"));
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
DataProvider::saveBuffer();
ZMBuf::$server->reload();
}
/**
* 解析CQ码
* @param $msg
* @return array|null
* 0123456
* [CQ:at]
*/
static function getCQ($msg) {
if (($start = mb_strpos($msg, '[')) === false) return null;
if (($end = mb_strpos($msg, ']')) === false) return null;
$msg = mb_substr($msg, $start + 1, $end - $start - 1);
if (mb_substr($msg, 0, 3) != "CQ:") return null;
$msg = mb_substr($msg, 3);
$msg2 = explode(",", $msg);
$type = array_shift($msg2);
$array = [];
foreach ($msg2 as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$array[$sk] = implode("=", $ss);
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
}
}

View File

@ -1,142 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/4/12
* Time: 10:43
*/
class CQBot
{
/** @var Framework */
public $framework;
//传入数据
public $data = null;
//检测有没有回复过消息
private $function_called = false;
public $starttime;
public $endtime;
public $self_id;
public $circle;
public function __construct(Framework $framework, $circle, $package) {
$this->circle = $circle;
$this->starttime = microtime(true);
$this->framework = $framework;
$this->data = $package;
$this->self_id = $this->data["self_id"];
}
public function execute() {
if ($this->circle >= 5) return false;
if ($this->data === null) return false;
if (isset($it["user_id"]) && CQUtil::isRobot($this->data["user_id"])) return false;
if (isset($it["group_id"]) && $this->data["group_id"] == Cache::get("admin_group")) {
if ($this->getRobotId() != Cache::get("admin_active")) {
return false;
}
}
if ($this->data["message"] == "")
return false;
foreach (Cache::get("mods") as $v) {
/** @var ModBase $r */
$r = new $v($this, $this->data);
if ($r->split_execute === true) {
$msg = trim($this->data["message"]);
$msg = explodeMsg($msg);
$r->execute($msg);
}
}
$this->endtime = microtime(true);
return $this->function_called;
}
/**
* 快速回复消息
* @param $msg
* @param callable|null $callback
* @param bool $async
* @return bool
*/
public function reply($msg, callable $callback = null, $async = false) {
$this->function_called = true;
switch ($this->data["message_type"]) {
case "group":
$this->function_called = true;
if (!$async) return CQAPI::send_group_msg($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback);
else return CQAPI::send_group_msg_async($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback);
case "private":
$this->function_called = true;
if (!$async) return CQAPI::send_private_msg($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback);
else return CQAPI::send_private_msg_async($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback);
case "discuss":
$this->function_called = true;
if (!$async) return CQAPI::send_discuss_msg($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback);
else return CQAPI::send_discuss_msg_async($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback);
case "wechat":
//TODO: add wechat account support in the future
break;
}
return false;
}
public function isAdmin($user) {
if (in_array($user, Cache::get("admin"))) return true;
else return false;
}
public function replace($msg, $dat) {
$msg = str_replace("{at}", '[CQ:at,qq=' . $dat["user_id"] . ']', $msg);
$msg = str_replace("{and}", '&', $msg);
while (strpos($msg, '{') !== false && strpos($msg, '}') !== false) {
if (strpos($msg, '{') > strpos($msg, '}')) return $msg;
$start = strpos($msg, '{');
$end = strpos($msg, '}');
$sub = explode("=", substr($msg, $start + 1, $end - $start - 1));
switch ($sub[0]) {
case "at":
$qq = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:at,qq=' . $qq . ']', $msg);
break;
case "image":
case "record":
$pictFile = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:' . $sub[0] . ',file=' . $pictFile . ']', $msg);
break;
case "dice":
$file = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:dice,type=' . $file . ']', $msg);
break;
case "shake":
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:shake]', $msg);
break;
case "music":
$id = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:music,type=163,id=' . $id . ']', $msg);
break;
case "internet":
array_shift($sub);
$id = implode("=", $sub);
if (substr($id, 0, 7) != "http://") $id = "http://" . $id;
$is = file_get_contents($id, false, NULL, 0, 1024);
if ($is == false) $is = "[请求时发生了错误] 如有疑问,请联系管理员";
$msg = str_replace(substr($msg, $start, $end - $start + 1), $is, $msg);
break 2;
default:
break 2;
}
}
return $msg;
}
/**
* 返回当前机器人的id
* @return string|null
*/
public function getRobotId() {
return $this->data["self_id"] ?? null;
}
}

View File

@ -1,232 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/26
* Time: 9:23 AM
*/
/**
* Class CQAPI
* @method static send_private_msg($self_id, $params, callable $function = null)
* @method static send_group_msg($self_id, $params, callable $function = null)
* @method static send_discuss_msg($self_id, $params, callable $function = null)
* @method static send_msg($self_id, $params, callable $function = null)
* @method static delete_msg($self_id, $params, callable $function = null)
* @method static send_like($self_id, $params, callable $function = null)
* @method static set_group_kick($self_id, $params, callable $function = null)
* @method static set_group_ban($self_id, $params, callable $function = null)
* @method static set_group_anonymous_ban($self_id, $params, callable $function = null)
* @method static set_group_whole_ban($self_id, $params, callable $function = null)
* @method static set_group_admin($self_id, $params, callable $function = null)
* @method static set_group_anonymous($self_id, $params, callable $function = null)
* @method static set_group_card($self_id, $params, callable $function = null)
* @method static set_group_leave($self_id, $params, callable $function = null)
* @method static set_group_special_title($self_id, $params, callable $function = null)
* @method static set_discuss_leave($self_id, $params, callable $function = null)
* @method static set_friend_add_request($self_id, $params, callable $function = null)
* @method static set_group_add_request($self_id, $params, callable $function = null)
* @method static get_login_info($self_id, $params, callable $function = null)
* @method static get_stranger_info($self_id, $params, callable $function = null)
* @method static get_group_list($self_id, $params, callable $function = null)
* @method static get_group_member_info($self_id, $params, callable $function = null)
* @method static get_group_member_list($self_id, $params, callable $function = null)
* @method static get_cookies($self_id, $params, callable $function = null)
* @method static get_csrf_token($self_id, $params, callable $function = null)
* @method static get_credentials($self_id, $params, callable $function = null)
* @method static get_record($self_id, $params, callable $function = null)
* @method static get_status($self_id, $params, callable $function = null)
* @method static get_version_info($self_id, $params, callable $function = null)
* @method static set_restart($self_id, $params, callable $function = null)
* @method static set_restart_plugin($self_id, $params, callable $function = null)
* @method static clean_data_dir($self_id, $params, callable $function = null)
* @method static clean_plugin_log($self_id, $params, callable $function = null)
* @method static _get_friend_list($self_id, $params, callable $function = null)
* @method static _get_group_info($self_id, $params, callable $function = null)
* @method static _get_vip_info($self_id, $params, callable $function = null)
* @method static send_private_msg_async($self_id, $params, callable $function = null)
* @method static send_group_msg_async($self_id, $params, callable $function = null)
* @method static send_discuss_msg_async($self_id, $params, callable $function = null)
* @method static send_msg_async($self_id, $params, callable $function = null)
* @method static delete_msg_async($self_id, $params, callable $function = null)
* @method static set_group_kick_async($self_id, $params, callable $function = null)
* @method static set_group_ban_async($self_id, $params, callable $function = null)
* @method static set_group_anonymous_ban_async($self_id, $params, callable $function = null)
* @method static set_group_whole_ban_async($self_id, $params, callable $function = null)
* @method static set_group_admin_async($self_id, $params, callable $function = null)
* @method static set_group_anonymous_async($self_id, $params, callable $function = null)
* @method static set_group_card_async($self_id, $params, callable $function = null)
* @method static set_group_leave_async($self_id, $params, callable $function = null)
* @method static set_group_special_title_async($self_id, $params, callable $function = null)
* @method static set_discuss_leave_async($self_id, $params, callable $function = null)
* @method static set_friend_add_request_async($self_id, $params, callable $function = null)
* @method static set_group_add_request_async($self_id, $params, callable $function = null)
*/
class CQAPI
{
public static function debug($msg, $head = null, $self_id = null) {
if($head === null) $msg = date("[H:i:s") . " DEBUG] ".$msg;
if($self_id === null) $self_id = CQUtil::findRobot();
else $msg = $head.$msg;
if($self_id !== null){
return self::send_group_msg($self_id, ["message" => $msg, "group_id" => Cache::get("admin_group")]);
}
return false;
}
public static function __callStatic($name, $arg) {
if(mb_substr($name, -6) == "_after"){
$all = self::getSupportedAPIs();
$find = null;
$true_name = mb_substr($name, 0, -6);
if(!in_array($true_name, $all)){
Console::error("Unknown API " . $name);
return false;
}
$ms = array_shift($arg);
Scheduler::after($ms, function() use ($true_name, $arg){
$reply = ["action" => $true_name];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
$reply["params"] = $arg[1];
}
return self::processAPI($arg[0], $reply, $arg[2]);
});
return true;
}
$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::error("Unknown API " . $name);
return false;
}
$reply = ["action" => $find];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
$reply["params"] = $arg[1];
}
return self::processAPI($arg[0], $reply, $arg[2]);
}
/********************** 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 $self_id
* @param $reply
* @param callable|null $function
* @return bool
*/
private static function processAPI($self_id, $reply, callable $function = null) {
$api_id = Cache::$api_id->get();
$reply["echo"] = $api_id;
Cache::$api_id->add(1);
if ($self_id instanceof RobotWSConnection) {
$connection = $self_id;
$self_id = $connection->getQQ();
} else $connection = ConnectionManager::getRobotConnection($self_id);
if ($connection instanceof NullConnection) {
Console::error("未找到qq号" . $self_id . "的API连接");
return false;
}
if ($connection->push(json_encode($reply))) {
Cache::appendKey("sent_api", $api_id, [
"data" => $reply,
"time" => microtime(true),
"func" => $function,
"self_id" => $self_id
]);
if (in_array($reply["action"], self::getLoggedAPIs())) {
Console::msg($reply);
Cache::$out_count->add(1);
}
return true;
} else return false;
}
}

View File

@ -1,16 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/26
* Time: 10:01 PM
*/
class CustomWSConnection extends WSConnection
{
public function __construct(swoole_websocket_server $server, $fd, $request) {
parent::__construct($server, $fd, $request->server["remote_addr"]);
$this->create_success = true;
// Here to put your custom other websocket connection to manage.
}
}

View File

@ -1,37 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/18
* Time: 11:11 AM
*/
class NullConnection extends WSConnection
{
private $qq = 0;
public function __construct(swoole_websocket_server $server, $fd, $remote, $qq = 0) {
parent::__construct($server, $fd, $remote);
$this->qq = $qq;
}
public function push($data, $push_error_record = true) {
$data = unicodeDecode($data);
CQUtil::errorlog("API推送失败未发送的消息: \n" . $data, "API ERROR", false);
if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true));
return false;
}
public function sendAPI($api, $params = [], $echo = []){
$data["action"] = $api;
if($params != []) $data["params"] = $params;
$echo_result["self_id"] = $this->qq;
if($echo != []){
if(count($echo) >= 1) $echo_result['type'] = array_shift($echo);
if(!empty($echo)) $echo_result["params"] = $echo;
}
$data["echo"] = $echo_result;
Console::debug("将要发送的API包" . json_encode($data, 128 | 256));
return $this->push(json_encode($data));
}
}

View File

@ -1,133 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/14
* Time: 11:59 PM
*/
class RobotWSConnection extends WSConnection
{
const ALIAS_LIST = [
"10001" => "小马哥",
"838714432" => "鲸鱼的test机器人"
];
private $qq;
private $alias;//别名
private $sub_type;
public function __construct(swoole_websocket_server $server, $fd, $qq, swoole_http_request $request, $sub_type) {
parent::__construct($server, $fd, $request->server["remote_addr"]);
$this->qq = $qq;
$this->alias = self::ALIAS_LIST[$qq] ?? "机器人" . $qq;
$this->sub_type = $sub_type;
foreach (ConnectionManager::getAll("robot") as $k => $v) {
if ($v->getQQ() == $this->getQQ() && $k != $this->fd && $v->getSubType() == $this->getSubType()) {
$v->close();
}
}
if ($sub_type != "event") {
$obj = $this;
$r = $this->sendAPI("get_version_info", [], function ($response) use ($obj) {
Cache::set("version_info", $response["data"]);
});
if ($r)
$this->create_success = $this->sendAPI("send_group_msg", ["message" => "[CQBot] 机器人 " . $this->getAlias() . " 已连接链接fd" . $this->fd, "group_id" => Cache::get("admin_group")]);
} else $this->create_success = true;
}
/**
* 返回连接的QQ
* @return mixed
*/
public function getQQ() {
return $this->qq;
}
/**
* @return mixed|string
*/
public function getAlias() {
return $this->alias;
}
public function sendAPI($api, $params = [], callable $callback = null) {
$data["action"] = $api;
if ($params != []) $data["params"] = $params;
if ($this->sub_type == "event") {
$conns = ConnectionManager::getAll("robot");
foreach ($conns as $k => $v) {
if ($v->getSubType() == "api" && $v->getQQ() == $this->getQQ()) {
$api_id = Cache::$api_id->get();
$data["echo"] = $api_id;
Cache::$api_id->add(1);
Cache::appendKey("sent_api", $api_id, [
"data" => $data,
"time" => microtime(true),
"func" => $callback,
"self_id" => $this->getQQ()
]);
if ($v->push(json_encode($data))) {
if (in_array($data["action"], CQAPI::getLoggedAPIs())) {
Console::msg($data);
Cache::$out_count->add(1);
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 998,
"data" => null,
"self_id" => $this->getQQ()
];
$s = Cache::get("sent_api")[$data["echo"]];
StatusParser::parse($response, $data);
if ($s["func"] !== null)
call_user_func($s["func"], $response, $data);
Cache::removeKey("sent_api", $data["echo"]);
return false;
}
}
}
return false;
}
$api_id = Cache::$api_id->get();
$data["echo"] = $api_id;
Cache::$api_id->add(1);
Cache::appendKey("sent_api", $api_id, [
"data" => $data,
"time" => microtime(true),
"func" => $callback,
"self_id" => $this->getQQ()
]);
if ($this->push(json_encode($data))) {
if (in_array($data["action"], CQAPI::getLoggedAPIs())) {
Console::msg($data);
Cache::$out_count->add(1);
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 999,
"data" => null,
"self_id" => $this->getQQ()
];
$s = Cache::get("sent_api")[$data["echo"]];
StatusParser::parse($response, $data);
if ($s["func"] !== null)
call_user_func($s["func"], $response, $data);
Cache::removeKey("sent_api", $data["echo"]);
return false;
}
}
/**
* @return mixed
*/
public function getSubType() {
return $this->sub_type;
}
}

View File

@ -1,64 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/7/27
* Time: 12:31 PM
*/
abstract class WSConnection
{
public $fd;
protected $server;
protected $remote_address;
public $create_success = false;
public function __construct(swoole_websocket_server $server, $fd, $remote) {
$this->server = $server;
$this->fd = $fd;
$this->remote_address = $remote;
}
/**
* 返回swoole server
* @return swoole_websocket_server
*/
public function getServer() {
return $this->server;
}
public function getType(){
if($this instanceof RobotWSConnection) return "robot";
elseif($this instanceof CustomWSConnection) return "custom";
else return "unknown";
}
public function push($data, $push_error_record = true) {
if ($data === null || $data == "") {
Console::error("Empty data pushed.");
return false;
}
if ($this->server->push($this->fd, $data) === false) {
$data = unicodeDecode($data);
CQUtil::errorlog("API推送失败未发送的消息: \n" . $data, "API ERROR", false);
if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true));
return false;
}
return true;
}
public function close() {
$this->server->close($this->fd);
ConnectionManager::remove($this->fd);
}
/**
* @return mixed
*/
public function getRemoteAddress() {
return $this->remote_address;
}
}

View File

@ -1,17 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午3:10
*/
abstract class Event
{
/**
* @return Framework
*/
public function getFramework(){
return Framework::getInstance();
}
}

View File

@ -1,31 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:35 AM
*/
class MessageEvent extends Event
{
public function __construct($req) {
if (CQUtil::isRobot($req["user_id"])) return;
$in_count = Cache::$in_count->get();
Cache::$in_count->add(1);
if (Cache::$data["info_level"] >= 1) {
$num = CQUtil::getRobotAlias($req["self_id"]);
$type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG" . $num . ":" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" . $num : "DISCUSS_MSG" . $num . ":" . $req["discuss_id"])) : strtoupper($req["post_type"]);
Console::put(Console::setColor(date("H:i:s"), "green") . Console::setColor(" [$in_count]" . $type, "lightlightblue") . Console::setColor(" " . $req["user_id"], "yellow") . Console::setColor(" > ", "gray") . $req["message"]);
}
CQUtil::updateMsg();//更新消息速度
//传入消息处理的逻辑
$c = new CQBot($this->getFramework(), 0, $req);
$c->execute();
$value = $c->endtime - $c->starttime;
Console::debug("Using time: " . $value);
}
}

View File

@ -1,29 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 11:06 AM
*/
class MetaEvent extends Event
{
public function __construct($req) {
switch ($req["meta_event_type"]) {
case "lifecycle":
//插件生命周期事件
switch ($req["sub_type"]) {
case "enable":
Console::info("机器人" . $req["self_id"] . "的HTTP插件" . Console::setColor("启动", "green") . "运行");
break;
case "disable":
Console::info("机器人" . $req["self_id"] . "的HTTP插件" . Console::setColor("停止", "red") . "运行");
break;
}
break;
case "heartbeat":
//处理心跳包中的status事件目前炸毛不需要。
break;
}
}
}

View File

@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:36 AM
*/
class NoticeEvent extends Event
{
public function __construct($req) {
foreach(Cache::get("mods") as $k => $v){
if(in_array("onNotice", get_class_methods($v))){
/** @var ModBase $v */
$v::onNotice($req);
}
}
}
}

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