initial 2.0.0-a4 commit

This commit is contained in:
jerry
2020-11-03 21:02:24 +08:00
parent da584e0542
commit 29fa9d8662
48 changed files with 794 additions and 1470 deletions

View File

@@ -1,169 +0,0 @@
<?php
namespace ZM\Event\CQ;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQMessage;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
class MessageEvent
{
private $function_call = false;
private $data;
private $circle;
/** @var ConnectionObject|Response */
private $connection;
public function __construct($data, $conn_or_response, $circle = 0) {
$this->data = $data;
$this->connection = $conn_or_response;
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
$dispatcher = new EventDispatcher(CQBefore::class . "::message");
$dispatcher->setRuleFunction(function ($v) {
if($v->level < 200) EventDispatcher::interrupt();
return true;
});
$dispatcher->setReturnFunction(function($result){
if(!$result) EventDispatcher::interrupt();
});
$dispatcher->dispatchEvents();
foreach (ZMBuf::get("wait_api", []) as $k => $v) {
if(zm_data_hash(ctx()->getData()) == $v["hash"]) {
$v["result"] = context()->getData()["message"];
ZMBuf::appendKey("wait_api", $k, $v);
Co::resume($v["coroutine"]);
return false;
}
if (context()->getData()["user_id"] == $v["user_id"] &&
context()->getData()["self_id"] == $v["self_id"] &&
context()->getData()["message_type"] == $v["message_type"] &&
(context()->getData()[context()->getData()["message_type"] . "_id"] ?? context()->getData()["user_id"]) ==
($v[$v["message_type"] . "_id"] ?? $v["user_id"])) {
$v["result"] = context()->getData()["message"];
ZMBuf::appendKey("wait_api", $k, $v);
Co::resume($v["coroutine"]);
return false;
}
}
foreach (EventManager::$events[CQBefore::class]["message"] ?? [] as $v) {
if ($v->level >= 200) continue;
$c = $v->class;
if (ctx()->getCache("level") != 0) continue;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if (!$r) context()->setCache("block_continue", true);
}
);
if (context()->getCache("block_continue") === true) return false;
}
return true;
}
/**
* @noinspection PhpRedundantCatchClauseInspection
*/
public function onActivate() {
try {
$word = split_explode(" ", str_replace("\r", "", context()->getMessage()));
if (count(explode("\n", $word[0])) >= 2) {
$enter = explode("\n", context()->getMessage());
$first = split_explode(" ", array_shift($enter));
$word = array_merge($first, $enter);
foreach ($word as $k => $v) {
$word[$k] = trim($word[$k]);
}
}
//分发CQCommand事件
$dispatcher = new EventDispatcher(CQCommand::class);
$dispatcher->setRuleFunction(function ($v) use ($word) {
if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") 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) ||
($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, ctx()->getMessage())) !== false) ||
($v->fullMatch != "" && (preg_match("/" . $v->fullMatch . "/u", ctx()->getMessage(), $args)) != 0)) {
return true;
}
}
return false;
});
$dispatcher->setReturnFunction(function ($result) {
if (is_string($result)) ctx()->reply($result);
EventDispatcher::interrupt();
});
$r = $dispatcher->dispatchEvents($word);
if ($r === null) return;
//分发CQMessage事件
$msg_dispatcher = new EventDispatcher(CQMessage::class);
$msg_dispatcher->setRuleFunction(function ($v) {
return ($v->message == '' || ($v->message != '' && $v->message == context()->getData()["message"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"])) &&
($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == context()->getData()["raw_message"]));
});
$msg_dispatcher->setReturnFunction(function ($result) {
if (is_string($result)) ctx()->reply($result);
});
$msg_dispatcher->dispatchEvents(ctx()->getMessage());
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
/**
* 在调用完事件后执行的
* @throws AnnotationException
*/
public function onAfter() {
context()->setCache("block_continue", null);
foreach (ZMBuf::$events[CQAfter::class]["message"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if (!$r) context()->setCache("block_continue", true);
}
);
if (context()->getCache("block_continue") === true) return false;
}
return true;
}
public function hasReply() {
return $this->function_call;
}
}

View File

@@ -1,71 +0,0 @@
<?php
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\Store\ZMBuf;
class MetaEvent
{
private $data;
private $connection;
private $circle;
public function __construct($data, $connection, $circle = 0) {
$this->data = $data;
$this->connection = $connection;
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["meta_event"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
$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();
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
}

View File

@@ -1,95 +0,0 @@
<?php
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQNotice;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\Store\ZMBuf;
class NoticeEvent
{
private $data;
private $connection;
private $circle;
public function __construct($data, $connection, $circle = 0) {
$this->data = $data;
$this->connection = $connection;
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["notice"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/**
* @throws AnnotationException
*/
public function onActivate() {
try {
$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();
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
/**
* @return bool
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class]["notice"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
}

View File

@@ -1,96 +0,0 @@
<?php
namespace ZM\Event\CQ;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQRequest;
use ZM\Event\EventHandler;
use ZM\Exception\WaitTimeoutException;
use ZM\Store\ZMBuf;
class RequestEvent
{
private $data;
private $connection;
private $circle;
public function __construct($data, $connection, $circle = 0) {
$this->data = $data;
$this->connection = $connection;
$this->circle = $circle;
}
/**
* @return bool
* @throws AnnotationException
*/
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class]["request"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
/**
* @throws AnnotationException
* @noinspection PhpRedundantCatchClauseInspection
*/
public function onActivate() {
try {
$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();
EventHandler::callWithMiddleware($obj[$c],$v->method, [], [], function($r) {
if (is_string($r)) context()->reply($r);
});
if (context()->getCache("block_continue") === true) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
/**
* @return bool
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class]["request"] ?? [] as $v) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["data" => context()->getData(), "connection" => $this->connection],
[],
function ($r) {
if(!$r) context()->setCache("block_continue", true);
}
);
if(context()->getCache("block_continue") === true) return false;
}
return true;
}
}

View File

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

View File

@@ -7,6 +7,7 @@ namespace ZM\Event;
use Doctrine\Common\Annotations\AnnotationException;
use Exception;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Exception\InterruptException;
use ZM\Utils\ZMUtil;
@@ -42,9 +43,13 @@ class EventDispatcher
public function dispatchEvents(...$params) {
try {
foreach (EventManager::$events[$this->class] ?? [] as $v) {
foreach ((EventManager::$events[$this->class] ?? []) as $v) {
if($this->class == CQMetaEvent::class) {
//eval(BP);
}
$result = $this->dispatchEvent($v, $this->rule, ...$params);
if (is_callable($this->return_func)) ($this->return_func)($result);
if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result);
}
return true;
} catch (InterruptException $e) {

View File

@@ -1,293 +0,0 @@
<?php
namespace ZM\Event;
use Co;
use Doctrine\Common\Annotations\AnnotationException;
use Error;
use Exception;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use ZM\Event\Swoole\{MessageEvent, RequestEvent, WSCloseEvent, WSOpenEvent};
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\WebSocket\Frame;
use ZM\Annotation\CQ\CQAPIResponse;
use ZM\Annotation\CQ\CQAPISend;
use ZM\Annotation\Http\MiddlewareClass;
use ZM\Context\Context;
use ZM\Http\MiddlewareInterface;
use ZM\Http\Response;
use ZM\Store\LightCache;
use ZM\Store\ZMBuf;
use ZM\Utils\ZMUtil;
class EventHandler
{
/**
* @param $event_name
* @param $param0
* @param null $param1
* @throws AnnotationException
*/
public static function callSwooleEvent($event_name, $param0, $param1 = null) {
//$starttime = microtime(true);
unset(Context::$context[Co::getCid()]);
$event_name = strtolower($event_name);
switch ($event_name) {
case "message":
/** @var Frame $param1 */
/** @var Server $param0 */
$conn = ManagerGM::get($param1->fd);
set_coroutine_params(["server" => $param0, "frame" => $param1, "connection" => $conn]);
try {
(new MessageEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::trace();
}
break;
case "request":
try {
set_coroutine_params(["request" => $param0, "response" => $param1]);
(new RequestEvent($param0, $param1))->onActivate()->onAfter();
} catch (Exception $e) {
/** @var Response $param1 */
$param1->status(500);
Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
" [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
);
if (!$param1->isEnd()) {
if (ZMConfig::get("global", "debug_mode"))
$param1->end("Internal server error: " . $e->getMessage());
else
$param1->end("Internal server error.");
}
Console::error("Internal server exception (500), caused by " . get_class($e));
Console::log($e->getTraceAsString(), "gray");
} catch (Error $e) {
/** @var Response $param1 */
$param1->status(500);
Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] .
" [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
);
$doc = "Internal server error<br>";
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
if (Console::getLevel() >= 4) $doc .= $error_msg;
if (!$param1->isEnd()) $param1->end($doc);
Console::error("Internal server error (500): " . $error_msg);
Console::log($e->getTraceAsString(), "gray");
}
break;
case "open":
/** @var Request $param1 */
set_coroutine_params(["server" => $param0, "request" => $param1, "fd" => $param1->fd]);
try {
(new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::trace();
}
break;
case "close":
set_coroutine_params(["server" => $param0, "fd" => $param1]);
try {
(new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
} catch (Error $e) {
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
Console::error("Fatal error when calling $event_name: " . $error_msg);
Console::trace();
}
break;
}
//Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . " 秒", "gold"));
}
/**
* @param $event_data
* @param $conn_or_response
* @param int $level
* @return bool
* @throws AnnotationException
*/
public static function callCQEvent($event_data, $conn_or_response, int $level = 0) {
ctx()->setCache("level", $level);
if ($level >= 5) {
Console::warning("Recursive call reached " . $level . " times");
Console::trace();
return false;
}
$starttime = microtime(true);
switch ($event_data["post_type"]) {
case "message":
$event = new CQ\MessageEvent($event_data, $conn_or_response, $level);
if ($event->onBefore()) $event->onActivate();
$event->onAfter();
return $event->hasReply();
break;
case "notice":
$event = new CQ\NoticeEvent($event_data, $conn_or_response, $level);
if ($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "request":
$event = new CQ\RequestEvent($event_data, $conn_or_response, $level);
if ($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "meta_event":
$event = new CQ\MetaEvent($event_data, $conn_or_response, $level);
if ($event->onBefore()) $event->onActivate();
return true;
}
unset($starttime);
return false;
}
/**
* @param $req
* @throws AnnotationException
*/
public static function callCQResponse($req) {
Console::debug("收到来自API连接的回复" . json_encode($req, 128 | 256));
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
if (isset($req["echo"]) && LightCache::isset("sent_api_" . $req["echo"])) {
$origin = LightCache::get("sent_api_" . $req["echo"]);
$self_id = $origin["self_id"];
$response = [
"status" => $status,
"retcode" => $retcode,
"data" => $data,
"self_id" => $self_id,
"echo" => $req["echo"]
];
set_coroutine_params(["cq_response" => $response]);
if (isset(ZMBuf::$events[CQAPIResponse::class][$req["retcode"]])) {
list($c, $method) = ZMBuf::$events[CQAPIResponse::class][$req["retcode"]];
$class = new $c(["data" => $origin["data"]]);
call_user_func_array([$class, $method], [$origin["data"], $req]);
}
$origin_ctx = ctx()->copy();
ctx()->setCache("action", $origin["data"]["action"] ?? "unknown");
ctx()->setData($origin["data"]);
foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
if (($v->action == "" || $v->action == ctx()->getCache("action")) && $v->with_result) {
$c = $v->class;
self::callWithMiddleware($c, $v->method, context()->copy(), [ctx()->getCache("action"), $origin["data"]["params"] ?? [], ctx()->getRobotId()]);
if (context()->getCache("block_continue") === true) break;
}
}
set_coroutine_params($origin_ctx);
if (($origin["func"] ?? null) !== null) {
call_user_func($origin["func"], $response, $origin["data"]);
} elseif (($origin["coroutine"] ?? false) !== false) {
$r = LightCache::get("sent_api_" . $req["echo"]);
$r["result"] = $response;
LightCache::set("sent_api_" . $req["echo"], $r);
Co::resume($origin['coroutine']);
}
LightCache::unset("sent_api_" . $req["echo"]);
}
}
public static function callCQAPISend($reply, ?ConnectionObject $connection) {
$action = $reply["action"] ?? null;
if ($action === null) {
Console::warning("API 激活事件异常!");
return;
}
if (ctx() === null) $content = [];
else $content = ctx()->copy();
go(function () use ($action, $reply, $connection, $content) {
set_coroutine_params($content);
context()->setCache("action", $action);
context()->setCache("reply", $reply);
foreach (ZMBuf::$events[CQAPISend::class] ?? [] as $k => $v) {
if (($v->action == "" || $v->action == $action) && !$v->with_result) {
$c = $v->class;
self::callWithMiddleware($c, $v->method, context()->copy(), [$reply["action"], $reply["params"] ?? [], $connection->getOption('connect_id')]);
if (context()->getCache("block_continue") === true) break;
}
}
});
}
/**
* @param $c
* @param $method
* @param array $class_construct
* @param array $func_args
* @param null $after_call
* @return mixed|null
* @throws AnnotationException
* @throws Exception
*/
public static function callWithMiddleware($c, $method, array $class_construct, array $func_args, $after_call = null) {
$return_value = null;
$plain_class = is_object($c) ? get_class($c) : $c;
if (isset(ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method])) {
$middlewares = ZMBuf::$events[MiddlewareInterface::class][$plain_class][$method];
$before_result = true;
$r = [];
foreach ($middlewares as $k => $middleware) {
if (!isset(ZMBuf::$events[MiddlewareClass::class][$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middleware];
$before = $middleware_obj["class"];
//var_dump($middleware_obj);
$r[$k] = new $before();
$r[$k]->class = is_object($c) ? get_class($c) : $c;
$r[$k]->method = $method;
if (isset($middleware_obj["before"])) {
$rs = $middleware_obj["before"];
$before_result = $r[$k]->$rs(...$func_args);
if ($before_result === false) break;
}
}
if ($before_result) {
try {
if (is_object($c)) $class = $c;
elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
else $class = new $c($class_construct);
$result = $class->$method(...$func_args);
if (is_callable($after_call))
$return_value = $after_call($result);
} catch (Exception $e) {
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
if (!isset($middleware_obj["exceptions"])) continue;
foreach ($middleware_obj["exceptions"] as $name => $method) {
if ($e instanceof $name) {
$r[$i]->$method($e);
context()->setCache("block_continue", true);
}
}
if (context()->getCache("block_continue") === true) return $return_value;
}
throw $e;
}
}
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = ZMBuf::$events[MiddlewareClass::class][$middlewares[$i]];
if (isset($middleware_obj["after"], $r[$i])) {
$r[$i]->{$middleware_obj["after"]}(...$func_args);
}
}
} else {
if (is_object($c)) $class = $c;
elseif ($class_construct == []) $class = ZMUtil::getModInstance($c);
else $class = new $c($class_construct);
$result = call_user_func_array([$class, $method], $func_args);
if (is_callable($after_call))
$return_value = call_user_func_array($after_call, [$result]);
}
return $return_value;
}
}

View File

@@ -11,7 +11,7 @@ use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnTick;
use ZM\Console\Console;
use ZM\Store\ZMBuf;
use ZM\Store\ZMAtomic;
class EventManager
{
@@ -20,7 +20,7 @@ class EventManager
public static $middlewares = [];
public static $req_mapping = [];
public static function addEvent($event_name, AnnotationBase $event_obj) {
public static function addEvent($event_name, ?AnnotationBase $event_obj) {
self::$events[$event_name][] = $event_obj;
}
@@ -43,7 +43,7 @@ class EventManager
Console::debug("Added Middleware-based timer: " . $plain_class . " -> " . $vss->method);
Timer::tick($vss->tick_ms, function () use ($vss, $dispatcher) {
set_coroutine_params([]);
if (ZMBuf::atomic("stop_signal")->get() != 0) {
if (ZMAtomic::get("stop_signal")->get() != 0) {
Timer::clearAll();
return;
}

View File

@@ -17,7 +17,7 @@ use Swoole\Timer;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Swoole\OnWorkerStart;
use ZM\Annotation\Swoole\SwooleEvent;
use ZM\Annotation\Swoole\OnSwooleEvent;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
@@ -33,6 +33,8 @@ use ZM\Exception\DbException;
use ZM\Framework;
use ZM\Http\Response;
use ZM\Module\QQBot;
use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool;
use ZM\Store\ZMBuf;
use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil;
@@ -132,9 +134,9 @@ class ServerEventHandler
foreach ($server->connections as $v) {
$server->close($v);
}
if (ZMBuf::$sql_pool !== null) {
ZMBuf::$sql_pool->close();
ZMBuf::$sql_pool = null;
if (SqlPoolStorage::$sql_pool !== null) {
SqlPoolStorage::$sql_pool->close();
SqlPoolStorage::$sql_pool = null;
}
// 这里执行的是只需要执行一遍的代码,比如终端监听器和键盘监听器
@@ -175,7 +177,7 @@ class ServerEventHandler
}
}
$sql = ZMConfig::get("global", "sql_config");
ZMBuf::$sql_pool = new PDOPool((new PDOConfig())
SqlPoolStorage::$sql_pool = new PDOPool((new PDOConfig())
->withHost($sql["sql_host"])
->withPort($sql["sql_port"])
// ->withUnixSocket('/tmp/mysql.sock')
@@ -188,6 +190,13 @@ class ServerEventHandler
DB::initTableList();
}
// 开箱即用的Redis
$redis = ZMConfig::get("global", "redis_config");
if($redis !== null && $redis["host"] != "") {
if (!extension_loaded("redis")) Console::error("Can not find redis extension.\n");
else ZMRedisPool::init($redis);
}
$this->loadAnnotations(); //加载composer资源、phar外置包、注解解析注册等
//echo json_encode(debug_backtrace(), 128|256);
@@ -238,11 +247,11 @@ class ServerEventHandler
* @param Frame $frame
*/
public function onMessage($server, Frame $frame) {
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd);
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd.": ".TermColor::ITALIC.$frame->data.TermColor::RESET);
unset(Context::$context[Co::getCid()]);
$conn = ManagerGM::get($frame->fd);
set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]);
$dispatcher = new EventDispatcher(SwooleEvent::class);
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
return strtolower($v->type) == 'message';
@@ -278,7 +287,7 @@ class ServerEventHandler
Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
set_coroutine_params(["request" => $request, "response" => $response]);
$dis = new EventDispatcher();
$dis = new EventDispatcher(OnSwooleEvent::class);
$dis->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
return strtolower($v->type) == 'request';
@@ -322,7 +331,7 @@ class ServerEventHandler
else
$response->end("Internal server error.");
}
Console::error("Internal server exception (500), caused by " . get_class($e));
Console::error("Internal server exception (500), caused by " . get_class($e).": ".$e->getMessage());
Console::log($e->getTraceAsString(), "gray");
} catch (Error $e) {
$response->status(500);
@@ -354,7 +363,8 @@ class ServerEventHandler
ManagerGM::pushConnect($request->fd, $type_conn);
$conn = ManagerGM::get($request->fd);
set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]);
$dispatcher = new EventDispatcher(SwooleEvent::class);
$conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? "");
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
return strtolower($v->type) == 'open';
@@ -389,7 +399,7 @@ class ServerEventHandler
if ($conn === null) return;
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
set_coroutine_params(["server" => $server, "connection" => $conn, "fd" => $fd]);
$dispatcher = new EventDispatcher(SwooleEvent::class);
$dispatcher = new EventDispatcher(OnSwooleEvent::class);
$dispatcher->setRuleFunction(function ($v) {
if ($v->getRule() == '') {
return strtolower($v->type) == 'close';
@@ -421,9 +431,13 @@ class ServerEventHandler
*/
public function onPipeMessage(Server $server, $src_worker_id, $data) {
//var_dump($data, $server->worker_id);
unset(Context::$context[Co::getCid()]);
//unset(Context::$context[Co::getCid()]);
$data = json_decode($data, true);
switch ($data["action"]) {
switch ($data["action"] ?? '') {
case "resume_ws_message":
$obj = $data["data"];
Co::resume($obj["coroutine"]);
break;
case "stop":
Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器');
Timer::clearAll();
@@ -434,6 +448,9 @@ class ServerEventHandler
case 'echo':
Console::success('接收到来自 #' . $src_worker_id . ' 的消息');
break;
case 'send':
$server->sendMessage(json_encode(["action" => "echo"]), $data["target"]);
break;
default:
echo $data . PHP_EOL;
}
@@ -481,17 +498,17 @@ class ServerEventHandler
}
//加载插件
$plugins = ZMConfig::get("global", "plugins") ?? [];
$plugins = ZMConfig::get("global", "modules") ?? [];
if (!isset($plugins["qqbot"])) $plugins["qqbot"] = true;
if ($plugins["qqbot"]) {
$obj = new SwooleEvent();
$obj = new OnSwooleEvent();
$obj->class = QQBot::class;
$obj->method = 'handle';
$obj->type = 'message';
$obj->level = 99999;
$obj->rule = 'connectIsQQ()';
EventManager::addEvent(SwooleEvent::class, $obj);
EventManager::addEvent(OnSwooleEvent::class, $obj);
}
//TODO: 编写加载外部插件的方式

View File

@@ -1,97 +0,0 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEvent;
use Exception;
use ZM\Event\EventHandler;
use ZM\Store\ZMBuf;
class MessageEvent implements SwooleEventInterface
{
/**
* @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() {
$conn = ManagerGM::get(context()->getFrame()->fd);
try {
if ($conn->getName() == "qq") {
$data = json_decode(context()->getFrame()->data, true);
if (isset($data["post_type"])) {
set_coroutine_params(["data" => $data, "connection" => $conn]);
ctx()->setCache("level", 0);
Console::debug("Calling CQ Event from fd=" . $conn->getFd());
EventHandler::callCQEvent($data, ManagerGM::get(context()->getFrame()->fd), 0);
} else{
set_coroutine_params(["connection" => $conn]);
EventHandler::callCQResponse($data);
}
}
foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["server" => $this->server, "frame" => $this->frame, "connection" => $conn],
[$conn]
);
if (context()->getCache("block_continue") === true) break;
}
}
} catch (Exception $e) {
Console::warning("Websocket message event exception: " . (($cs = $e->getMessage()) == "" ? get_class($e) : $cs));
Console::warning("In ". $e->getFile() . " at line ".$e->getLine());
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
$class = new $c();
call_user_func_array([$class, $v->method], []);
if (context()->getCache("block_continue") === true) 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, ManagerGM::get($this->frame->fd));
break;
case "dataEqual": //handle websocket message事件时才能用
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data);
break;
}
return true;
}
}

View File

@@ -1,186 +0,0 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Exception;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\EventManager;
use ZM\Store\ZMBuf;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEvent;
use ZM\Event\EventHandler;
use ZM\Http\Response;
use ZM\Utils\DataProvider;
use ZM\Utils\HttpUtil;
use ZM\Utils\ZMUtil;
class RequestEvent implements SwooleEventInterface
{
/**
* @var Request
*/
private $request;
/**
* @var Response
*/
private $response;
public function __construct(Request $request, Response $response) {
$this->request = $request;
$this->response = $response;
}
/**
* @return $this|SwooleEvent
* @throws Exception
*/
public function onActivate() {
foreach (ZMConfig::get("global", "http_header") as $k => $v) {
$this->response->setHeader($k, $v);
}
$uri = $this->request->server["request_uri"];
Console::verbose($this->request->server["remote_addr"] . " request " . $uri);
$uri = explode("/", $uri);
$uri = array_diff($uri, ["..", "", "."]);
$node = EventManager::$req_mapping;
$params = [];
while (true) {
$r = array_shift($uri);
if ($r === null) break;
if (($cnt = count($node["son"] ?? [])) == 1) {
if (isset($node["param_route"])) {
foreach ($node["son"] as $k => $v) {
if ($v["id"] == $node["param_route"]) {
$node = $v;
$params[mb_substr($v["name"], 1, -1)] = $r;
continue 2;
}
}
} elseif ($node["son"][0]["name"] == $r) {
$node = $node["son"][0];
continue;
}
} elseif ($cnt >= 1) {
if (isset($node["param_route"])) {
foreach ($node["son"] as $k => $v) {
if ($v["id"] == $node["param_route"]) {
$node = $v;
$params[mb_substr($v["name"], 1, -1)] = $r;
continue 2;
}
}
}
foreach ($node["son"] as $k => $v) {
if ($v["name"] == $r) {
$node = $v;
continue 2;
}
}
}
if (ZMConfig::get("global", "static_file_server")["status"]) {
$base_dir = ZMConfig::get("global", "static_file_server")["document_root"];
$base_index = ZMConfig::get("global", "static_file_server")["document_index"];
$uri = $this->request->server["request_uri"];
$path = realpath($base_dir . urldecode($uri));
if ($path !== false) {
if (is_dir($path)) $path = $path . '/';
$work = realpath(DataProvider::getWorkingDir()) . '/';
if (strpos($path, $work) !== 0) {
$this->responseStatus(403);
return $this;
}
if (is_dir($path)) {
foreach ($base_index as $vp) {
if (is_file($path . $vp)) {
Console::info("[200] " . $uri . " (static)");
$exp = strtolower(pathinfo($path . $vp)['extension'] ?? "unknown");
$this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
$this->response->end(file_get_contents($path . $vp));
return $this;
}
}
} elseif (is_file($path)) {
Console::info("[200] " . $uri . " (static)");
$exp = strtolower(pathinfo($path)['extension'] ?? "unknown");
$this->response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
$this->response->end(file_get_contents($path));
return $this;
}
}
}
$this->response->status(404);
$this->response->end(HttpUtil::getHttpCodePage(404));
return $this;
}
context()->setCache("params", $params);
if (in_array(strtoupper($this->request->server["request_method"]), $node["request_method"] ?? [])) { //判断目标方法在不在里面
$c_name = $node["class"];
EventHandler::callWithMiddleware(
$c_name,
$node["method"],
["request" => $this->request, "response" => &$this->response, "params" => $params],
[$params],
function ($result) {
if (is_string($result) && !$this->response->isEnd()) $this->response->end($result);
if ($this->response->isEnd()) context()->setCache("block_continue", true);
}
);
}
foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) {
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
$c = $v->class;
EventHandler::callWithMiddleware($c, $v->method, ["request" => $this->request, "response" => $this->response], []);
if (context()->getCache("block_continue") === true) break;
}
}
if (!$this->response->isEnd()) {
$this->response->status(404);
$this->response->end(HttpUtil::getHttpCodePage(404));
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["request" => $this->request, "response" => $this->response]);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
}
}
return $this;
}
private function responseStatus(int $int) {
$this->response->status($int);
$this->response->end();
}
private function parseSwooleRule($v) {
switch (explode(":", $v->rule)[0]) {
case "containsGet":
case "containsPost":
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request);
break;
case "containsJson":
$content = $this->request->rawContent();
$content = json_decode($content, true);
if ($content === null) return false;
if ($v->callback instanceof Closure) return call_user_func($v->callback, $content);
break;
}
return true;
}
}

View File

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

View File

@@ -1,70 +0,0 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use Swoole\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEvent;
use ZM\Event\EventHandler;
use ZM\Store\ZMBuf;
use ZM\Utils\ZMUtil;
class WSCloseEvent implements SwooleEventInterface
{
public $server;
public $fd;
public function __construct(Server $server, int $fd) {
$this->server = $server;
$this->fd = $fd;
}
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onActivate() {
Console::debug("Websocket closed #{$this->fd}");
set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ManagerGM::get($this->fd)]);
foreach(ZMBuf::$events[SwooleEvent::class] ?? [] as $v) {
if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) {
$c = $v->class;
EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
if(context()->getCache("block_continue") === true) break;
}
}
ManagerGM::popConnect($this->fd);
return $this;
}
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
EventHandler::callWithMiddleware($c, $v->method, ["server" => $this->server, "fd" => $this->fd], []);
if(context()->getCache("block_continue") === true) 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, ManagerGM::get($this->fd));
break;
}
return true;
}
}

View File

@@ -1,106 +0,0 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Doctrine\Common\Annotations\AnnotationException;
use ZM\Config\ZMConfig;
use ZM\ConnectionManager\ConnectionObject;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use Swoole\Http\Request;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEvent;
use ZM\Event\EventHandler;
use ZM\Store\ZMBuf;
class WSOpenEvent implements SwooleEventInterface
{
/**
* @var Server
*/
private $server;
/**
* @var Request
*/
private $request;
/** @var ConnectionObject */
private $conn;
public function __construct(Server $server, Request $request) {
$this->server = $server;
$this->request = $request;
}
/**
* @inheritDoc
* @throws AnnotationException
*/
public function onActivate() {
$type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? "");
$type_conn = $this->getTypeClassName($type);
ManagerGM::pushConnect($this->request->fd, $type_conn);
if ($type_conn == "qq") {
ManagerGM::setName($this->request->fd, "qq");
$qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? "";
$self_token = ZMConfig::get("global", "access_token") ?? "";
if (isset($this->request->header["authorization"])) {
Console::debug($this->request->header["authorization"]);
}
$remote_token = $this->request->get["token"] ?? (isset($this->request->header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : "");
if ($qq != "" && ($self_token == $remote_token)) {
ManagerGM::setOption($this->request->fd, "connect_id", $qq);
$this->conn = ManagerGM::get($this->request->fd);
} else {
$this->conn = ManagerGM::get($this->request->fd);
Console::warning("connection of CQ has invalid QQ or token!");
Console::debug("Remote token: " . $remote_token);
}
} else {
$this->conn = ManagerGM::get($this->request->fd);
}
set_coroutine_params(["server" => $this->server, "request" => $this->request, "connection" => $this->conn]);
foreach (ZMBuf::$events[SwooleEvent::class] ?? [] as $v) {
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
EventHandler::callWithMiddleware(
$c,
$v->method,
["server" => $this->server, "request" => $this->request, "connection" => $this->conn],
[$this->conn]
);
if (context()->getCache("block_continue") === true) break;
}
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
if (!$this->server->exists($this->conn->getFd())) return $this;
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
$class = new $v["class"]();
call_user_func_array([$class, $v["method"]], [$this->conn]);
if (context()->getCache("block_continue") === true) 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;
}
}