Compare commits

...

14 Commits
2.0.3 ... 2.1.4

Author SHA1 Message Date
jerry
076339baec move warning to framework 2021-01-03 18:16:35 +08:00
jerry
50026be73d fix a bug 2021-01-03 18:13:44 +08:00
jerry
a1ad634926 fix a bug 2021-01-02 20:00:33 +08:00
jerry
0a5defaf29 fix a bug 2021-01-02 19:44:19 +08:00
jerry
557efc47a8 update to 2.1.4 version 2021-01-02 19:40:13 +08:00
jerry
c566f940e0 add custom app version 2021-01-02 18:10:20 +08:00
jerry
381062c6c5 update to 2.1.3 version
fix an AnnotationParser bug which is stupid
2021-01-02 16:36:25 +08:00
jerry
b31876025e update composer.json 2021-01-02 13:35:45 +08:00
jerry
ae8b0acdaa update to 2.1.2 version 2021-01-02 13:35:24 +08:00
jerry
20ca3e7416 update docs 2021-01-02 13:28:26 +08:00
jerry
a1bfc031b8 update to 2.1.1 version 2021-01-02 13:20:16 +08:00
jerry
8a6f8f54a5 update composer.json path 2021-01-02 13:19:39 +08:00
jerry
19f0bffcd8 update composer.json 2021-01-02 13:16:22 +08:00
jerry
6337b626d6 update to 2.1.0 version
add @OnCloseEvent, @OnOpenEvent, @OnMessageEvent, @OnRequestEvent
update EventDispatcher.php and change it to status-based schema
fix @CQBefore bugs
2021-01-02 13:15:50 +08:00
42 changed files with 396 additions and 175 deletions

View File

@@ -3,8 +3,12 @@
"description": "High performance QQ robot and web server development framework",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "2.0.3",
"extra": [],
"version": "2.1.4",
"extra": {
"exclude_annotate": [
"src/ZM"
]
},
"authors": [
{
"name": "whale",

View File

@@ -4,7 +4,7 @@
## Swoole\Http\Request
此类是 Swoole 内部的一个类,一般在收到 HTTP 请求时,在 `@RequestMapping``@OnSwooleEvent("request")` 两个注解下可用,用作获取 GET、POST参数上传到后端的文件、Cookies 等。详见 [Swoole 文档 - Request](http://wiki.swoole.com/#/http_server?id=httprequest) 。
此类是 Swoole 内部的一个类,一般在收到 HTTP 请求时,在 `@RequestMapping``@OnRequestEvent()` 两个注解下可用,用作获取 GET、POST参数上传到后端的文件、Cookies 等。详见 [Swoole 文档 - Request](http://wiki.swoole.com/#/http_server?id=httprequest) 。
### 属性

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -26,19 +26,19 @@ public function hello() {
获取 Swoole WebSocker Server 对象。此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server)。
可以使用的事件:`@OnSwooleEvent("message")``@OnSwooleEvent("open")``@OnSwooleEvent("close")``@OnStart()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
可以使用的事件:`@OnMessageEvent()``@OnOpenEvent()``@OnCloseEvent()``@OnStart()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。
## getFrame() - 获取 WS 数据帧
获取 `\Swoole\Websocket\Frame` 对象,此对象是 Swoole 的对象,详情见 [Swoole 文档](https://wiki.swoole.com/#/websocket_server?id=swoolewebsocketframe)。
可以使用的事件:`@OnSwooleEvent("message")` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等,
可以使用的事件:`@OnMessageEvent()` 以及所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等,
## getFd() - 返回 fd 值
获取当前连入 Swoole 服务器的连接文件描述符 ID。返回 int。一般代表连接号可用来绑定对应链接。
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnSwooleEvent("open")``@OnSwooleEvent("close")`
可以使用的事件:所有 **getFrame()** 可以使用的,`@OnOpenEvent()``@OnCloseEvent()`
!!! tip "提示"
@@ -92,13 +92,13 @@ public function onMessage() {
返回 `\Swoole\Http\Request` 对象,可在 `@RequestMapping` 中使用,获取 Cookie请求头GET 参数什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httprequest)。
可以使用的事件:`@RequestMapping()``@OnSwooleEvent("request")``@OnSwooleEvent("open")`
可以使用的事件:`@RequestMapping()``@OnRequestEvent()``@OnOpenEvent()`
## getResponse() - HTTP 响应对象
返回 `\Swoole\Http\Response` 对象的增强版,可在 HTTP 请求相关的事件中使用,返回内容和设置 Cookie 什么的。[Swoole 文档](https://wiki.swoole.com/#/http_server?id=httpresponse)。
可以使用的事件:`@RequestMapping()``@OnSwooleEvent("request")`
可以使用的事件:`@RequestMapping()``@OnRequestEvent()`
下面是使用以上两个功能的组合示例:
@@ -124,7 +124,7 @@ public function ping() {
## getRobot() - 获取机器人 API 对象
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](机器人API.md)。
返回当前上下文关联的机器人 API 调用对象 [ZMRobot](robot-api.md)。
可以使用的事件:所有 HTTP API 发来的事件:`@CQCommand()``@CQMessage()` 等。

View File

@@ -4,7 +4,7 @@
## OnSwooleEvent()
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接HTTP 请求到达等。
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。请尽量使用上面几个新的注解。
### 属性

View File

@@ -1,8 +1,34 @@
# 更新日志v2 版本)
## v2.1.3
> 更新时间2021.1.2
- 修复:注解解析器在某种特殊情况下导致的 bug
## v2.1.2
> 更新时间2021.1.2
- 修复:引入包模式启动时会导致的满屏报错
## v2.1.1
> 更新时间2021.1.2
- 修复:自定义加载注解选定 composer.json 文件错误的 bug
## v2.1.0
> 更新时间2021.1.2
- 新增:`@OnOpenEvent``@OnCloseEvent``@OnMessageEvent``@OnRequestEvent`
- 优化事件分发器,修复一些事件分发过程中的 bug
- 修复 `@CQBefore` 事件的 bug
## v2.0.3
> 更新事件2020.12.31
> 更新时间2020.12.31
- 修复CQBefore 注解事件在 level 低于 200 时无法调用的 bug
- 修复CQMetaEvent 注解事件调用时报错的 bug

View File

@@ -52,26 +52,26 @@ copyright: 'Copyright © 2019 - 2020 CrazyBot Team    &n
nav:
- 指南:
- 介绍: index.md
- 安装框架: guide/安装.md
- 快速上手(机器人篇): guide/快速上手-机器人.md
- 快速上手HTTP篇: guide/快速上手-http.md
- 选择聊天机器人实例: guide/OneBot实例.md
- 基本配置: guide/基本配置.md
- 编写模块: guide/编写模块.md
- 注册事件响应: guide/注册事件响应.md
- 安装框架: guide/installation.md
- 快速上手(机器人篇): guide/quickstart-robot.md
- 快速上手HTTP篇: guide/quickstart-http.md
- 选择聊天机器人实例: guide/onebot-choose.md
- 基本配置: guide/basic-config.md
- 编写模块: guide/write-module.md
- 注册事件响应: guide/register-event.md
- 事件和注解:
- 事件和注解: event/index.md
- 机器人注解事件: event/机器人注解事件.md
- HTTP 路由注解事件: event/路由注解事件.md
- 框架核心注解事件: event/框架注解事件.md
- 中间件注解: event/中间件.md
- 自定义注解: event/自定义注解.md
- 事件分发器: event/事件分发器.md
- 机器人注解事件: event/robot-annotations.md
- HTTP 路由注解事件: event/route-annotations.md
- 框架核心注解事件: event/framework-annotations.md
- 中间件注解: event/middleware.md
- 自定义注解: event/custom-annotations.md
- 事件分发器: event/event-dispatcher.md
- 框架组件:
- 框架组件: component/index.md
- 机器人 API: component/机器人API.md
- CQ 码(多媒体消息): component/CQ码.md
- 上下文: component/上下文.md
- 机器人 API: component/robot-api.md
- CQ 码(多媒体消息): component/cqcode.md
- 上下文: component/context.md
- 进阶开发:
- 进阶开发: advanced/index.md
- 从 v1 升级: advanced/to-v2.md

View File

@@ -1,4 +1,4 @@
<?php
<?php #plain
//这里写你的全局函数
function pgo(callable $func, $name = "default") {

View File

@@ -3,13 +3,14 @@
namespace Module\Example;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\RequestMapping;
use ZM\Event\EventDispatcher;
use ZM\Store\Redis\ZMRedis;
use ZM\Utils\ZMUtil;
/**
@@ -19,29 +20,6 @@ use ZM\Utils\ZMUtil;
*/
class Hello
{
/**
* 一个简单的redis连接池使用demo将下方user_id改为你自己的QQ号即可(为了不被不法分子利用)
* @CQCommand("redis_test",user_id=627577391)
*/
public function testCase() {
$a = new ZMRedis();
$redis = $a->get();
$r1 = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想设置的操作[r/w]");
switch ($r1) {
case "r":
$k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想读取的键名");
$result = $redis->get($k);
ctx()->reply("结果:" . $result);
break;
case "w":
$k = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的键名");
$v = ctx()->getArgs(ZM_MATCH_FIRST, "请说出你想写入的字符串");
$result = $redis->set($k, $v);
ctx()->reply("结果:" . ($result ? "成功" : "失败"));
break;
}
}
/**
* 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ
* @CQCommand(".reload",user_id=627577391)
@@ -116,7 +94,7 @@ class Hello
/**
* 在机器人连接后向终端输出信息
* @OnSwooleEvent("open",rule="connectIsQQ()")
* @OnOpenEvent("qq")
* @param $conn
*/
public function onConnect(ConnectionObject $conn) {
@@ -125,7 +103,7 @@ class Hello
/**
* 在机器人断开连接后向终端输出信息
* @OnSwooleEvent("close",rule="connectIsQQ()")
* @OnCloseEvent("qq")
* @param ConnectionObject $conn
*/
public function onDisconnect(ConnectionObject $conn) {
@@ -134,7 +112,7 @@ class Hello
/**
* 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰
* @OnSwooleEvent("request",rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
* @OnRequestEvent(rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200)
*/
public function onRequest() {
EventDispatcher::interrupt();
@@ -142,7 +120,7 @@ class Hello
/**
* 框架会默认关闭未知的WebSocket链接因为这个绑定的事件你可以根据你自己的需求进行修改
* @OnSwooleEvent(type="open",rule="connectIsDefault()")
* @OnOpenEvent("default")
*/
public function closeUnknownConn() {
Console::info("Unknown connection , I will close it.");

View File

@@ -2,6 +2,7 @@
namespace Module\Middleware;
use Exception;
use ZM\Annotation\Http\HandleAfter;
use ZM\Annotation\Http\HandleBefore;
use ZM\Annotation\Http\HandleException;
@@ -37,8 +38,11 @@ class TimerMiddleware implements MiddlewareInterface
/**
* @HandleException(\Exception::class)
* @param Exception $e
* @throws Exception
*/
public function onException() {
public function onException(Exception $e) {
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
throw $e;
}
}

View File

@@ -33,7 +33,7 @@ class AnnotationParser
*/
public function __construct() {
$this->start_time = microtime(true);
$this->loadAnnotationClasses();
//$this->loadAnnotationClasses();
$this->req_mapping[0] = [
'id' => 0,
'pid' => -1,
@@ -88,7 +88,7 @@ class AnnotationParser
//预处理1将适用于每一个函数的注解到类注解重新注解到每个函数下面
if ($vs instanceof ErgodicAnnotation) {
foreach ($this->annotation_map[$v]["methods"] as $method) {
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
$copy = clone $vs;
$copy->method = $method->getName();
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
@@ -100,14 +100,14 @@ class AnnotationParser
unset($this->annotation_map[$v]);
continue 2;
} elseif ($vs instanceof MiddlewareClass) {
Console::verbose("正在注册中间件 " . $reflection_class->getName());
Console::debug("正在注册中间件 " . $reflection_class->getName());
$rs = $this->registerMiddleware($vs, $reflection_class);
$this->middlewares[$rs["name"]] = $rs;
}
}
//预处理3处理每个函数上面的特殊注解就是需要操作一些东西的
foreach ($this->annotation_map[$v]["methods_annotations"] as $method_name => $methods_annotations) {
foreach (($this->annotation_map[$v]["methods_annotations"] ?? []) as $method_name => $methods_annotations) {
foreach ($methods_annotations as $method_anno) {
/** @var AnnotationBase $method_anno */
$method_anno->class = $v;
@@ -135,11 +135,11 @@ class AnnotationParser
public function generateAnnotationEvents() {
$o = [];
foreach ($this->annotation_map as $module => $obj) {
foreach ($obj["class_annotations"] as $class_annotation) {
foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
if ($class_annotation instanceof ErgodicAnnotation) continue;
else $o[get_class($class_annotation)][] = $class_annotation;
}
foreach ($obj["methods_annotations"] as $method_name => $methods_annotations) {
foreach (($obj["methods_annotations"] ?? []) as $method_name => $methods_annotations) {
foreach ($methods_annotations as $annotation) {
$o[get_class($annotation)][] = $annotation;
}
@@ -297,7 +297,7 @@ class AnnotationParser
return $result;
}
private function sortByLevel(&$events, string $class_name, $prefix = "") {
public function sortByLevel(&$events, string $class_name, $prefix = "") {
if (is_a($class_name, Level::class, true)) {
$class_name .= $prefix;
usort($events[$class_name], function ($a, $b) {

View File

@@ -0,0 +1,22 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\Interfaces\Rule;
/**
* @Annotation
* @Target("METHOD")
* Class OnCloseEvent
* @package ZM\Annotation\Swoole
*/
class OnCloseEvent extends OnSwooleEventBase
{
/**
* @var string
*/
public $connect_type = "default";
}

View File

@@ -0,0 +1,21 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @Annotation
* @Target("METHOD")
* Class OnMessageEvent
* @package ZM\Annotation\Swoole
*/
class OnMessageEvent extends OnSwooleEventBase
{
/**
* @var string
*/
public $connect_type = "default";
}

View File

@@ -0,0 +1,21 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* @Annotation
* @Target("METHOD")
* Class OnOpenEvent
* @package ZM\Annotation\Swoole
*/
class OnOpenEvent extends OnSwooleEventBase
{
/**
* @var string
*/
public $connect_type = "default";
}

View File

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

View File

@@ -10,7 +10,7 @@ use ZM\Annotation\AnnotationBase;
* Class OnWorkerStart
* @package ZM\Annotation\Swoole
* @Annotation
* @Target("ALL")
* @Target("METHOD")
*/
class OnStart extends AnnotationBase
{

View File

@@ -5,17 +5,14 @@ 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 OnSwooleEvent
* @Annotation
* @Target("ALL")
* @Target("METHOD")
* @package ZM\Annotation\Swoole
*/
class OnSwooleEvent extends AnnotationBase implements Rule, Level
class OnSwooleEvent extends OnSwooleEventBase
{
/**
* @var string
@@ -23,14 +20,6 @@ class OnSwooleEvent extends AnnotationBase implements Rule, Level
*/
public $type;
/** @var string */
public $rule = "";
/** @var int */
public $level = 20;
public $callback = null;
/**
* @return string
*/
@@ -44,33 +33,4 @@ class OnSwooleEvent extends AnnotationBase implements Rule, Level
public function setType(string $type) {
$this->type = $type;
}
/**
* @return string
*/
public function getRule(): string {
return $this->rule;
}
/**
* @param string $rule
*/
public function setRule(string $rule) {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level) {
$this->level = $level;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace ZM\Annotation\Swoole;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Interfaces\Rule;
abstract class OnSwooleEventBase extends AnnotationBase implements Level, Rule
{
/**
* @var string
*/
public $rule = "";
/**
* @var int
*/
public $level = 20;
/**
* @return string
*/
public function getRule(): string {
return $this->rule !== "" ? $this->rule : true;
}
/**
* @param string $rule
*/
public function setRule(string $rule) {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level) {
$this->level = $level;
}
}

View File

@@ -5,8 +5,8 @@ namespace ZM\Event;
use Doctrine\Common\Annotations\AnnotationException;
use Error;
use Exception;
use ZM\Annotation\AnnotationBase;
use ZM\Console\Console;
use ZM\Exception\InterruptException;
use ZM\Exception\ZMException;
@@ -17,6 +17,12 @@ use ZM\Utils\ZMUtil;
class EventDispatcher
{
const STATUS_NORMAL = 0; //正常结束
const STATUS_INTERRUPTED = 1; //被interrupt了不管在什么地方
const STATUS_EXCEPTION = 2; //执行过程中抛出了异常
const STATUS_BEFORE_FAILED = 3; //中间件HandleBefore返回了false所以不执行此方法
const STATUS_RULE_FAILED = 4; //判断事件执行的规则函数判定为false所以不执行此方法
/** @var string */
private $class;
/** @var null|callable */
@@ -27,6 +33,10 @@ class EventDispatcher
private $log = false;
/** @var int */
private $eid = 0;
/** @var int */
public $status = self::STATUS_NORMAL;
/** @var mixed */
public $store = null;
/**
* @param null $return_var
@@ -74,41 +84,53 @@ class EventDispatcher
return $this;
}
/**
* @param mixed ...$params
* @throws Exception
* @throws InterruptException
*/
public function dispatchEvents(...$params) {
try {
foreach ((EventManager::$events[$this->class] ?? []) as $v) {
$result = $this->dispatchEvent($v, $this->rule, ...$params);
$this->dispatchEvent($v, $this->rule, ...$params);
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。");
if ($result !== false && is_callable($this->return_func)) {
if($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) continue;
if (is_callable($this->return_func) && $this->status === self::STATUS_NORMAL) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ...");
($this->return_func)($result);
($this->return_func)($this->store);
}
}
return true;
if($this->status === self::STATUS_RULE_FAILED) $this->status = self::STATUS_NORMAL;
} catch (InterruptException $e) {
return $e->return_var;
} catch (AnnotationException $e) {
return false;
$this->store = $e->return_var;
$this->status = self::STATUS_INTERRUPTED;
} catch (Exception $e) {
$this->status = self::STATUS_EXCEPTION;
throw $e;
} catch (Error $e) {
$this->status = self::STATUS_EXCEPTION;
throw $e;
}
}
/**
* @param AnnotationBase|null $v
* @param mixed $v
* @param null $rule_func
* @param mixed ...$params
* @throws AnnotationException
* @throws InterruptException
* @return mixed
* @return bool
*/
public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) {
public function dispatchEvent($v, $rule_func = null, ...$params) {
$q_c = $v->class;
$q_f = $v->method;
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在判断 " . $q_c . "::" . $q_f . " 方法下的 rule ...");
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在判断 " . $q_c . "::" . $q_f . " 方法下的 ruleFunc ...");
if ($rule_func !== null && !$rule_func($v)) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 判断为 false, 拒绝执行此方法。");
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 ruleFunc 判断为 false, 拒绝执行此方法。");
$this->status = self::STATUS_RULE_FAILED;
return false;
}
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 为真,继续执行方法本身 ...");
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 ruleFunc 为真,继续执行方法本身 ...");
if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
$middlewares = EventManager::$middleware_map[$q_c][$q_f];
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware" . implode(", ", $middlewares));
@@ -138,7 +160,7 @@ class EventDispatcher
try {
$q_o = ZMUtil::getModInstance($q_c);
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
$result = $q_o->$q_f(...$params);
$this->store = $q_o->$q_f(...$params);
} catch (Exception $e) {
if ($e instanceof InterruptException) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 检测到事件阻断调用,正在跳出事件分发器 ...");
@@ -166,13 +188,17 @@ class EventDispatcher
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 后置事件执行完毕!");
}
}
return $result;
$this->status = self::STATUS_NORMAL;
return true;
}
$this->status = self::STATUS_BEFORE_FAILED;
return false;
} else {
$q_o = ZMUtil::getModInstance($q_c);
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
return $q_o->$q_f(...$params);
$this->store = $q_o->$q_f(...$params);
$this->status = self::STATUS_NORMAL;
return true;
}
}
}

View File

@@ -22,6 +22,7 @@ class EventManager
public static function addEvent($event_name, ?AnnotationBase $event_obj) {
self::$events[$event_name][] = $event_obj;
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
}
public static function loadEventByParser(AnnotationParser $parser) {

View File

@@ -9,6 +9,7 @@ use Error;
use Exception;
use PDO;
use ReflectionException;
use Swoole\Coroutine;
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Event;
@@ -16,6 +17,10 @@ use Swoole\Process;
use Swoole\Timer;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnMessageEvent;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\Annotation\Swoole\OnStart;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Config\ZMConfig;
@@ -114,7 +119,7 @@ class ServerEventHandler
Console::debug("正在关闭 " . ($server->taskworker ? "Task" : "") . "Worker 进程 " . Console::setColor("#" . \server()->worker_id, "gold") . TermColor::frontColor256(59) . ", pid=" . posix_getpid());
server()->stop($worker_id);
});
unset(Context::$context[Co::getCid()]);
unset(Context::$context[Coroutine::getCid()]);
if ($server->taskworker === false) {
try {
register_shutdown_function(function () use ($server) {
@@ -194,7 +199,7 @@ class ServerEventHandler
// 开箱即用的Redis
$redis = ZMConfig::get("global", "redis_config");
if($redis !== null && $redis["host"] != "") {
if ($redis !== null && $redis["host"] != "") {
if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n");
else ZMRedisPool::init($redis);
}
@@ -211,7 +216,8 @@ class ServerEventHandler
return server()->worker_id === $v->worker_id || $v->worker_id === -1;
});
$dispatcher->dispatchEvents($server, $worker_id);
Console::debug("@OnStart 执行完毕");
if ($dispatcher->status === EventDispatcher::STATUS_NORMAL) Console::debug("@OnStart 执行完毕");
else Console::warning("@OnStart 执行异常!");
} catch (Exception $e) {
Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
@@ -250,10 +256,16 @@ class ServerEventHandler
*/
public function onMessage($server, Frame $frame) {
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET);
unset(Context::$context[Co::getCid()]);
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd . ": " . TermColor::ITALIC . $frame->data . TermColor::RESET);
unset(Context::$context[\Swoole\Coroutine::getCid()]);
$conn = ManagerGM::get($frame->fd);
set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]);
$dispatcher1 = new EventDispatcher(OnMessageEvent::class);
$dispatcher1->setRuleFunction(function ($v) {
return ctx()->getConnection()->getName() == $v->connect_type && eval("return " . $v->getRule() . ";");
});
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
@@ -268,6 +280,7 @@ class ServerEventHandler
});
try {
//$starttime = microtime(true);
$dispatcher1->dispatchEvents($conn);
$dispatcher->dispatchEvents($conn);
//Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!");
} catch (Exception $e) {
@@ -293,6 +306,11 @@ class ServerEventHandler
Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
set_coroutine_params(["request" => $request, "response" => $response]);
$dis1 = new EventDispatcher(OnRequestEvent::class);
$dis1->setRuleFunction(function ($v) {
return eval("return " . $v->getRule() . ";") ? true : false;
});
$dis = new EventDispatcher(OnSwooleEvent::class);
$dis->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
@@ -305,8 +323,9 @@ class ServerEventHandler
});
try {
$no_interrupt = $dis->dispatchEvents($request, $response);
if ($no_interrupt !== null) {
$dis1->dispatchEvents($request, $response);
$dis->dispatchEvents($request, $response);
if ($dis->status === EventDispatcher::STATUS_NORMAL && $dis1->status === EventDispatcher::STATUS_NORMAL) {
$result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params);
if ($result === true) {
ctx()->setCache("params", $params);
@@ -318,8 +337,8 @@ class ServerEventHandler
$div->request_method = $node["request_method"];
$div->class = $node["class"];
//Console::success("正在执行路由:".$node["method"]);
$r = $dispatcher->dispatchEvent($div, null, $params, $request, $response);
if (is_string($r) && !$response->isEnd()) $response->end($r);
$dispatcher->dispatchEvent($div, null, $params, $request, $response);
if (is_string($dispatcher->store) && !$response->isEnd()) $response->end($dispatcher->store);
}
}
if (!$response->isEnd()) {
@@ -337,7 +356,7 @@ class ServerEventHandler
else
$response->end("Internal server error.");
}
Console::error("Internal server exception (500), caused by " . get_class($e).": ".$e->getMessage());
Console::error("Internal server exception (500), caused by " . get_class($e) . ": " . $e->getMessage());
Console::log($e->getTraceAsString(), "gray");
} catch (Error $e) {
$response->status(500);
@@ -351,7 +370,7 @@ class ServerEventHandler
else
$response->end("Internal server error.");
}
Console::error("Internal server error (500), caused by " . get_class($e).": ".$e->getMessage());
Console::error("Internal server error (500), caused by " . get_class($e) . ": " . $e->getMessage());
Console::log($e->getTraceAsString(), "gray");
}
}
@@ -371,6 +390,11 @@ class ServerEventHandler
set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]);
$conn->setOption("connect_id", strval($request->header["x-self-id"] ?? ""));
$dispatcher1 = new EventDispatcher(OnOpenEvent::class);
$dispatcher1->setRuleFunction(function ($v) {
return ctx()->getConnection()->getName() == $v->connect_type && eval("return " . $v->getRule() . ";");
});
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
@@ -387,6 +411,7 @@ class ServerEventHandler
LightCacheInside::set("connect", "conn_fd", $request->fd);
}
}
$dispatcher1->dispatchEvents($conn);
$dispatcher->dispatchEvents($conn);
} catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
@@ -412,6 +437,10 @@ class ServerEventHandler
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]);
$dispatcher1 = new EventDispatcher(OnCloseEvent::class);
$dispatcher1->setRuleFunction(function ($v) {
return $v->connect_type == ctx()->getConnection()->getName() && eval("return " . $v->getRule() . ";");
});
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
@@ -429,6 +458,7 @@ class ServerEventHandler
LightCacheInside::set("connect", "conn_fd", -1);
}
}
$dispatcher1->dispatchEvents($conn);
$dispatcher->dispatchEvents($conn);
} catch (Exception $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
@@ -505,7 +535,16 @@ class ServerEventHandler
//加载各个模块的注解类,以及反射
Console::debug("检索Module中");
$parser = new AnnotationParser();
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/Module/", "Module");
$path = DataProvider::getWorkingDir() . "/src/";
$dir = scandir($path);
unset($dir[0], $dir[1]);
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($dir as $v) {
if (is_dir($path . "/" . $v) && isset($composer["autoload"]["psr-4"][$v . "\\"]) && !in_array($composer["autoload"]["psr-4"][$v . "\\"], $composer["extra"]["exclude_annotate"] ?? [])) {
Console::verbose("Add " . $v . " to register path");
$parser->addRegisterPath(DataProvider::getWorkingDir() . "/src/" . $v . "/", $v);
}
}
$parser->registerMods();
EventManager::loadEventByParser($parser); //加载事件
@@ -518,14 +557,14 @@ class ServerEventHandler
//加载插件
$plugins = ZMConfig::get("global", "modules") ?? [];
if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false];
if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false, "message_level" => 99999];
if ($plugins["onebot"]) {
$obj = new OnSwooleEvent();
$obj->class = QQBot::class;
$obj->method = 'handle';
$obj->type = 'message';
$obj->level = 99999;
$obj->level = $plugins["onebot"]["message_level"] ?? 99999;
$obj->rule = 'connectIsQQ()';
EventManager::addEvent(OnSwooleEvent::class, $obj);
if ($plugins["onebot"]["single_bot_mode"]) {
@@ -536,7 +575,6 @@ class ServerEventHandler
}
//TODO: 编写加载外部插件的方式
$this->loadExternalModules($plugins);
}
private function addWatcher($maindir, $fd) {
@@ -550,11 +588,4 @@ class ServerEventHandler
}
}
}
private function loadExternalModules($plugins) {
foreach ($plugins as $k => $v) {
if ($k == "onebot") continue;
}
}
}

View File

@@ -55,9 +55,9 @@ class Framework
ZMAtomic::init();
try {
$sw = ZMConfig::get("global");
if(!is_dir($sw["zm_data"])) mkdir($sw["zm_data"]);
if(!is_dir($sw["config_dir"])) mkdir($sw["config_dir"]);
if(!is_dir($sw["crash_dir"])) mkdir($sw["crash_dir"]);
if (!is_dir($sw["zm_data"])) mkdir($sw["zm_data"]);
if (!is_dir($sw["config_dir"])) mkdir($sw["config_dir"]);
if (!is_dir($sw["crash_dir"])) mkdir($sw["crash_dir"]);
ManagerGM::init(ZMConfig::get("global", "swoole")["max_connection"] ?? 2048, 0.5, [
[
"key" => "connect_id",
@@ -94,6 +94,7 @@ class Framework
"version" => ZM_VERSION,
"config" => $args["env"] === null ? 'global.php' : $args["env"]
];
if(APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
$out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"];
}
@@ -129,6 +130,40 @@ class Framework
LightCache::init($r);
LightCacheInside::init();
SpinLock::init($r["size"]);
set_error_handler(function ($error_no, $error_msg, $error_file, $error_line) {
switch ($error_no) {
case E_WARNING:
$level_tips = 'PHP Warning: ';
break;
case E_NOTICE:
$level_tips = 'PHP Notice: ';
break;
case E_DEPRECATED:
$level_tips = 'PHP Deprecated: ';
break;
case E_USER_ERROR:
$level_tips = 'User Error: ';
break;
case E_USER_WARNING:
$level_tips = 'User Warning: ';
break;
case E_USER_NOTICE:
$level_tips = 'User Notice: ';
break;
case E_USER_DEPRECATED:
$level_tips = 'User Deprecated: ';
break;
case E_STRICT:
$level_tips = 'PHP Strict: ';
break;
default:
$level_tips = 'Unkonw Type Error: ';
break;
} // do some handle
$error = $level_tips . $error_msg . ' in ' . $error_file . ' on ' . $error_line;
Console::warning($error); // 如果 return false 则错误会继续递交给 PHP 标准错误处理 /
return true;
}, E_ALL | E_STRICT);
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage());
@@ -174,11 +209,9 @@ class Framework
}
}
foreach ($event_list as $k => $v) {
self::$server->on($k, function (...$param) use ($v) {
$c = ZMUtil::getModInstance($v->class);
$m = $v->method;
$c->$m(...$param);
});
$c = ZMUtil::getModInstance($v->class);
$m = $v->method;
self::$server->on($k, function (...$param) use ($c, $m) { $c->$m(...$param); });
}
}

View File

@@ -94,7 +94,8 @@ class Response
*/
public function status($http_code, $reason = null) {
$this->status_code = $http_code;
return $this->response->status($http_code, $reason);
if (!$this->is_end) return $this->response->status($http_code, $reason);
else return false;
}
public function getStatusCode() {
@@ -107,7 +108,8 @@ class Response
* @return mixed
*/
public function setStatusCode($http_code, $reason = null) {
return $this->response->setStatusCode($http_code, $reason);
if (!$this->is_end) return $this->response->setStatusCode($http_code, $reason);
else return false;
}
/**
@@ -117,7 +119,8 @@ class Response
* @return mixed
*/
public function header($key, $value, $ucwords = null) {
return $this->response->header($key, $value, $ucwords);
if (!$this->is_end) return $this->response->header($key, $value, $ucwords);
else return false;
}
/**
@@ -127,7 +130,7 @@ class Response
* @return mixed
*/
public function setHeader($key, $value, $ucwords = null) {
return $this->response->setHeader($key, $value, $ucwords);
return !$this->is_end ? $this->response->setHeader($key, $value, $ucwords) : false;
}
/**
@@ -159,13 +162,17 @@ class Response
* @return mixed
*/
public function end($content = null) {
$this->is_end = true;
return $this->response->end($content);
if(!$this->is_end) {
$this->is_end = true;
return $this->response->end($content);
} else {
return false;
}
}
public function isEnd() { return $this->is_end; }
public function endWithStatus($status_code = 200, $content = null){
public function endWithStatus($status_code = 200, $content = null) {
$this->status($status_code);
$this->end($content);
}

View File

@@ -35,7 +35,10 @@ class QQBot
set_coroutine_params(["data" => $data]);
ctx()->setCache("level", 0);
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
$this->dispatchBeforeEvents($data); // >= 200 的level before在这里执行
if ($data["post_type"] != "meta_event") {
$r = $this->dispatchBeforeEvents($data); // before在这里执行元事件不执行before为减少不必要的调试日志
if ($r->store === "block") EventDispatcher::interrupt();
}
if (CoMessage::resumeByWS()) {
EventDispatcher::interrupt();
}
@@ -49,17 +52,27 @@ class QQBot
}
}
/**
* @param $data
* @return EventDispatcher
* @throws InterruptException
*/
public function dispatchBeforeEvents($data) {
$before = new EventDispatcher(CQBefore::class);
$before->setRuleFunction(function ($v) use ($data) {
return $v->cq_event == $data["post_type"];
});
$before->setReturnFunction(function ($result) {
if (!$result) EventDispatcher::interrupt();
if (!$result) EventDispatcher::interrupt("block");
});
$before->dispatchEvents($data);
return $before;
}
/**
* @param $data
* @throws InterruptException
*/
private function dispatchEvents($data) {
//Console::warning("最xia数据包".json_encode($data));
switch ($data["post_type"]) {
@@ -73,16 +86,15 @@ class QQBot
$word[$k] = trim($word[$k]);
}
}
//分发CQCommand事件
$dispatcher = new EventDispatcher(CQCommand::class);
$dispatcher->setRuleFunction(function (CQCommand $v) use ($word) {
if(array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) return false;
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) return false;
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ctx()->getUserId())) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (ctx()->getGroupId() ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == ctx()->getMessageType()))
) {
if(($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
array_shift($word);
ctx()->setCache("match", $word);
return true;
@@ -95,14 +107,14 @@ class QQBot
} elseif ($v->keyword != "" && mb_strpos(ctx()->getMessage(), $v->keyword) !== false) {
ctx()->setCache("match", explode($v->keyword, ctx()->getMessage()));
return true;
}elseif ($v->pattern != "") {
} elseif ($v->pattern != "") {
$match = matchArgs($v->pattern, ctx()->getMessage());
if($match !== false) {
if ($match !== false) {
ctx()->setCache("match", $match);
return true;
}
} elseif ($v->regex != "") {
if(preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) {
if (preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) {
ctx()->setCache("match", $word2);
return true;
}
@@ -112,10 +124,10 @@ class QQBot
});
$dispatcher->setReturnFunction(function ($result) {
if (is_string($result)) ctx()->reply($result);
EventDispatcher::interrupt();
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
});
$r = $dispatcher->dispatchEvents();
if ($r === null) EventDispatcher::interrupt();
$dispatcher->dispatchEvents();
if ($dispatcher->status == EventDispatcher::STATUS_INTERRUPTED) EventDispatcher::interrupt();
//分发CQMessage事件
$msg_dispatcher = new EventDispatcher(CQMessage::class);

View File

@@ -1,10 +1,12 @@
<?php
<?php #plain
use ZM\Config\ZMConfig;
use ZM\Utils\DataProvider;
define("ZM_START_TIME", microtime(true));
define("ZM_DATA", ZMConfig::get("global", "zm_data"));
define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown");
define("APP_VERSION", json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true)["version"] ?? "unknown");
define("CRASH_DIR", ZMConfig::get("global", "crash_dir"));
@mkdir(ZM_DATA);
@mkdir(CRASH_DIR);

View File

@@ -1,4 +1,4 @@
<?php
<?php #plain
use Swoole\Coroutine;
use ZM\API\ZMRobot;
@@ -88,6 +88,13 @@ function getAllClasses($dir, $indoor_name) {
//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") {
if(substr(file_get_contents($dir.$v), 6, 6) == "#plain") continue;
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir()."/composer.json"), true);
foreach($composer["autoload"]["files"] as $fi) {
if(realpath(DataProvider::getWorkingDir()."/".$fi) == realpath($dir.$v)) {
continue 2;
}
}
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}