mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 20:54:52 +08:00
initial commit
This commit is contained in:
parent
517082d159
commit
769b87af6a
17
.gitignore
vendored
Executable file → Normal file
17
.gitignore
vendored
Executable file → Normal 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
21
LICENSE
@ -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.
|
||||
144
README.md
Executable file → Normal file
144
README.md
Executable file → Normal file
@ -1,141 +1,41 @@
|
||||
# CQBot-swoole
|
||||
|
||||
## 此分支停止维护!
|
||||
# zhamao-framework
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
|
||||
|
||||
一个异步、多平台兼容的**聊天机器人**框架。
|
||||
一个异步、多平台兼容的 **聊天机器人** 框架。
|
||||
|
||||
**当前正在重构,框架可能会有很大的变化,旧框架将创建old分支,重构完成后新款框架为master分支。**
|
||||
**框架同时将更名为zhamao-framework(炸毛框架),与旧版本兼容性不并存,届时需要根据文档进行模块的升级。**
|
||||

|
||||
|
||||
## 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
|
||||
# 安装PHP(ubuntu/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
|
||||
|
||||
#安装PHP(CentOS)
|
||||
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 插件群里提问,当然更好的话可以加作者 QQ(627577391)或提交 Issue 进行疑难解答。
|
||||
|
||||
欢迎随时在HTTP-API插件群提问,当然更好的话可以加作者QQ(627577391)或提交issue进行疑难解答。
|
||||
|
||||
本项目在有更新内容时,请及时关注GitHub的动态,更新前请将自己的**模块**代码做好备份。
|
||||
本项目在更行内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
44
bin/start
Executable file
44
bin/start
Executable 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
3
bin/update-composer
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
|
||||
composer update
|
||||
@ -1,19 +1,31 @@
|
||||
{
|
||||
"name": "zhamao/framework",
|
||||
"description": "high-performance intelligent assistant",
|
||||
"minimum-stability": "stable",
|
||||
"license": "MIT",
|
||||
"license": "proprietary",
|
||||
"authors": [
|
||||
{
|
||||
"name": "whale",
|
||||
"email": "crazysnowcc@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "swift",
|
||||
"email": "hugo_swift@yahoo.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"ext-mbstring": "^7.1",
|
||||
"swoole/ide-helper": "@dev",
|
||||
"ext-mbstring": "*",
|
||||
"swlib/saber": "^1.0",
|
||||
"doctrine/annotations": "^1.8",
|
||||
"ext-json": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-mysqli": "*",
|
||||
"ext-dom": "20031129",
|
||||
"ext-gd": "^7.1",
|
||||
"ext-curl": "^7.1",
|
||||
"ext-openssl": "^7.1"
|
||||
"ext-posix": "*",
|
||||
"ext-ctype": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"eaglewu/swoole-ide-helper": "dev-master"
|
||||
"repositories": {
|
||||
"packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.aliyun.com/composer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
395
composer.lock
generated
Normal file
395
composer.lock
generated
Normal 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
64
config/global.php
Normal 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;
|
||||
20
cqbot.json
20
cqbot.json
@ -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
|
||||
}
|
||||
@ -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
11
resources/html/404.html
Normal 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
BIN
resources/images/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 385 KiB |
19
src/Custom/Annotation/Example.php
Normal file
19
src/Custom/Annotation/Example.php
Normal 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;
|
||||
}
|
||||
14
src/Custom/Connection/CustomConnection.php
Normal file
14
src/Custom/Connection/CustomConnection.php
Normal 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
212
src/Framework/Console.php
Executable 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);
|
||||
}
|
||||
}
|
||||
106
src/Framework/FrameworkLoader.php
Normal file
106
src/Framework/FrameworkLoader.php
Normal 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);
|
||||
}
|
||||
}
|
||||
33
src/Framework/GlobalConfig.php
Normal file
33
src/Framework/GlobalConfig.php
Normal 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;
|
||||
}
|
||||
}
|
||||
10
src/Framework/InfoLevel.php
Normal file
10
src/Framework/InfoLevel.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Framework;
|
||||
|
||||
|
||||
class InfoLevel
|
||||
{
|
||||
const INFO = 0;
|
||||
}
|
||||
18
src/Framework/Logger.php
Normal file
18
src/Framework/Logger.php
Normal 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);
|
||||
}
|
||||
}
|
||||
264
src/Framework/RemoteShell.php
Normal file
264
src/Framework/RemoteShell.php
Normal 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
95
src/Framework/ZMBuf.php
Executable 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
src/Framework/global_functions.php
Normal file
146
src/Framework/global_functions.php
Normal 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;
|
||||
}
|
||||
34
src/Module/Example/Hello.php
Normal file
34
src/Module/Example/Hello.php
Normal 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 "你好啊,我是由炸毛框架构建的机器人!";
|
||||
}
|
||||
}
|
||||
45
src/Module/TestMod/CQTest.php
Normal file
45
src/Module/TestMod/CQTest.php
Normal 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(){
|
||||
|
||||
}
|
||||
}
|
||||
28
src/Module/TestMod/Hola.php
Normal file
28
src/Module/TestMod/Hola.php
Normal 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();
|
||||
}
|
||||
}
|
||||
29
src/Scheduler/MessageEvent.php
Normal file
29
src/Scheduler/MessageEvent.php
Normal 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
140
src/Scheduler/Scheduler.php
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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("&", "&", $str);
|
||||
$str = str_replace("[", "[", $str);
|
||||
$str = str_replace("]", "]", $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("&", "&", $msg);
|
||||
$msg = str_replace("[", "[", $msg);
|
||||
$msg = str_replace("]", "]", $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
249
src/ZM/API/CQAPI.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/ZM/Annotation/AnnotationBase.php
Normal file
29
src/ZM/Annotation/AnnotationBase.php
Normal 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;
|
||||
}
|
||||
}
|
||||
223
src/ZM/Annotation/AnnotationParser.php
Normal file
223
src/ZM/Annotation/AnnotationParser.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/ZM/Annotation/CQ/CQAfter.php
Normal file
22
src/ZM/Annotation/CQ/CQAfter.php
Normal 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;
|
||||
}
|
||||
42
src/ZM/Annotation/CQ/CQBefore.php
Normal file
42
src/ZM/Annotation/CQ/CQBefore.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
35
src/ZM/Annotation/CQ/CQCommand.php
Normal file
35
src/ZM/Annotation/CQ/CQCommand.php
Normal 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; }
|
||||
|
||||
}
|
||||
41
src/ZM/Annotation/CQ/CQMessage.php
Normal file
41
src/ZM/Annotation/CQ/CQMessage.php
Normal 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;
|
||||
}
|
||||
}
|
||||
40
src/ZM/Annotation/CQ/CQMetaEvent.php
Normal file
40
src/ZM/Annotation/CQ/CQMetaEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
42
src/ZM/Annotation/CQ/CQNotice.php
Normal file
42
src/ZM/Annotation/CQ/CQNotice.php
Normal 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;
|
||||
}
|
||||
}
|
||||
42
src/ZM/Annotation/CQ/CQRequest.php
Normal file
42
src/ZM/Annotation/CQ/CQRequest.php
Normal 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;
|
||||
}
|
||||
}
|
||||
23
src/ZM/Annotation/Http/Controller.php
Normal file
23
src/ZM/Annotation/Http/Controller.php
Normal 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 = '';
|
||||
}
|
||||
39
src/ZM/Annotation/Http/RequestMapping.php
Normal file
39
src/ZM/Annotation/Http/RequestMapping.php
Normal 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 = [];
|
||||
}
|
||||
30
src/ZM/Annotation/Http/RequestMethod.php
Normal file
30
src/ZM/Annotation/Http/RequestMethod.php
Normal 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';
|
||||
}
|
||||
10
src/ZM/Annotation/Interfaces/CustomAnnotation.php
Normal file
10
src/ZM/Annotation/Interfaces/CustomAnnotation.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Interfaces;
|
||||
|
||||
|
||||
interface CustomAnnotation
|
||||
{
|
||||
|
||||
}
|
||||
12
src/ZM/Annotation/Interfaces/Level.php
Normal file
12
src/ZM/Annotation/Interfaces/Level.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Interfaces;
|
||||
|
||||
|
||||
interface Level
|
||||
{
|
||||
public function getLevel();
|
||||
|
||||
public function setLevel(int $level);
|
||||
}
|
||||
10
src/ZM/Annotation/Interfaces/Rule.php
Normal file
10
src/ZM/Annotation/Interfaces/Rule.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Interfaces;
|
||||
|
||||
|
||||
interface Rule
|
||||
{
|
||||
public function getRule();
|
||||
}
|
||||
103
src/ZM/Annotation/MappingNode.php
Normal file
103
src/ZM/Annotation/MappingNode.php
Normal 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;
|
||||
}
|
||||
}
|
||||
18
src/ZM/Annotation/Module/Closed.php
Normal file
18
src/ZM/Annotation/Module/Closed.php
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
25
src/ZM/Annotation/Module/SaveBuffer.php
Normal file
25
src/ZM/Annotation/Module/SaveBuffer.php
Normal 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;
|
||||
}
|
||||
76
src/ZM/Annotation/Swoole/SwooleEventAfter.php
Normal file
76
src/ZM/Annotation/Swoole/SwooleEventAfter.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
76
src/ZM/Annotation/Swoole/SwooleEventAt.php
Normal file
76
src/ZM/Annotation/Swoole/SwooleEventAt.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
23
src/ZM/Connection/CQConnection.php
Normal file
23
src/ZM/Connection/CQConnection.php
Normal 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";
|
||||
}
|
||||
}
|
||||
73
src/ZM/Connection/ConnectionManager.php
Normal file
73
src/ZM/Connection/ConnectionManager.php
Normal 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;
|
||||
}
|
||||
}
|
||||
13
src/ZM/Connection/ProxyConnection.php
Normal file
13
src/ZM/Connection/ProxyConnection.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class ProxyConnection extends WSConnection
|
||||
{
|
||||
|
||||
public function getType() {
|
||||
return "proxy";
|
||||
}
|
||||
}
|
||||
13
src/ZM/Connection/UnknownConnection.php
Normal file
13
src/ZM/Connection/UnknownConnection.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class UnknownConnection extends WSConnection
|
||||
{
|
||||
|
||||
public function getType() {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
10
src/ZM/Connection/WCConnection.php
Normal file
10
src/ZM/Connection/WCConnection.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Connection;
|
||||
|
||||
|
||||
class WCConnection extends WSConnection
|
||||
{
|
||||
public function getType() { return "wc"; }
|
||||
}
|
||||
52
src/ZM/Connection/WSConnection.php
Normal file
52
src/ZM/Connection/WSConnection.php
Normal 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
120
src/ZM/DB/DB.php
Normal 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
28
src/ZM/DB/DeleteBody.php
Normal 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
28
src/ZM/DB/InsertBody.php
Normal 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
88
src/ZM/DB/SelectBody.php
Normal 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
79
src/ZM/DB/Table.php
Normal 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
50
src/ZM/DB/UpdateBody.php
Normal 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
33
src/ZM/DB/WhereBody.php
Normal 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];
|
||||
}
|
||||
}
|
||||
18
src/ZM/DBCache/CourseCache.php
Normal file
18
src/ZM/DBCache/CourseCache.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
class CourseCache implements DBCache
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
public static function reset() {
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
10
src/ZM/DBCache/DBCache.php
Normal file
10
src/ZM/DBCache/DBCache.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
interface DBCache
|
||||
{
|
||||
public static function reset();
|
||||
}
|
||||
15
src/ZM/DBCache/DBCacheManager.php
Normal file
15
src/ZM/DBCache/DBCacheManager.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
class DBCacheManager
|
||||
{
|
||||
public static function freeAllCache(){
|
||||
DHUerCache::reset();
|
||||
UserCache::reset();
|
||||
GroupCache::reset();
|
||||
CourseCache::reset();
|
||||
}
|
||||
}
|
||||
17
src/ZM/DBCache/DHUerCache.php
Normal file
17
src/ZM/DBCache/DHUerCache.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
class DHUerCache implements DBCache
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
public static function reset() {
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
18
src/ZM/DBCache/GroupCache.php
Normal file
18
src/ZM/DBCache/GroupCache.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
class GroupCache implements DBCache
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
public static function reset() {
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
18
src/ZM/DBCache/UserCache.php
Normal file
18
src/ZM/DBCache/UserCache.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\DBCache;
|
||||
|
||||
|
||||
class UserCache implements DBCache
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private static $data;
|
||||
|
||||
public static function reset() {
|
||||
self::$data = [];
|
||||
}
|
||||
}
|
||||
149
src/ZM/Event/CQ/MessageEvent.php
Normal file
149
src/ZM/Event/CQ/MessageEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
71
src/ZM/Event/CQ/MetaEvent.php
Normal file
71
src/ZM/Event/CQ/MetaEvent.php
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
88
src/ZM/Event/CQ/NoticeEvent.php
Normal file
88
src/ZM/Event/CQ/NoticeEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
89
src/ZM/Event/CQ/RequestEvent.php
Normal file
89
src/ZM/Event/CQ/RequestEvent.php
Normal 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
11
src/ZM/Event/Event.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
|
||||
interface Event
|
||||
{
|
||||
const SWOOLE = 1;
|
||||
const CQ = 2;
|
||||
}
|
||||
115
src/ZM/Event/EventHandler.php
Normal file
115
src/ZM/Event/EventHandler.php
Normal 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"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
94
src/ZM/Event/Swoole/MessageEvent.php
Normal file
94
src/ZM/Event/Swoole/MessageEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
132
src/ZM/Event/Swoole/RequestEvent.php
Normal file
132
src/ZM/Event/Swoole/RequestEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
20
src/ZM/Event/Swoole/SwooleEvent.php
Normal file
20
src/ZM/Event/Swoole/SwooleEvent.php
Normal 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();
|
||||
}
|
||||
61
src/ZM/Event/Swoole/WSCloseEvent.php
Normal file
61
src/ZM/Event/Swoole/WSCloseEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
93
src/ZM/Event/Swoole/WSOpenEvent.php
Normal file
93
src/ZM/Event/Swoole/WSOpenEvent.php
Normal 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;
|
||||
}
|
||||
}
|
||||
137
src/ZM/Event/Swoole/WorkerStartEvent.php
Normal file
137
src/ZM/Event/Swoole/WorkerStartEvent.php
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
12
src/ZM/Exception/DbException.php
Normal file
12
src/ZM/Exception/DbException.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class DbException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
12
src/ZM/Exception/InvalidArgumentException.php
Normal file
12
src/ZM/Exception/InvalidArgumentException.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidArgumentException extends Exception
|
||||
{
|
||||
|
||||
}
|
||||
20
src/ZM/Exception/WaitTimeoutException.php
Normal file
20
src/ZM/Exception/WaitTimeoutException.php
Normal 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
229
src/ZM/Http/Response.php
Normal 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
165
src/ZM/ModBase.php
Normal 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
20
src/ZM/ModHandleType.php
Normal 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;
|
||||
}
|
||||
46
src/ZM/Utils/DataProvider.php
Normal file
46
src/ZM/Utils/DataProvider.php
Normal 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
101
src/ZM/Utils/SQLPool.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/ZM/Utils/ScheduleManager.php
Normal file
10
src/ZM/Utils/ScheduleManager.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
class ScheduleManager
|
||||
{
|
||||
//TODO: 写framework部分的schedule控制代码
|
||||
}
|
||||
71
src/ZM/Utils/ZMUtil.php
Normal file
71
src/ZM/Utils/ZMUtil.php
Normal 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];
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user