diff --git a/composer.json b/composer.json index a043fc23..852f7116 100644 --- a/composer.json +++ b/composer.json @@ -3,7 +3,7 @@ "description": "High performance QQ robot and web server development framework", "minimum-stability": "stable", "license": "Apache-2.0", - "version": "2.0.0-b4", + "version": "2.0.0-b5", "extra": {}, "authors": [ { @@ -35,6 +35,10 @@ "symfony/routing": "^5.1", "symfony/polyfill-php80": "^1.20" }, + "suggest": { + "ext-ctype": "*", + "ext-mbstring": "*" + }, "autoload": { "psr-4": { "ZM\\": "src/ZM", @@ -57,4 +61,4 @@ "phpunit/phpunit": "^9.3", "swoole/ide-helper": "@dev" } -} \ No newline at end of file +} diff --git a/config/global.php b/config/global.php index eed3fef0..03832714 100644 --- a/config/global.php +++ b/config/global.php @@ -50,8 +50,6 @@ $config['sql_config'] = [ 'sql_username' => 'name', 'sql_database' => 'db_name', 'sql_password' => '', - 'sql_enable_cache' => true, - 'sql_reset_cache' => '0300', 'sql_options' => [ PDO::ATTR_STRINGIFY_FETCHES => false, PDO::ATTR_EMULATE_PREPARES => false @@ -115,7 +113,10 @@ $config['command_register_class'] = [ /** 服务器启用的外部第三方和内部插件 */ $config['modules'] = [ - 'onebot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 + 'onebot' => [ + 'status' => true, + 'single_bot_mode' => false + ], // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 ]; return $config; diff --git a/resources/html/subdir/index.html b/resources/html/subdir/index.html new file mode 100644 index 00000000..5c370979 --- /dev/null +++ b/resources/html/subdir/index.html @@ -0,0 +1,10 @@ + + + + + Example page + + +
+ + diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 40a41545..858ffe88 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -8,7 +8,9 @@ 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; /** * Class Hello @@ -40,11 +42,20 @@ class Hello } } + /** + * 使用命令 .reload 发给机器人远程重载,注意将 user_id 换成你自己的 QQ + * @CQCommand(".reload",user_id=627577391) + */ + public function reload() { + ctx()->reply("重启中..."); + ZMUtil::reload(); + } + /** * @CQCommand("我是谁") */ public function whoami() { - $user = ctx()->getRobot()->setCallback(true)->getLoginInfo(); + $user = ctx()->getRobot()->getLoginInfo(); return "你是" . $user["data"]["nickname"] . ",QQ号是" . $user["data"]["user_id"]; } @@ -121,6 +132,14 @@ class Hello Console::info("机器人 " . $conn->getOption("connect_id") . " 已断开连接!"); } + /** + * 阻止 Chrome 自动请求 /favicon.ico 导致的多条请求并发和干扰 + * @OnSwooleEvent("request",rule="ctx()->getRequest()->server['request_uri'] == '/favicon.ico'",level=200) + */ + public function onRequest() { + EventDispatcher::interrupt(); + } + /** * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 * @OnSwooleEvent(type="open",rule="connectIsDefault()") diff --git a/src/Module/Middleware/TimerMiddleware.php b/src/Module/Middleware/TimerMiddleware.php index 34e21c7b..d90d2570 100644 --- a/src/Module/Middleware/TimerMiddleware.php +++ b/src/Module/Middleware/TimerMiddleware.php @@ -2,8 +2,9 @@ namespace Module\Middleware; -use ZM\Annotation\Http\After; -use ZM\Annotation\Http\Before; +use ZM\Annotation\Http\HandleAfter; +use ZM\Annotation\Http\HandleBefore; +use ZM\Annotation\Http\HandleException; use ZM\Annotation\Http\MiddlewareClass; use ZM\Console\Console; use ZM\Http\MiddlewareInterface; @@ -19,7 +20,7 @@ class TimerMiddleware implements MiddlewareInterface private $starttime; /** - * @Before() + * @HandleBefore() * @return bool */ public function onBefore() { @@ -28,9 +29,16 @@ class TimerMiddleware implements MiddlewareInterface } /** - * @After() + * @HandleAfter() */ public function onAfter() { Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms."); } + + /** + * @HandleException(\Exception::class) + */ + public function onException() { + Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred."); + } } diff --git a/src/ZM/API/ZMRobot.php b/src/ZM/API/ZMRobot.php index 126382bb..45230948 100644 --- a/src/ZM/API/ZMRobot.php +++ b/src/ZM/API/ZMRobot.php @@ -24,7 +24,7 @@ class ZMRobot /** @var ConnectionObject|null */ private $connection; - private $callback = null; + private $callback = true; private $prefix = 0; /** @@ -50,6 +50,9 @@ class ZMRobot return new ZMRobot($r[array_rand($r)]); } + public static function getFirst() { + } + /** * @return ZMRobot[] */ diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 6bcb7eaf..0024768a 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -9,7 +9,7 @@ use ZM\Console\Console; use ReflectionClass; use ReflectionException; use ReflectionMethod; -use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; +use ZM\Annotation\Http\{HandleAfter, HandleBefore, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Module\Closed; use ZM\Utils\DataProvider; @@ -287,8 +287,8 @@ class AnnotationParser foreach ($reflection_class->getMethods() as $vss) { $method_annotations = $this->reader->getMethodAnnotations($vss); foreach ($method_annotations as $vsss) { - if ($vsss instanceof Before) $result["before"] = $vss->getName(); - if ($vsss instanceof After) $result["after"] = $vss->getName(); + if ($vsss instanceof HandleBefore) $result["before"] = $vss->getName(); + if ($vsss instanceof HandleAfter) $result["after"] = $vss->getName(); if ($vsss instanceof HandleException) { $result["exceptions"][$vsss->class_name] = $vss->getName(); } diff --git a/src/ZM/Annotation/CQ/CQCommand.php b/src/ZM/Annotation/CQ/CQCommand.php index 6a6d4021..a7ec162b 100644 --- a/src/ZM/Annotation/CQ/CQCommand.php +++ b/src/ZM/Annotation/CQ/CQCommand.php @@ -25,6 +25,8 @@ class CQCommand extends AnnotationBase implements Level public $start_with = ""; /** @var string */ public $end_with = ""; + /** @var string */ + public $keyword = ""; /** @var string[] */ public $alias = []; /** @var string */ diff --git a/src/ZM/Annotation/Http/After.php b/src/ZM/Annotation/Http/HandleAfter.php similarity index 80% rename from src/ZM/Annotation/Http/After.php rename to src/ZM/Annotation/Http/HandleAfter.php index ef24db54..9d2e43a4 100644 --- a/src/ZM/Annotation/Http/After.php +++ b/src/ZM/Annotation/Http/HandleAfter.php @@ -9,11 +9,11 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class After + * Class HandleAfter * @package ZM\Annotation\Http * @Annotation * @Target("METHOD") */ -class After extends AnnotationBase +class HandleAfter extends AnnotationBase { -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Http/Before.php b/src/ZM/Annotation/Http/HandleBefore.php similarity index 65% rename from src/ZM/Annotation/Http/Before.php rename to src/ZM/Annotation/Http/HandleBefore.php index f09cfc40..90a092a5 100644 --- a/src/ZM/Annotation/Http/Before.php +++ b/src/ZM/Annotation/Http/HandleBefore.php @@ -4,16 +4,15 @@ namespace ZM\Annotation\Http; -use Doctrine\Common\Annotations\Annotation\Required; use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class Before + * Class HandleBefore * @package ZM\Annotation\Http * @Annotation * @Target("METHOD") */ -class Before extends AnnotationBase +class HandleBefore extends AnnotationBase { -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/Swoole/ZMSetup.php b/src/ZM/Annotation/Swoole/OnSetup.php similarity index 85% rename from src/ZM/Annotation/Swoole/ZMSetup.php rename to src/ZM/Annotation/Swoole/OnSetup.php index b3165937..654002b6 100644 --- a/src/ZM/Annotation/Swoole/ZMSetup.php +++ b/src/ZM/Annotation/Swoole/OnSetup.php @@ -13,6 +13,6 @@ use ZM\Annotation\AnnotationBase; * @Annotation * @Target("METHOD") */ -class ZMSetup extends AnnotationBase +class OnSetup extends AnnotationBase { } diff --git a/src/ZM/Annotation/Swoole/OnWorkerStart.php b/src/ZM/Annotation/Swoole/OnStart.php similarity index 86% rename from src/ZM/Annotation/Swoole/OnWorkerStart.php rename to src/ZM/Annotation/Swoole/OnStart.php index 1eb0f14d..e95e6275 100644 --- a/src/ZM/Annotation/Swoole/OnWorkerStart.php +++ b/src/ZM/Annotation/Swoole/OnStart.php @@ -12,7 +12,7 @@ use ZM\Annotation\AnnotationBase; * @Annotation * @Target("ALL") */ -class OnWorkerStart extends AnnotationBase +class OnStart extends AnnotationBase { /** * @var int diff --git a/src/ZM/Annotation/Swoole/SwooleEventAfter.php b/src/ZM/Annotation/Swoole/SwooleEventAfter.php deleted file mode 100644 index 6626a69e..00000000 --- a/src/ZM/Annotation/Swoole/SwooleEventAfter.php +++ /dev/null @@ -1,76 +0,0 @@ -type; - } - - /** - * @param string $type - */ - 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; - } - - -} \ No newline at end of file diff --git a/src/ZM/Annotation/Swoole/HandleEvent.php b/src/ZM/Annotation/Swoole/SwooleHandler.php similarity index 83% rename from src/ZM/Annotation/Swoole/HandleEvent.php rename to src/ZM/Annotation/Swoole/SwooleHandler.php index 80a0fac1..d70fb7d0 100644 --- a/src/ZM/Annotation/Swoole/HandleEvent.php +++ b/src/ZM/Annotation/Swoole/SwooleHandler.php @@ -9,12 +9,12 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class HandleEvent + * Class SwooleHandler * @package ZM\Annotation\Swoole * @Annotation * @Target("METHOD") */ -class HandleEvent extends AnnotationBase +class SwooleHandler extends AnnotationBase { /** * @var string diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 08bece9a..f19ab656 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -68,9 +68,6 @@ class ConsoleApplication extends Application } } - if (!is_dir(DataProvider::getWorkingDir() . '/src/')) { - die("Unable to find source directory.\nMaybe you need to run \"init\"?"); - } ZMConfig::setDirectory(DataProvider::getWorkingDir() . '/config'); ZMConfig::env($args["env"] ?? ""); if (ZMConfig::get("global") === false) { diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 6ad6fa12..e1de0e1e 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -149,7 +149,7 @@ class Context implements ContextInterface } catch (Exception $e) { $r = false; } - if($r === false) { + if ($r === false) { throw new WaitTimeoutException($this, $timeout_prompt); } return $r["message"]; @@ -203,7 +203,6 @@ class Context implements ContextInterface 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) { @@ -215,9 +214,9 @@ class Context implements ContextInterface } return $this->waitMessage($prompt_msg); case ZM_MATCH_FIRST: - if (isset($arg[1])) { - $a = $arg[1]; - array_splice($arg, 1, 1); + if (isset($arg[0])) { + $a = $arg[0]; + array_splice($arg, 0, 1); ctx()->setCache("match", $arg); return $a; } else { @@ -227,6 +226,22 @@ class Context implements ContextInterface throw new InvalidArgumentException(); } + /** + * @param string $prompt_msg + * @return int|mixed|string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function getNextArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_FIRST, $prompt_msg); } + + /** + * @param string $prompt_msg + * @return int|mixed|string + * @throws InvalidArgumentException + * @throws WaitTimeoutException + */ + public function getFullArg($prompt_msg = "") { return $this->getArgs(ZM_MATCH_ALL, $prompt_msg); } + public function cloneFromParent() { set_coroutine_params(self::$context[Co::getPcid()] ?? self::$context[$this->cid]); return context(); diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index 94689d37..00cbda60 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -103,6 +103,10 @@ interface ContextInterface */ public function getArgs($mode, $prompt_msg); + public function getNextArg($prompt_msg = ""); + + public function getFullArg($prompt_msg = ""); + public function setCache($key, $value); /** diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 5f5339bf..8bbd3654 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -7,8 +7,12 @@ namespace ZM\Event; use Doctrine\Common\Annotations\AnnotationException; use Exception; use ZM\Annotation\AnnotationBase; -use ZM\Annotation\CQ\CQMetaEvent; +use ZM\Console\Console; use ZM\Exception\InterruptException; +use ZM\Exception\ZMException; +use ZM\Store\LightCacheInside; +use ZM\Store\Lock\SpinLock; +use ZM\Store\ZMAtomic; use ZM\Utils\ZMUtil; class EventDispatcher @@ -19,6 +23,10 @@ class EventDispatcher private $rule = null; /** @var null|callable */ private $return_func = null; + /** @var bool */ + private $log = false; + /** @var int */ + private $eid = 0; /** * @throws InterruptException @@ -27,8 +35,32 @@ class EventDispatcher throw new InterruptException('interrupt'); } + public static function enableEventTrace($event_class) { + SpinLock::lock("_event_trace"); + $list = LightCacheInside::get("wait_api", "event_trace"); + $list[$event_class] = true; + LightCacheInside::set("wait_api", "event_trace", $list); + SpinLock::unlock("_event_trace"); + } + + public static function disableEventTrace($event_class) { + SpinLock::lock("_event_trace"); + $list = LightCacheInside::get("wait_api", "event_trace"); + unset($list[$event_class]); + LightCacheInside::set("wait_api", "event_trace", $list); + SpinLock::unlock("_event_trace"); + } + public function __construct(string $class = '') { $this->class = $class; + try { + $this->eid = ZMAtomic::get("_event_id")->add(1); + $list = LightCacheInside::get("wait_api", "event_trace"); + } catch (ZMException $e) { + $list = []; + } + if (isset($list[$class])) $this->log = true; + if ($this->log) Console::verbose("[事件分发{$this->eid}] 开始分发事件: " . $class); } public function setRuleFunction(callable $rule = null) { @@ -43,10 +75,13 @@ class EventDispatcher public function dispatchEvents(...$params) { try { - foreach ((EventManager::$events[$this->class] ?? []) as $v) { $result = $this->dispatchEvent($v, $this->rule, ...$params); - if ($result !== false && is_callable($this->return_func)) ($this->return_func)($result); + if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。"); + if ($result !== false && is_callable($this->return_func)) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ..."); + ($this->return_func)($result); + } } return true; } catch (InterruptException $e) { @@ -67,9 +102,15 @@ class EventDispatcher public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { $q_c = $v->class; $q_f = $v->method; - if ($rule_func !== null && !$rule_func($v)) return false; + if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在判断 " . $q_c . "::" . $q_f . " 方法下的 rule ..."); + if ($rule_func !== null && !$rule_func($v)) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 判断为 false, 拒绝执行此方法。"); + return false; + } + if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 rule 为真,继续执行方法本身 ..."); 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)); $before_result = true; $r = []; foreach ($middlewares as $k => $middleware) { @@ -81,22 +122,34 @@ class EventDispatcher $r[$k]->class = $q_c; $r[$k]->method = $q_f; if (isset($middleware_obj["before"])) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ..."); $rs = $middleware_obj["before"]; $before_result = $r[$k]->$rs(...$params); - if ($before_result === false) break; + if ($before_result === false) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 false,停止执行原事件,开始执行下一事件。"); + break; + } else { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 前置事件为 true,继续执行原事件。"); + } } } if ($before_result) { try { $q_o = ZMUtil::getModInstance($q_c); + if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ..."); $result = $q_o->$q_f(...$params); } catch (Exception $e) { - if ($e instanceof InterruptException) throw $e; + if ($e instanceof InterruptException) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 检测到事件阻断调用,正在跳出事件分发器 ..."); + throw $e; + } + if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ..."); for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; if (!isset($middleware_obj["exceptions"])) continue; foreach ($middleware_obj["exceptions"] as $name => $method) { if ($e instanceof $name) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 的异常 " . get_class($e) . " 被 Middleware:" . $middlewares[$i] . " 下的 " . get_class($r[$i]) . "::" . $method . " 捕获。"); $r[$i]->$method($e); self::interrupt(); } @@ -107,7 +160,9 @@ class EventDispatcher for ($i = count($middlewares) - 1; $i >= 0; --$i) { $middleware_obj = EventManager::$middlewares[$middlewares[$i]]; if (isset($middleware_obj["after"], $r[$i])) { + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ..."); $r[$i]->{$middleware_obj["after"]}(...$params); + if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 后置事件执行完毕!"); } } return $result; @@ -115,6 +170,7 @@ class EventDispatcher 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); } } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 6783e1be..ddb3827d 100644 --- a/src/ZM/Event/ServerEventHandler.php +++ b/src/ZM/Event/ServerEventHandler.php @@ -16,7 +16,7 @@ use Swoole\Process; use Swoole\Timer; use ZM\Annotation\AnnotationParser; use ZM\Annotation\Http\RequestMapping; -use ZM\Annotation\Swoole\OnWorkerStart; +use ZM\Annotation\Swoole\OnStart; use ZM\Annotation\Swoole\OnSwooleEvent; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; @@ -24,7 +24,7 @@ use ZM\Console\Console; use Swoole\Http\Request; use Swoole\Server; use Swoole\WebSocket\Frame; -use ZM\Annotation\Swoole\HandleEvent; +use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\TermColor; use ZM\Context\Context; use ZM\Context\ContextInterface; @@ -33,6 +33,7 @@ use ZM\Exception\DbException; use ZM\Framework; use ZM\Http\Response; use ZM\Module\QQBot; +use ZM\Store\LightCacheInside; use ZM\Store\MySQL\SqlPoolStorage; use ZM\Store\Redis\ZMRedisPool; use ZM\Store\ZMBuf; @@ -44,7 +45,7 @@ use ZM\Utils\ZMUtil; class ServerEventHandler { /** - * @HandleEvent("start") + * @SwooleHandler("start") */ public function onStart() { global $terminal_id; @@ -87,14 +88,14 @@ class ServerEventHandler } /** - * @HandleEvent("shutdown") + * @SwooleHandler("shutdown") */ public function onShutdown() { Console::debug("正在关闭 Master 进程,pid=" . posix_getpid()); } /** - * @HandleEvent("WorkerStop") + * @SwooleHandler("WorkerStop") * @param $server * @param $worker_id */ @@ -103,7 +104,7 @@ class ServerEventHandler } /** - * @HandleEvent("WorkerStart") + * @SwooleHandler("WorkerStart") * @param Server $server * @param $worker_id */ @@ -205,12 +206,12 @@ class ServerEventHandler EventManager::registerTimerTick(); //启动计时器 //ZMBuf::unsetCache("wait_start"); set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); - $dispatcher = new EventDispatcher(OnWorkerStart::class); + $dispatcher = new EventDispatcher(OnStart::class); $dispatcher->setRuleFunction(function ($v) { return server()->worker_id === $v->worker_id || $v->worker_id === -1; }); $dispatcher->dispatchEvents($server, $worker_id); - Console::debug("@OnWorkerStart 执行完毕"); + Console::debug("@OnStart 执行完毕"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); @@ -243,7 +244,7 @@ class ServerEventHandler } /** - * @HandleEvent("message") + * @SwooleHandler("message") * @param $server * @param Frame $frame */ @@ -266,9 +267,9 @@ class ServerEventHandler } }); try { - $starttime = microtime(true); + //$starttime = microtime(true); $dispatcher->dispatchEvents($conn); - Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); + //Console::success("Used ".round((microtime(true) - $starttime) * 1000, 3)." ms!"); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; Console::error("Uncaught exception " . get_class($e) . " when calling \"message\": " . $error_msg); @@ -282,7 +283,7 @@ class ServerEventHandler } /** - * @HandleEvent("request") + * @SwooleHandler("request") * @param $request * @param $response */ @@ -356,7 +357,7 @@ class ServerEventHandler } /** - * @HandleEvent("open") + * @SwooleHandler("open") * @param $server * @param Request $request */ @@ -369,6 +370,7 @@ class ServerEventHandler $conn = ManagerGM::get($request->fd); set_coroutine_params(["server" => $server, "request" => $request, "connection" => $conn, "fd" => $request->fd]); $conn->setOption("connect_id", strval($request->header["x-self-id"]) ?? ""); + $dispatcher = new EventDispatcher(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { @@ -380,6 +382,11 @@ class ServerEventHandler } }); try { + if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) { + if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", $request->fd); + } + } $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; @@ -394,7 +401,7 @@ class ServerEventHandler } /** - * @HandleEvent("close") + * @SwooleHandler("close") * @param $server * @param $fd */ @@ -404,6 +411,8 @@ 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(OnSwooleEvent::class); $dispatcher->setRuleFunction(function ($v) { if ($v->getRule() == '') { @@ -415,6 +424,11 @@ class ServerEventHandler } }); try { + if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) { + if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", -1); + } + } $dispatcher->dispatchEvents($conn); } catch (Exception $e) { $error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"; @@ -429,7 +443,7 @@ class ServerEventHandler } /** - * @HandleEvent("pipeMessage") + * @SwooleHandler("pipeMessage") * @param $server * @param $src_worker_id * @param $data @@ -462,7 +476,7 @@ class ServerEventHandler } /** - * @HandleEvent("task") + * @SwooleHandler("task") */ public function onTask() { } @@ -504,7 +518,7 @@ class ServerEventHandler //加载插件 $plugins = ZMConfig::get("global", "modules") ?? []; - if (!isset($plugins["onebot"])) $plugins["onebot"] = true; + if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false]; if ($plugins["onebot"]) { $obj = new OnSwooleEvent(); @@ -514,6 +528,11 @@ class ServerEventHandler $obj->level = 99999; $obj->rule = 'connectIsQQ()'; EventManager::addEvent(OnSwooleEvent::class, $obj); + if ($plugins["onebot"]["single_bot_mode"]) { + LightCacheInside::set("connect", "conn_fd", -1); + } else { + LightCacheInside::set("connect", "conn_fd", -2); + } } //TODO: 编写加载外部插件的方式 diff --git a/src/ZM/Exception/DbException.php b/src/ZM/Exception/DbException.php index d663eacc..007d9ffe 100644 --- a/src/ZM/Exception/DbException.php +++ b/src/ZM/Exception/DbException.php @@ -4,9 +4,7 @@ namespace ZM\Exception; -use Exception; - class DbException extends ZMException { -} \ No newline at end of file +} diff --git a/src/ZM/Exception/InterruptException.php b/src/ZM/Exception/InterruptException.php index f5d00aa8..b5b5a5f8 100644 --- a/src/ZM/Exception/InterruptException.php +++ b/src/ZM/Exception/InterruptException.php @@ -4,8 +4,6 @@ namespace ZM\Exception; -use Exception; - class InterruptException extends ZMException { diff --git a/src/ZM/Exception/InvalidArgumentException.php b/src/ZM/Exception/InvalidArgumentException.php index 7ccc3b70..0052a29e 100644 --- a/src/ZM/Exception/InvalidArgumentException.php +++ b/src/ZM/Exception/InvalidArgumentException.php @@ -4,9 +4,7 @@ namespace ZM\Exception; -use Exception; - class InvalidArgumentException extends ZMException { -} \ No newline at end of file +} diff --git a/src/ZM/Exception/NotInitializedException.php b/src/ZM/Exception/NotInitializedException.php index f1bf753d..6f56e830 100644 --- a/src/ZM/Exception/NotInitializedException.php +++ b/src/ZM/Exception/NotInitializedException.php @@ -4,8 +4,6 @@ namespace ZM\Exception; -use Exception; - class NotInitializedException extends ZMException { diff --git a/src/ZM/Exception/RobotNotFoundException.php b/src/ZM/Exception/RobotNotFoundException.php index 9b734260..49880dda 100644 --- a/src/ZM/Exception/RobotNotFoundException.php +++ b/src/ZM/Exception/RobotNotFoundException.php @@ -4,7 +4,6 @@ namespace ZM\Exception; -use Exception; use Throwable; /** diff --git a/src/ZM/Exception/WaitTimeoutException.php b/src/ZM/Exception/WaitTimeoutException.php index c9d18255..76472561 100644 --- a/src/ZM/Exception/WaitTimeoutException.php +++ b/src/ZM/Exception/WaitTimeoutException.php @@ -4,7 +4,6 @@ namespace ZM\Exception; -use Exception; use Throwable; class WaitTimeoutException extends ZMException diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index b575733e..aef733f7 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -6,7 +6,7 @@ namespace ZM; use Doctrine\Common\Annotations\AnnotationReader; use Exception; -use ZM\Annotation\Swoole\ZMSetup; +use ZM\Annotation\Swoole\OnSetup; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; use ZM\Event\ServerEventHandler; @@ -20,7 +20,7 @@ use ReflectionException; use ReflectionMethod; use Swoole\Runtime; use Swoole\WebSocket\Server; -use ZM\Annotation\Swoole\HandleEvent; +use ZM\Annotation\Swoole\SwooleHandler; use ZM\Console\Console; use ZM\Utils\ZMUtil; @@ -67,7 +67,7 @@ class Framework self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); $this->server_set = ZMConfig::get("global", "swoole"); Console::init( - ZMConfig::get("global", "info_level"), + ZMConfig::get("global", "info_level") ?? 2, self::$server, $args["log-theme"] ?? "default", ($o = ZMConfig::get("console_color")) === false ? [] : $o @@ -150,11 +150,11 @@ class Framework $method_annotations = $reader->getMethodAnnotations($vs); if ($method_annotations != []) { $annotation = $method_annotations[0]; - if ($annotation instanceof HandleEvent) { + if ($annotation instanceof SwooleHandler) { $annotation->class = $v; $annotation->method = $vs->getName(); $event_list[strtolower($annotation->event)] = $annotation; - } elseif ($annotation instanceof ZMSetup) { + } elseif ($annotation instanceof OnSetup) { $annotation->class = $v; $annotation->method = $vs->getName(); $c = new $v(); diff --git a/src/ZM/Http/Response.php b/src/ZM/Http/Response.php index 2c937f89..c8cae4b8 100644 --- a/src/ZM/Http/Response.php +++ b/src/ZM/Http/Response.php @@ -4,6 +4,8 @@ namespace ZM\Http; +use ZM\Console\Console; + class Response { @@ -92,6 +94,7 @@ class Response */ public function status($http_code, $reason = null) { $this->status_code = $http_code; + Console::trace(); return $this->response->status($http_code, $reason); } @@ -184,7 +187,8 @@ class Response * @return mixed */ public function redirect($location, $http_code = null) { - return $this->redirect($location, $http_code); + $this->is_end = true; + return $this->response->redirect($location, $http_code); } /** diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index 902c1db7..c37207a4 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -21,7 +21,6 @@ use ZM\Utils\CoMessage; /** * Class QQBot * @package ZM\Module - * @ExternalModule("onebot") */ class QQBot { @@ -80,12 +79,13 @@ class QQBot //分发CQCommand事件 $dispatcher = new EventDispatcher(CQCommand::class); $dispatcher->setRuleFunction(function (CQCommand $v) use ($word) { - if ($v->match == "" && $v->pattern == "" && $v->regex == "") 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)) { + array_shift($word); ctx()->setCache("match", $word); return true; } elseif ($v->start_with != "" && mb_strpos(ctx()->getMessage(), $v->start_with) === 0) { @@ -94,6 +94,9 @@ class QQBot } elseif ($v->end_with != "" && strlen(ctx()->getMessage()) == (strripos(ctx()->getMessage(), $v->end_with) + strlen($v->end_with))) { ctx()->setCache("match", [substr(ctx()->getMessage(), 0, strripos(ctx()->getMessage(), $v->end_with))]); return true; + } elseif ($v->keyword != "" && mb_strpos(ctx()->getMessage(), $v->keyword) !== false) { + ctx()->setCache("match", explode($v->keyword, ctx()->getMessage())); + return true; }elseif ($v->pattern != "") { $match = matchArgs($v->pattern, ctx()->getMessage()); if($match !== false) { diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index b6f0c720..c28c61e5 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -92,7 +92,7 @@ class LightCache } elseif (is_bool($value)) { $data_type = "bool"; $value = json_encode($value); - }else { + } else { throw new Exception("Only can set string, array and int"); } try { @@ -126,7 +126,7 @@ class LightCache throw new Exception("Only can set string, array and int"); } try { - if(self::$kv_table->get($key) === false) return false; + if (self::$kv_table->get($key) === false) return false; return self::$kv_table->set($key, [ "value" => $value, "data_type" => $data_type @@ -170,7 +170,7 @@ class LightCache } public static function savePersistence() { - if(self::$kv_table === null) return; + if (self::$kv_table === null) return; $r = []; foreach (self::$kv_table as $k => $v) { if ($v["expire"] === -2) { @@ -179,8 +179,10 @@ class LightCache } } if(self::$config["persistence_path"] == "") return; - $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); - if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + if (file_exists(self::$config["persistence_path"])) { + $r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256)); + if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!"); + } } private static function checkExpire($key) { diff --git a/src/ZM/Store/LightCacheInside.php b/src/ZM/Store/LightCacheInside.php index f3838cc7..c161eca0 100644 --- a/src/ZM/Store/LightCacheInside.php +++ b/src/ZM/Store/LightCacheInside.php @@ -6,6 +6,7 @@ namespace ZM\Store; use Exception; use Swoole\Table; +use ZM\Exception\ZMException; class LightCacheInside { @@ -15,9 +16,11 @@ class LightCacheInside public static $last_error = ''; public static function init() { - self::$kv_table["wait_api"] = new Table(2, 0); + self::$kv_table["wait_api"] = new Table(3, 0); self::$kv_table["wait_api"]->column("value", Table::TYPE_STRING, 65536); - $result = self::$kv_table["wait_api"]->create(); + self::$kv_table["connect"] = new Table(8, 0); + self::$kv_table["connect"]->column("value", Table::TYPE_STRING, 256); + $result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create(); if ($result === false) { self::$last_error = '系统内存不足,申请失败'; return $result; @@ -25,8 +28,14 @@ class LightCacheInside return $result; } + /** + * @param string $table + * @param string $key + * @return mixed|null + * @throws ZMException + */ public static function get(string $table, string $key) { - if (!isset(self::$kv_table[$table])) throw new Exception("not initialized LightCache"); + if (!isset(self::$kv_table[$table])) throw new ZMException("not initialized LightCache"); $r = self::$kv_table[$table]->get($key); return $r === false ? null : json_decode($r["value"], true); } @@ -36,10 +45,10 @@ class LightCacheInside * @param string $key * @param string|array|int $value * @return mixed - * @throws Exception + * @throws ZMException */ public static function set(string $table, string $key, $value) { - if (self::$kv_table === null) throw new Exception("not initialized LightCache"); + if (self::$kv_table === null) throw new ZMException("not initialized LightCache"); try { return self::$kv_table[$table]->set($key, [ "value" => json_encode($value, 256) diff --git a/src/ZM/Store/ZMAtomic.php b/src/ZM/Store/ZMAtomic.php index 850a465e..b4e88ff8 100644 --- a/src/ZM/Store/ZMAtomic.php +++ b/src/ZM/Store/ZMAtomic.php @@ -29,6 +29,7 @@ class ZMAtomic } self::$atomics["stop_signal"] = new Atomic(0); self::$atomics["wait_msg_id"] = new Atomic(0); + self::$atomics["_event_id"] = new Atomic(0); for ($i = 0; $i < 10; ++$i) { self::$atomics["_tmp_" . $i] = new Atomic(0); } diff --git a/src/ZM/Utils/SingletonTrait.php b/src/ZM/Utils/SingletonTrait.php new file mode 100644 index 00000000..a6649b61 --- /dev/null +++ b/src/ZM/Utils/SingletonTrait.php @@ -0,0 +1,24 @@ +connections as $v) { - server()->close($v); - } LightCache::savePersistence(); //DataProvider::saveBuffer(); Timer::clearAll(); diff --git a/src/ZM/global_functions.php b/src/ZM/global_functions.php index 605c74dd..3a4a6971 100644 --- a/src/ZM/global_functions.php +++ b/src/ZM/global_functions.php @@ -1,16 +1,20 @@ $v) { - foreach($v as $ks => $vs) { + foreach (EventManager::$events as $event => $v) { + foreach ($v as $ks => $vs) { //echo get_class($vs).": ".$vs->class." => ".$vs->method.PHP_EOL; - if($vs->class == $s["class"] && $vs->method == $s["function"]) { - $list[get_class($vs)][]=$vs; + if ($vs->class == $s["class"] && $vs->method == $s["function"]) { + $list[get_class($vs)][] = $vs; } } } @@ -227,7 +231,7 @@ function ctx() { } } -function debug($msg) { Console::debug($msg); } +function zm_debug($msg) { Console::debug($msg); } function onebot_target_id_name($message_type) { return ($message_type == "group" ? "group_id" : "user_id"); @@ -268,6 +272,22 @@ function server() { return Framework::$server; } +/** + * @return ZMRobot + * @throws RobotNotFoundException + * @throws ZMException + */ +function bot() { + if (($conn = LightCacheInside::get("connect", "conn_fd")) == -2) { + return ZMRobot::getRandom(); + } elseif ($conn != -1) { + if (($obj = ManagerGM::get($conn)) !== null) return new ZMRobot($obj); + else throw new RobotNotFoundException("单机器人连接模式可能连接了多个机器人!"); + } else { + throw new RobotNotFoundException("没有任何机器人连接到框架!"); + } +} + diff --git a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php index d09b2d4b..3ade094b 100644 --- a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php +++ b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php @@ -9,7 +9,7 @@ use Module\Example\Hello; use PHPUnit\Framework\TestCase; use ReflectionException; use ZM\Annotation\AnnotationParser; -use ZM\Annotation\Swoole\OnWorkerStart; +use ZM\Annotation\Swoole\OnStart; use ZM\Console\Console; class AnnotationParserRegisterTest extends TestCase @@ -34,8 +34,8 @@ class AnnotationParserRegisterTest extends TestCase public function testAnnotation() { ob_start(); $gen = $this->parser->generateAnnotationEvents(); - $m = $gen[OnWorkerStart::class][0]->method; - $class = $gen[OnWorkerStart::class][0]->class; + $m = $gen[OnStart::class][0]->method; + $class = $gen[OnStart::class][0]->class; $c = new $class(); try { $c->$m();