From 169a751e0f338761b67799afac67ebc4474ef490 Mon Sep 17 00:00:00 2001 From: whale Date: Wed, 29 Apr 2020 15:29:56 +0800 Subject: [PATCH] update to 1.2 version Generate systemd script Default info_level set to 2 Modify & add some comment for Example module Brand new Console Add daemon command argument Add #OnTick annotation Add ZMRobot API class --- README.md | 2 +- bin/systemd | 22 +- composer.json | 7 +- config/global.php | 2 +- src/Framework/Console.php | 156 ++++---- src/Framework/FrameworkLoader.php | 52 ++- src/Framework/InfoLevel.php | 10 - src/Framework/Logger.php | 18 - src/Framework/ZMBuf.php | 6 +- src/Framework/global_functions.php | 4 +- src/Module/Example/Hello.php | 11 +- src/ZM/API/CQ.php | 14 +- src/ZM/API/CQAPI.php | 13 +- src/ZM/Annotation/AnnotationParser.php | 20 +- src/ZM/Annotation/Swoole/OnTick.php | 25 ++ src/ZM/Connection/ConnectionManager.php | 6 +- src/ZM/Connection/WSConnection.php | 7 +- src/ZM/Context/Context.php | 11 + src/ZM/Context/ContextInterface.php | 4 + src/ZM/DB/DB.php | 26 +- src/ZM/DB/DeleteBody.php | 8 +- src/ZM/DB/InsertBody.php | 5 + src/ZM/DB/SelectBody.php | 21 + src/ZM/Event/EventHandler.php | 16 +- src/ZM/Event/Swoole/MessageEvent.php | 2 +- src/ZM/Event/Swoole/WorkerStartEvent.php | 8 + src/ZM/Exception/RobotNotFoundException.php | 20 + src/ZM/Utils/DataProvider.php | 38 +- src/ZM/Utils/ZMRobot.php | 412 ++++++++++++++++++++ src/ZM/Utils/ZMRobotExperiment.php | 104 +++++ src/ZM/Utils/ZMUtil.php | 22 +- 31 files changed, 872 insertions(+), 200 deletions(-) delete mode 100644 src/Framework/InfoLevel.php delete mode 100644 src/Framework/Logger.php create mode 100644 src/ZM/Annotation/Swoole/OnTick.php create mode 100644 src/ZM/Exception/RobotNotFoundException.php create mode 100644 src/ZM/Utils/ZMRobot.php create mode 100644 src/ZM/Utils/ZMRobotExperiment.php diff --git a/README.md b/README.md index fab52a9f..a1c06cb4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE) [![版本](https://img.shields.io/badge/version-1.1-green.svg)]() -[![goto counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/goto.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=goto) +[![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid) [![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO) 一个异步、多平台兼容的 **聊天机器人** 框架。 diff --git a/bin/systemd b/bin/systemd index ce044385..acdecac0 100644 --- a/bin/systemd +++ b/bin/systemd @@ -1,6 +1,5 @@ #!/usr/bin/env php - 0, //消息发送(调用send_*_msg的统计数量) 'reload_time' => 0, //调用reload功能统计数量 'wait_msg_id' => 0, //协程挂起id自增 - 'info_level' => 0, //终端显示的log等级 + 'info_level' => 2, //终端显示的log等级 ]; /** 自动保存的缓存保存时间(秒) */ diff --git a/src/Framework/Console.php b/src/Framework/Console.php index 7ed8f333..c459cef4 100755 --- a/src/Framework/Console.php +++ b/src/Framework/Console.php @@ -14,8 +14,7 @@ use Exception; class Console { - static function setColor($string, $color = "") - { + static function setColor($string, $color = "") { switch ($color) { case "red": return "\x1b[38;5;203m" . $string . "\x1b[m"; @@ -25,6 +24,7 @@ class Console return "\x1b[38;5;227m" . $string . "\x1b[m"; case "blue": return "\033[34m" . $string . "\033[0m"; + case "pink": // I really don't know what stupid color it is. case "lightpurple": return "\x1b[38;5;207m" . $string . "\x1b[m"; case "lightblue": @@ -33,8 +33,6 @@ class Console return "\x1b[38;5;214m" . $string . "\x1b[m"; case "gray": return "\x1b[38;5;59m" . $string . "\x1b[m"; - case "pink": - return "\x1b[38;5;207m" . $string . "\x1b[m"; case "lightlightblue": return "\x1b[38;5;63m" . $string . "\x1b[m"; default: @@ -42,9 +40,8 @@ class Console } } - static function error($obj, $head = null) - { - if ($head === null) $head = date("[H:i:s ") . "ERROR] "; + static function error($obj, $head = null) { + if ($head === null) $head = date("[H:i:s] ") . "[E] "; if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; @@ -58,90 +55,80 @@ class Console echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n"); } - static function warning($obj, $head = null) - { - if ($head === null) $head = date("[H:i:s") . " WARN] "; + static function warning($obj, $head = null) { + if ($head === null) $head = date("[H:i:s]") . " [W] "; if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; } - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; + if(ZMBuf::$atomics["info_level"]->get() >= 1) { + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n"); } - echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n"); } - static function info($obj, $head = null) - { - if ($head === null) $head = date("[H:i:s ") . "INFO] "; + static function info($obj, $head = null) { + if ($head === null) $head = date("[H:i:s] ") . "[I] "; if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; } - if (!is_string($obj)) { - if (isset($trace)) { - var_dump($obj); - return; - } else $obj = "{Object}"; + if(ZMBuf::$atomics["info_level"]->get() >= 2) { + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n"); } - echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n"); } - static function log($obj, $color = "") - { + static function success($obj, $head = null) { + if ($head === null) $head = date("[H:i:s] ") . "[S] "; + if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) { + $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; + $trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] "; + } + if(ZMBuf::$atomics["info_level"]->get() >= 2) { + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "green") . "\n"); + } + } + + static function verbose($obj, $head = null) { + if($head === null) $head = date("[H:i:s] ") . "[V] "; + if(ZMBuf::$atomics["info_level"]->get() >= 3) { + if (!is_string($obj)) { + if (isset($trace)) { + var_dump($obj); + return; + } else $obj = "{Object}"; + } + echo(self::setColor($head . ($trace ?? "") . $obj, "blue") . "\n"); + } + } + + static function debug($obj) { + debug($obj); + } + + static function log($obj, $color = "") { if (!is_string($obj)) var_dump($obj); else echo(self::setColor($obj, $color) . "\n"); } - static function msg($obj, $self_id = "") - { - if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) { - if (!isset($obj["post_type"])) { - switch ($obj["action"]) { - case "send_private_msg": - $msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue"); - $msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue"); - $msg .= Console::setColor(" > ", "gray"); - $msg .= $obj["params"]["message"]; - Console::log($msg); - break; - case "send_group_msg": - //TODO: 写新的控制台消息(API消息处理) - Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); - break; - case "send_discuss_msg": - Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); - break; - case "send_msg": - $obj["action"] = "send_" . $obj["message_type"] . "_msg"; - self::msg($obj); - break; - case "send_wechat_msg": - Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? "")); - break; - default: - break; - } - } else { - if ($obj["post_type"] == "message") { - switch ($obj["message_type"]) { - case "group": - // - //TODO: 写新的控制台消息(event处理) - case "private": - case "discuss": - case "wechat": - } - } - } - } - } - - static function stackTrace() - { + static function stackTrace() { $log = "Stack trace:\n"; $trace = debug_backtrace(); //array_shift($trace); @@ -165,8 +152,7 @@ class Console echo $log; } - static function listenConsole() - { + static function listenConsole() { if (in_array('--disable-console-input', FrameworkLoader::$argv)) { self::info("ConsoleCommand disabled."); return; @@ -183,10 +169,18 @@ class Console * @param string $cmd * @return bool */ - private static function executeCommand(string $cmd) - { + private static function executeCommand(string $cmd) { $it = explodeMsg($cmd); switch ($it[0] ?? '') { + case 'logtest': + Console::log(date("[H:i:s]"). " [L] This is normal msg. (0)"); + Console::error("This is error msg. (0)"); + Console::warning("This is warning msg. (1)"); + Console::info("This is info msg. (2)"); + Console::success("This is success msg. (2)"); + Console::verbose("This is verbose msg. (3)"); + Console::debug("This is debug msg. (4)"); + return true; case 'call': $class_name = $it[1]; $function_name = $it[2]; @@ -203,6 +197,9 @@ class Console case 'echo': Console::info($it[1]); return true; + case 'color': + Console::log($it[2], $it[1]); + return true; case 'stop': ZMUtil::stop(); return false; @@ -218,9 +215,8 @@ class Console } } - public static function withSleep(string $string, int $int) - { + public static function withSleep(string $string, int $int) { self::info($string); sleep($int); } -} \ No newline at end of file +} diff --git a/src/Framework/FrameworkLoader.php b/src/Framework/FrameworkLoader.php index 54334dca..68f414cb 100644 --- a/src/Framework/FrameworkLoader.php +++ b/src/Framework/FrameworkLoader.php @@ -3,7 +3,10 @@ namespace Framework; +use Co; +use Swoole\Http\Request; use Swoole\Runtime; +use Swoole\WebSocket\Frame; use ZM\Event\EventHandler; use Exception; use Swoole\WebSocket\Server; @@ -32,7 +35,8 @@ class FrameworkLoader /** @var Server */ private $server; - public function __construct($args = []) { + public function __construct($args = []) + { if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!"); self::$instance = $this; self::$argv = $args; @@ -52,17 +56,38 @@ class FrameworkLoader try { $this->server = new Server(self::$settings->get("host"), self::$settings->get("port")); if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1"); - $this->server->set(self::$settings->get("swoole")); + $settings = self::$settings->get("swoole"); + if (in_array("--daemon", $args)) { + $settings["daemonize"] = 1; + Console::log("已启用守护进程,输出重定向到 " . $settings["log_file"]); + self::$argv[] = "--disable-console-input"; + } + $this->server->set($settings); $this->server->on("WorkerStart", [$this, "onWorkerStart"]); - $this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); }); + $this->server->on("message", function ($server, Frame $frame) { + Console::debug("Calling Swoole \"message\" event from fd=" . $frame->fd); + EventHandler::callSwooleEvent("message", $server, $frame); + }); $this->server->on("request", function ($request, $response) { $response = new Response($response); + Console::debug("Receiving Http request event, cid=" . Co::getCid()); EventHandler::callSwooleEvent("request", $request, $response); }); - $this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); }); - $this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); }); + $this->server->on("open", function ($server, Request $request) { + Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd); + EventHandler::callSwooleEvent("open", $server, $request); + }); + $this->server->on("close", function ($server, $fd) { + Console::debug("Calling Swoole \"close\" event from fd=" . $fd); + EventHandler::callSwooleEvent("close", $server, $fd); + }); ZMBuf::initAtomic(); - Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port")); + if (in_array("--log-error", $args)) ZMBuf::$atomics["info_level"]->set(0); + if (in_array("--log-warning", $args)) ZMBuf::$atomics["info_level"]->set(1); + if (in_array("--log-info", $args)) ZMBuf::$atomics["info_level"]->set(2); + if (in_array("--log-verbose", $args)) ZMBuf::$atomics["info_level"]->set(3); + if (in_array("--log-debug", $args)) ZMBuf::$atomics["info_level"]->set(4); + Console::log("host: " . self::$settings->get("host") . ", port: " . self::$settings->get("port") . ", log_level: " . ZMBuf::$atomics["info_level"]->get()); $this->server->start(); } catch (Exception $e) { Console::error("Framework初始化出现错误,请检查!"); @@ -71,15 +96,18 @@ class FrameworkLoader } } - private function requireGlobalFunctions() { + private function requireGlobalFunctions() + { require __DIR__ . '/global_functions.php'; } - private function registerAutoloader(string $string) { + private function registerAutoloader(string $string) + { if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !"); } - private function defineProperties() { + private function defineProperties() + { define("ZM_START_TIME", microtime(true)); define("ZM_DATA", self::$settings->get("zm_data")); define("CONFIG_DIR", self::$settings->get("config_dir")); @@ -93,7 +121,8 @@ class FrameworkLoader define("ZM_MATCH_SECOND", 3); } - private function selfCheck() { + private function selfCheck() + { if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); @@ -105,7 +134,8 @@ class FrameworkLoader return true; } - public function onWorkerStart(\Swoole\Server $server, $worker_id) { + public function onWorkerStart(\Swoole\Server $server, $worker_id) + { self::$instance = $this; self::$run_time = microtime(true); EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id); diff --git a/src/Framework/InfoLevel.php b/src/Framework/InfoLevel.php deleted file mode 100644 index 58de3ab9..00000000 --- a/src/Framework/InfoLevel.php +++ /dev/null @@ -1,10 +0,0 @@ -get() == 1) - Console::log(date("[H:i:s ") . "DEBUG] " . $msg, 'gray'); + if (ZMBuf::$atomics["info_level"]->get() >= 4) + Console::log(date("[H:i:s] ") . "[D] " . $msg, 'gray'); } diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index 47cd7d02..ca632eb6 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -15,10 +15,12 @@ use ZM\ModBase; /** * Class Hello * @package Module\Example + * @since 1.0 */ class Hello extends ModBase { /** + * 在机器人连接后向终端输出信息 * @SwooleEventAt("open",rule="connectType:qq") * @param $conn */ @@ -26,6 +28,7 @@ class Hello extends ModBase Console::info("机器人 ".$conn->getQQ()." 已连接!"); } /** + * 向机器人发送"你好",即可回复这句话 * @CQCommand("你好") */ public function hello(){ @@ -33,14 +36,16 @@ class Hello extends ModBase } /** - * @RequestMapping("/test/ping") + * 中间件测试的一个示例函数 + * @RequestMapping("/httpTimer") * @Middleware("timer") */ - public function pong(){ - return "pong"; + public function timer(){ + return "This page is used as testing TimerMiddleware! Do not use it in production."; } /** + * 框架会默认关闭未知的WebSocket链接,因为这个绑定的事件,你可以根据你自己的需求进行修改 * @SwooleEventAt(type="open",rule="connectType:unknown") */ public function closeUnknownConn(){ diff --git a/src/ZM/API/CQ.php b/src/ZM/API/CQ.php index b62a6338..9b83f789 100644 --- a/src/ZM/API/CQ.php +++ b/src/ZM/API/CQ.php @@ -18,7 +18,7 @@ class CQ if (is_numeric($qq) || $qq === "all") { return "[CQ:at,qq=" . $qq . "]"; } - Console::error("传入的QQ号码($qq)错误!"); + Console::warning("传入的QQ号码($qq)错误!"); return " "; } @@ -31,7 +31,7 @@ class CQ if (is_numeric($id)) { return "[CQ:face,id=" . $id . "]"; } - Console::error("传入的face id($id)错误!"); + Console::warning("传入的face id($id)错误!"); return " "; } @@ -44,7 +44,7 @@ class CQ if (is_numeric($id)) { return "[CQ:emoji,id=" . $id . "]"; } - Console::error("传入的emoji id($id)错误!"); + Console::warning("传入的emoji id($id)错误!"); return " "; } @@ -66,7 +66,7 @@ class CQ if (is_numeric($id)) { return "[CQ:sface,id=" . $id . "]"; } - Console::error("传入的sface id($id)错误!"); + Console::warning("传入的sface id($id)错误!"); return " "; } @@ -151,7 +151,7 @@ class CQ return "[CQ:music,type=$type,id=$id_or_url]"; case "custom": if ($title === null || $audio === null) { - Console::error("传入CQ码实例的标题和音频链接不能为空!"); + Console::warning("传入CQ码实例的标题和音频链接不能为空!"); return " "; } if ($content === null) $c = ""; @@ -160,7 +160,7 @@ class CQ else $i = ",image=" . $image; return "[CQ:music,type=custom,url=" . $id_or_url . ",audio=" . $audio . ",title=" . $title . $c . $i . "]"; default: - Console::error("传入的music type($type)错误!"); + Console::warning("传入的music type($type)错误!"); return " "; } } @@ -217,4 +217,4 @@ class CQ } return $msg; } -} \ No newline at end of file +} diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index cd6edb56..b0b5da89 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -93,12 +93,12 @@ class CQAPI } } if ($find === null) { - Console::error("Unknown API " . $name); + Console::warning("Unknown API " . $name); return false; } $reply = ["action" => $find]; if (!is_array($arg[1])) { - Console::error("Error when parsing params. Please make sure your params is an array."); + Console::warning("Error when parsing params. Please make sure your params is an array."); return false; } if ($arg[1] != []) { @@ -107,7 +107,7 @@ class CQAPI if (!($arg[0] instanceof CQConnection)) { $robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]); if ($robot == []) { - Console::error("发送错误,机器人连接不存在!"); + Console::warning("发送错误,机器人连接不存在!"); return false; } $arg[0] = $robot[0]; @@ -195,7 +195,7 @@ class CQAPI * @param |null $function * @return bool */ - private static function processAPI($connection, $reply, $function = null) { + public static function processAPI($connection, $reply, $function = null) { $api_id = ZMBuf::$atomics["wait_msg_id"]->get(); $reply["echo"] = $api_id; ZMBuf::$atomics["wait_msg_id"]->add(1); @@ -222,7 +222,7 @@ class CQAPI ]); } if ($connection->push(json_encode($reply))) { - Console::msg($reply, $connection->getQQ()); + //Console::msg($reply, $connection->getQQ()); ZMBuf::$atomics["out_count"]->add(1); if ($function === true) { Co::suspend(); @@ -232,6 +232,7 @@ class CQAPI } return true; } else { + Console::warning("CQAPI send failed, websocket push error."); $response = [ "status" => "failed", "retcode" => 999, @@ -246,4 +247,4 @@ class CQAPI return false; } } -} \ No newline at end of file +} diff --git a/src/ZM/Annotation/AnnotationParser.php b/src/ZM/Annotation/AnnotationParser.php index 1471508b..24149c7a 100644 --- a/src/ZM/Annotation/AnnotationParser.php +++ b/src/ZM/Annotation/AnnotationParser.php @@ -10,14 +10,16 @@ use ReflectionException; use ReflectionMethod; use ZM\Annotation\CQ\{CQAfter, CQBefore, CQCommand, CQMessage, CQMetaEvent, CQNotice, CQRequest}; use ZM\Annotation\Http\{After, Before, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping}; +use Swoole\Timer; use ZM\Annotation\Interfaces\CustomAnnotation; use ZM\Annotation\Interfaces\Level; use ZM\Annotation\Module\{Closed, InitBuffer, SaveBuffer}; -use ZM\Annotation\Swoole\{OnStart, SwooleEventAfter, SwooleEventAt}; +use ZM\Annotation\Swoole\{OnStart, OnTick, SwooleEventAfter, SwooleEventAt}; use ZM\Annotation\Interfaces\Rule; use ZM\Connection\WSConnection; use ZM\Http\MiddlewareInterface; use ZM\Utils\DataProvider; +use ZM\Utils\ZMUtil; class AnnotationParser { @@ -45,13 +47,15 @@ class AnnotationParser if ($vs instanceof Closed) { continue 2; } elseif ($vs instanceof Controller) { + Console::debug("找到 Controller 中间件: ".$vs->class); $class_prefix = $vs->prefix; } elseif ($vs instanceof SaveBuffer) { + Console::debug("注册自动保存的缓存变量: ".$vs->buf_name." (Dir:".$vs->sub_folder.")"); DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder); } elseif ($vs instanceof InitBuffer) { ZMBuf::set($vs->buf_name, []); } elseif ($vs instanceof MiddlewareClass) { - Console::info("正在注册中间件 " . $vs->class); + Console::verbose("正在注册中间件 " . $vs->class); $result = [ "class" => "\\" . $reflection_class->getName() ]; @@ -95,13 +99,14 @@ class AnnotationParser elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss; elseif ($vss instanceof RequestMapping) { self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix); - if($middleware_addon !== null) + if ($middleware_addon !== null) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method] = $middleware_addon->middleware; } elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss; elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss; elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss; elseif ($vss instanceof OnStart) ZMBuf::$events[OnStart::class][] = $vss; elseif ($vss instanceof Middleware) ZMBuf::$events[MiddlewareInterface::class][$vss->class][$vss->method] = $vss->middleware; + elseif ($vss instanceof OnTick) self::addTimerTick($vss); } } } @@ -123,6 +128,10 @@ class AnnotationParser } } } + if (ZMBuf::isset("timer_count")) { + Console::info("Added " . ZMBuf::get("timer_count") . " timer(s)!"); + ZMBuf::unsetCache("timer_count"); + } } private static function getRuleCallback($rule_str) { @@ -306,4 +315,9 @@ class AnnotationParser $tree[] = &$items[$item['id']]; return $tree; } + + private static function addTimerTick(?OnTick $vss) { + ZMBuf::set("timer_count", ZMBuf::get("timer_count", 0) + 1); + Timer::tick($vss->tick_ms, [ZMUtil::getModInstance($vss->class), $vss->method]); + } } diff --git a/src/ZM/Annotation/Swoole/OnTick.php b/src/ZM/Annotation/Swoole/OnTick.php new file mode 100644 index 00000000..c69862a5 --- /dev/null +++ b/src/ZM/Annotation/Swoole/OnTick.php @@ -0,0 +1,25 @@ +server->push($this->fd, $data) === false) { $data = unicode_decode($data); - if ($push_error_record) Logger::writeSwooleLog("API push failed. Data: " . $data); - Console::error("websocket数据未成功推送,长度:" . strlen($data)); + if ($push_error_record) Console::warning("API push failed. Data: " . $data); + Console::warning("websocket数据未成功推送,长度:" . strlen($data)); return false; } return true; } -} \ No newline at end of file +} diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index a376622d..3c1e4408 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -7,7 +7,10 @@ namespace ZM\Context; use Swoole\Http\Request; use Swoole\WebSocket\Frame; use swoole_server; +use ZM\Connection\ConnectionManager; +use ZM\Connection\CQConnection; use ZM\Http\Response; +use ZM\Utils\ZMRobot; class Context implements ContextInterface { @@ -68,4 +71,12 @@ class Context implements ContextInterface public function getCid() { return $this->cid; } + + /** + * @return ZMRobot|null + */ + public function getRobot() { + $conn = ConnectionManager::get($this->getFrame()->fd); + return $conn instanceof CQConnection ? new ZMRobot($conn) : null; + } } diff --git a/src/ZM/Context/ContextInterface.php b/src/ZM/Context/ContextInterface.php index dc6981c7..5667cec4 100644 --- a/src/ZM/Context/ContextInterface.php +++ b/src/ZM/Context/ContextInterface.php @@ -8,6 +8,7 @@ use Swoole\Http\Request; use Swoole\WebSocket\Frame; use Swoole\WebSocket\Server; use ZM\Http\Response; +use ZM\Utils\ZMRobot; interface ContextInterface { @@ -30,4 +31,7 @@ interface ContextInterface /** @return Request */ public function getRequest(); + + /** @return ZMRobot */ + public function getRobot(); } diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index 12e314f5..d605714a 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -14,6 +14,9 @@ class DB { private static $table_list = []; + /** + * @throws DbException + */ public static function initTableList() { $result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []); foreach ($result as $v) { @@ -40,10 +43,19 @@ class DB return Table::getTableInstance($table_name); } + /** + * @param $line + * @throws DbException + */ public static function statement($line) { self::rawQuery($line, []); } + /** + * @param $line + * @return bool + * @throws DbException + */ public static function unprepared($line) { if (ZMBuf::get("sql_log") === true) { $starttime = microtime(true); @@ -64,11 +76,17 @@ class DB "] " . $line . " (Error:" . $e->getMessage() . ")\n"; Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); } - Console::error($e->getMessage()); - return false; + Console::warning($e->getMessage()); + throw $e; } } + /** + * @param string $line + * @param array $params + * @return mixed + * @throws DbException + */ public static function rawQuery(string $line, $params = []) { if (ZMBuf::get("sql_log") === true) { $starttime = microtime(true); @@ -113,8 +131,8 @@ class DB "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); } - Console::error($e->getMessage()); - return false; + Console::warning($e->getMessage()); + throw $e; } } diff --git a/src/ZM/DB/DeleteBody.php b/src/ZM/DB/DeleteBody.php index bc6e590b..11812d83 100644 --- a/src/ZM/DB/DeleteBody.php +++ b/src/ZM/DB/DeleteBody.php @@ -4,6 +4,8 @@ namespace ZM\DB; +use ZM\Exception\DbException; + class DeleteBody { use WhereBody; @@ -21,8 +23,12 @@ class DeleteBody $this->table = $table; } + /** + * @return mixed + * @throws DbException + */ public function save() { list($sql, $param) = $this->getWhereSQL(); return DB::rawQuery("DELETE FROM " . $this->table->getTableName() . " WHERE " . $sql, $param); } -} \ No newline at end of file +} diff --git a/src/ZM/DB/InsertBody.php b/src/ZM/DB/InsertBody.php index 0b59d625..3b866428 100644 --- a/src/ZM/DB/InsertBody.php +++ b/src/ZM/DB/InsertBody.php @@ -4,6 +4,8 @@ namespace ZM\DB; +use ZM\Exception\DbException; + class InsertBody { /** @@ -22,6 +24,9 @@ class InsertBody $this->row = $row; } + /** + * @throws DbException + */ public function save() { DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, count($this->row), '?')).')', $this->row); } diff --git a/src/ZM/DB/SelectBody.php b/src/ZM/DB/SelectBody.php index b3159686..1982c7e4 100644 --- a/src/ZM/DB/SelectBody.php +++ b/src/ZM/DB/SelectBody.php @@ -5,6 +5,7 @@ namespace ZM\DB; use Framework\Console; +use ZM\Exception\DbException; class SelectBody { @@ -23,8 +24,16 @@ class SelectBody $this->select_thing = $select_thing; } + /** + * @return null + * @throws DbException + */ public function get() { return $this->fetchAll(); } + /** + * @return null + * @throws DbException + */ public function fetchAll() { if ($this->table->isCacheEnabled()) { $rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing)); @@ -40,10 +49,19 @@ class SelectBody return $this->getResult(); } + /** + * @return mixed|null + * @throws DbException + */ public function fetchFirst() { return $this->fetchAll()[0] ?? null; } + /** + * @param null $key + * @return mixed|null + * @throws DbException + */ public function value($key = null) { $r = $this->fetchFirst(); if ($r === null) return null; @@ -52,6 +70,9 @@ class SelectBody else return $r[$key] ?? null; } + /** + * @throws DbException + */ public function execute() { $str = $this->queryPrepare(); $this->result = DB::rawQuery($str[0], $str[1]); diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php index 559222d7..9fb8b540 100644 --- a/src/ZM/Event/EventHandler.php +++ b/src/ZM/Event/EventHandler.php @@ -11,6 +11,7 @@ use Framework\Console; use Framework\ZMBuf; use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent}; use ZM\Http\Response; +use ZM\Utils\DataProvider; use ZM\Utils\ZMUtil; class EventHandler @@ -21,15 +22,22 @@ class EventHandler switch ($event_name) { case "workerstart": try { + register_shutdown_function(function () { + $error = error_get_last(); + if ($error["type"] != 0) { + Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); + } + DataProvider::saveBuffer(); + ZMBuf::$server->shutdown(); + }); (new WorkerStartEvent($param0, $param1))->onActivate()->onAfter(); Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); - ZMUtil::stop(); return; - } catch(Error $e) { + } catch (Error $e) { var_export($e); ZMUtil::stop(); } @@ -43,8 +51,8 @@ class EventHandler } 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"] + Console::info($param0->server["remote_addr"] . ":" . $param0->server["remote_port"] . + " [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"] ); if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage()); Console::error("Internal server error (500), caused by uncaught exception."); diff --git a/src/ZM/Event/Swoole/MessageEvent.php b/src/ZM/Event/Swoole/MessageEvent.php index 6739a310..6288c962 100644 --- a/src/ZM/Event/Swoole/MessageEvent.php +++ b/src/ZM/Event/Swoole/MessageEvent.php @@ -60,7 +60,7 @@ class MessageEvent implements SwooleEvent } } } catch (Exception $e) { - Console::error("出现错误: " . $e->getMessage()); + Console::warning("Websocket message event exception: " . $e->getMessage()); } return $this; } diff --git a/src/ZM/Event/Swoole/WorkerStartEvent.php b/src/ZM/Event/Swoole/WorkerStartEvent.php index b2caa52f..73122f77 100644 --- a/src/ZM/Event/Swoole/WorkerStartEvent.php +++ b/src/ZM/Event/Swoole/WorkerStartEvent.php @@ -9,6 +9,7 @@ use Doctrine\Common\Annotations\AnnotationException; use Exception; use ReflectionException; use Swoole\Coroutine; +use Swoole\Process; use Swoole\Timer; use ZM\Annotation\AnnotationBase; use ZM\Annotation\AnnotationParser; @@ -21,10 +22,12 @@ use Framework\Console; use Framework\GlobalConfig; use Framework\ZMBuf; use Swoole\Server; +use ZM\Exception\DbException; use ZM\ModBase; use ZM\ModHandleType; use ZM\Utils\DataProvider; use ZM\Utils\SQLPool; +use ZM\Utils\ZMUtil; class WorkerStartEvent implements SwooleEvent { @@ -43,9 +46,14 @@ class WorkerStartEvent implements SwooleEvent * @return WorkerStartEvent * @throws AnnotationException * @throws ReflectionException + * @throws DbException */ public function onActivate(): WorkerStartEvent { Console::info("Worker启动中"); + Process::signal(SIGINT, function (){ + Console::warning("Server interrupted by keyboard."); + ZMUtil::stop(true); + }); ZMBuf::resetCache(); //清空变量缓存 ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 $this->resetConnections();//释放所有与framework的连接 diff --git a/src/ZM/Exception/RobotNotFoundException.php b/src/ZM/Exception/RobotNotFoundException.php new file mode 100644 index 00000000..7e63c524 --- /dev/null +++ b/src/ZM/Exception/RobotNotFoundException.php @@ -0,0 +1,20 @@ + $v) { + public static function saveBuffer() + { + $head = Console::setColor(date("[H:i:s] ") . "[V] Saving buffer......", "blue"); + if (ZMBuf::$atomics["info_level"]->get() >= 3) + echo $head; + foreach (self::$buffer_list as $k => $v) { self::setJsonData($v, ZMBuf::get($k)); } - echo Console::setColor("saved", "lightblue").PHP_EOL; + if (ZMBuf::$atomics["info_level"]->get() >= 3) + echo Console::setColor("saved", "blue") . PHP_EOL; } - public static function getFrameworkLink(){ + public static function getFrameworkLink() + { return ZMBuf::globals("http_reverse_link"); } - public static function getJsonData(string $string) { - if(!file_exists(self::getDataConfig().$string)) return []; - return json_decode(Co::readFile(self::getDataConfig().$string), true); + public static function getJsonData(string $string) + { + if (!file_exists(self::getDataConfig() . $string)) return []; + return json_decode(Co::readFile(self::getDataConfig() . $string), true); } - private static function setJsonData($filename, array $args) { + private static function setJsonData($filename, array $args) + { Co::writeFile(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING)); } - public static function getDataFolder() { + public static function getDataFolder() + { return ZM_DATA; } } diff --git a/src/ZM/Utils/ZMRobot.php b/src/ZM/Utils/ZMRobot.php new file mode 100644 index 00000000..f7676804 --- /dev/null +++ b/src/ZM/Utils/ZMRobot.php @@ -0,0 +1,412 @@ + $robot_id]); + if ($r == []) throw new RobotNotFoundException("机器人 " . $robot_id . " 未连接到框架!"); + return new ZMRobot($r[0]); + } + + /** + * @throws RobotNotFoundException + * @return ZMRobot + */ + public static function getRandom() { + $r = ConnectionManager::getByType("qq"); + if($r == []) throw new RobotNotFoundException("没有任何机器人连接到框架!"); + return new ZMRobot($r[array_rand($r)]); + } + + public function __construct(CQConnection $connection) { + $this->connection = $connection; + } + + public function setCallback($callback = true) { + $this->callback = $callback; + return $this; + } + + public function setPrefix($prefix = self::API_NORMAL) { + $this->prefix = $prefix; + return $this; + } + + public function sendPrivateMsg($user_id, $message, $auto_escape = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + public function sendGroupMsg($group_id, $message, $auto_escape = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + public function sendDiscussMsg($discuss_id, $message, $auto_escape = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'discuss_id' => $discuss_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + public function sendMsg($message_type, $target_id, $message, $auto_escape = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_type' => $message_type, + ($message_type == 'private' ? 'user' : $message_type) . '_id' => $target_id, + 'message' => $message, + 'auto_escape' => $auto_escape + ] + ], $this->callback); + } + + public function deleteMsg($message_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'message_id' => $message_id + ] + ], $this->callback); + } + + public function sendLike($user_id, $times = 1) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'times' => $times + ] + ], $this->callback); + } + + public function setGroupKick($group_id, $user_id, $reject_add_request = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'reject_add_request' => $reject_add_request + ] + ], $this->callback); + } + + public function setGroupBan($group_id, $user_id, $duration = 1800) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'duration' => $duration + ] + ], $this->callback); + } + + public function setGroupAnonymousBan($group_id, $anonymous_or_flag, $duration = 1800) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + (is_string($anonymous_or_flag) ? 'flag' : 'anonymous') => $anonymous_or_flag, + 'duration' => $duration + ] + ], $this->callback); + } + + public function setGroupWholeBan($group_id, $enable = true) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'enable' => $enable + ] + ], $this->callback); + } + + public function setGroupAdmin($group_id, $user_id, $enable = true) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'enable' => $enable + ] + ], $this->callback); + } + + public function setGroupAnonymous($group_id, $enable = true) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'enable' => $enable + ] + ], $this->callback); + } + + public function setGroupCard($group_id, $user_id, $card = "") { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'card' => $card + ] + ], $this->callback); + } + + public function setGroupLeave($group_id, $is_dismiss = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'is_dismiss' => $is_dismiss + ] + ], $this->callback); + } + + public function setGroupSpecialTitle($group_id, $user_id, $special_title = "", $duration = -1) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'special_title' => $special_title, + 'duration' => $duration + ] + ], $this->callback); + } + + public function setDiscussLeave($discuss_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'discuss_id' => $discuss_id + ] + ], $this->callback); + } + + public function setFriendAddRequest($flag, $approve = true, $remark = "") { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'flag' => $flag, + 'approve' => $approve, + 'remark' => $remark + ] + ], $this->callback); + } + + public function setGroupAddRequest($flag, $sub_type, $approve = true, $reason = "") { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'flag' => $flag, + 'sub_type' => $sub_type, + 'approve' => $approve, + 'reason' => $reason + ] + ], $this->callback); + } + + public function getLoginInfo() { + return CQAPI::processAPI($this->connection, ['action' => $this->getActionName(__FUNCTION__)], $this->callback); + } + + public function getStrangerInfo($user_id, $no_cache = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + public function getFriendList() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getGroupList() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getGroupInfo($group_id, $no_cache = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + public function getGroupMemberInfo($group_id, $user_id, $no_cache = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'user_id' => $user_id, + 'no_cache' => $no_cache + ] + ], $this->callback); + } + + public function getGroupMemberList($group_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id + ] + ]); + } + + public function getCookie($domain = "") { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'domain' => $domain + ] + ]); + } + + public function getCsrfToken() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getCredentials($domain = "") { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'domain' => $domain + ] + ], $this->callback); + } + + public function getRecord($file, $out_format, $full_path = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'file' => $file, + 'out_format' => $out_format, + 'full_path' => $full_path + ] + ], $this->callback); + } + + public function getImage($file) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'file' => $file + ] + ], $this->callback); + } + + public function canSendImage() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function canSendRecord() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getStatus() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getVersionInfo() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function setRestartPlugin($delay = 0) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'delay' => $delay + ] + ], $this->callback); + } + + public function cleanDataDir($data_dir) { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__), + 'params' => [ + 'data_dir' => $data_dir + ] + ], $this->callback); + } + + public function cleanPluginLog() { + return CQAPI::processAPI($this->connection, [ + 'action' => $this->getActionName(__FUNCTION__) + ], $this->callback); + } + + public function getExperimentAPI() { + return new ZMRobotExperiment($this->connection, $this->callback, $this->prefix); + } + + private function getActionName(string $method) { + $prefix = ($this->prefix == self::API_ASYNC ? '_async' : ($this->prefix == self::API_RATE_LIMITED ? '_rate_limited' : '')); + $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method)); + return $prefix . $func_name; + } +} diff --git a/src/ZM/Utils/ZMRobotExperiment.php b/src/ZM/Utils/ZMRobotExperiment.php new file mode 100644 index 00000000..2043fc67 --- /dev/null +++ b/src/ZM/Utils/ZMRobotExperiment.php @@ -0,0 +1,104 @@ +connection = $connection; + $this->callback = $callback; + $this->prefix = $prefix; + } + + public function setCallback($callback = true) { + $this->callback = $callback; + return $this; + } + + public function setPrefix($prefix = ZMRobot::API_NORMAL) { + $this->prefix = $prefix; + return $this; + } + + public function getFriendList($flat = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'flat' => $flat + ] + ], $this->callback); + } + + public function getGroupInfo($group_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id + ] + ], $this->callback); + } + + public function getVipInfo($user_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'user_id' => $user_id + ] + ], $this->callback); + } + + public function getGroupNotice($group_id) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id + ] + ], $this->callback); + } + + public function sendGroupNotice($group_id, $title, $content) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'group_id' => $group_id, + 'title' => $title, + 'content' => $content + ] + ], $this->callback); + } + + public function setRestart($clean_log = false, $clean_cache = false, $clean_event = false) { + return CQAPI::processAPI($this->connection, [ + 'action' => '_' . $this->getActionName(__FUNCTION__), + 'params' => [ + 'clean_log' => $clean_log, + 'clean_cache' => $clean_cache, + 'clean_event' => $clean_event + ] + ], $this->callback); + } + + private function getActionName(string $method) { + $prefix = ($this->prefix == ZMRobot::API_ASYNC ? '_async' : ($this->prefix == ZMRobot::API_RATE_LIMITED ? '_rate_limited' : '')); + $func_name = strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $method)); + return $prefix . $func_name; + } +} diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index b2fef90c..dd25953f 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -14,25 +14,26 @@ class ZMUtil * 检查workerStart是否运行结束 */ public static function checkWait() { - if(ZMBuf::isset("wait_start")) { + if (ZMBuf::isset("wait_start")) { ZMBuf::append("wait_start", Co::getCid()); Co::suspend(); } } - public static function stop() { + public static function stop($without_shutdown = false) { Console::info(Console::setColor("Stopping server...", "red")); foreach (ZMBuf::$server->connections as $v) { ZMBuf::$server->close($v); } DataProvider::saveBuffer(); - ZMBuf::$server->shutdown(); + if (!$without_shutdown) + ZMBuf::$server->shutdown(); ZMBuf::$server->stop(); } public static function getHttpCodePage(int $http_code) { - if(isset(ZMBuf::globals("http_default_code_page")[$http_code])) { - return Co::readFile(DataProvider::getResourceFolder()."html/".ZMBuf::globals("http_default_code_page")[$http_code]); + if (isset(ZMBuf::globals("http_default_code_page")[$http_code])) { + return Co::readFile(DataProvider::getResourceFolder() . "html/" . ZMBuf::globals("http_default_code_page")[$http_code]); } else return null; } @@ -68,4 +69,13 @@ class ZMUtil } return ["type" => $type, "params" => $array, "start" => $start, "end" => $end]; } -} \ No newline at end of file + + public static function getModInstance($class) { + if (!isset(ZMBuf::$instance[$class])) { + ZMBuf::$instance[$class] = new $class(); + return ZMBuf::$instance[$class]; + } else { + return ZMBuf::$instance[$class]; + } + } +}