diff --git a/composer.json b/composer.json index 91558a97..85aeec66 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "symfony/console": "^5.1", "symfony/polyfill-ctype": "^1.18", "zhamao/connection-manager": "^1.0", - "zhamao/console": "^1.0", + "zhamao/console": "*@dev", "zhamao/config": "^1.0", "zhamao/request": "^1.0", "symfony/routing": "^5.1" @@ -61,5 +61,11 @@ }, "require-dev": { "phpunit/phpunit": "^9.3" - } + }, + "repositories": [ + { + "type": "path", + "url": "/Users/jerry/project/git-project/zhamao-console" + } + ] } diff --git a/config/global.php b/config/global.php index 8a42c83d..c594efbc 100644 --- a/config/global.php +++ b/config/global.php @@ -1,4 +1,6 @@ set参数 */ $config['swoole'] = [ 'log_file' => $config['crash_dir'] . 'swoole_error.log', - 'worker_num' => 2, + 'worker_num' => 8, 'dispatch_mode' => 2, 'max_coroutine' => 30000, //'task_worker_num' => 4, @@ -37,7 +39,8 @@ $config['light_cache'] = [ "status" => true, "size" => 2048, //最多允许储存的条数(需要2的倍数) "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) - "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多) + "hash_conflict_proportion" => 0.6, //Hash冲突率(越大越好,但是需要的内存更多) + "persistence_path" => $config['zm_data']."_cache.json" ]; /** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ diff --git a/src/Module/Example/Hello.php b/src/Module/Example/Hello.php index bcb467b3..db3b718c 100644 --- a/src/Module/Example/Hello.php +++ b/src/Module/Example/Hello.php @@ -2,7 +2,7 @@ namespace Module\Example; -use ZM\Annotation\Swoole\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; use ZM\Annotation\Swoole\OnTick; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; @@ -10,6 +10,8 @@ use ZM\Annotation\CQ\CQCommand; use ZM\Annotation\Http\Middleware; use ZM\Annotation\Http\RequestMapping; use ZM\Annotation\Swoole\SwooleEvent; +use ZM\Store\LightCache; +use ZM\Store\ZMBuf; use ZM\Utils\ZMUtil; /** @@ -72,9 +74,9 @@ class Hello /** * 中间件测试的一个示例函数 * @RequestMapping("/httpTimer") - * @Middleware("timer") */ public function timer() { + ZMBuf::atomic("_tmp_2")->add(1); return "This page is used as testing TimerMiddleware! Do not use it in production."; } diff --git a/src/ZM/API/CQAPI.php b/src/ZM/API/CQAPI.php index 6e243c62..751a364a 100644 --- a/src/ZM/API/CQAPI.php +++ b/src/ZM/API/CQAPI.php @@ -7,6 +7,7 @@ use Co; use ZM\ConnectionManager\ConnectionObject; use ZM\Console\Console; use ZM\Event\EventHandler; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; trait CQAPI @@ -26,27 +27,20 @@ trait CQAPI } - public function processWebsocketAPI($connection, $reply, $function = null) { + public function processWebsocketAPI($connection, $reply, $function = false) { $api_id = ZMBuf::atomic("wait_msg_id")->get(); $reply["echo"] = $api_id; ZMBuf::atomic("wait_msg_id")->add(1); EventHandler::callCQAPISend($reply, $connection); - if (is_callable($function)) { - ZMBuf::appendKey("sent_api", $api_id, [ - "data" => $reply, - "time" => microtime(true), - "func" => $function, - "self_id" => $connection->getOption("connect_id") - ]); - } elseif ($function === true) { - ZMBuf::appendKey("sent_api", $api_id, [ + if ($function === true) { + LightCache::set("sent_api_".$api_id, [ "data" => $reply, "time" => microtime(true), "coroutine" => Co::getuid(), "self_id" => $connection->getOption("connect_id") ]); } else { - ZMBuf::appendKey("sent_api", $api_id, [ + LightCache::set("sent_api_".$api_id, [ "data" => $reply, "time" => microtime(true), "self_id" => $connection->getOption("connect_id") @@ -56,8 +50,8 @@ trait CQAPI if (server()->push($connection->getFd(), json_encode($reply))) { if ($function === true) { Co::suspend(); - $data = ZMBuf::get("sent_api")[$api_id]; - ZMBuf::unsetByValue("sent_api", $reply["echo"]); + $data = LightCache::get("sent_api_".$api_id); + LightCache::unset("sent_api_".$api_id); return isset($data['result']) ? $data['result'] : null; } return true; @@ -69,10 +63,10 @@ trait CQAPI "data" => null, "self_id" => $connection->getOption("connect_id") ]; - $s = ZMBuf::get("sent_api")[$reply["echo"]]; + $s = LightCache::get("sent_api_".$reply["echo"]); if (($s["func"] ?? null) !== null) call_user_func($s["func"], $response, $reply); - ZMBuf::unsetByValue("sent_api", $reply["echo"]); + LightCache::unset("sent_api_".$reply["echo"]); if ($function === true) return $response; return false; } diff --git a/src/ZM/Annotation/Swoole/OnStart.php b/src/ZM/Annotation/Swoole/OnWorkerStart.php similarity index 80% rename from src/ZM/Annotation/Swoole/OnStart.php rename to src/ZM/Annotation/Swoole/OnWorkerStart.php index 3c56ee5f..1eb0f14d 100644 --- a/src/ZM/Annotation/Swoole/OnStart.php +++ b/src/ZM/Annotation/Swoole/OnWorkerStart.php @@ -7,12 +7,12 @@ use Doctrine\Common\Annotations\Annotation\Target; use ZM\Annotation\AnnotationBase; /** - * Class OnStart + * Class OnWorkerStart * @package ZM\Annotation\Swoole * @Annotation * @Target("ALL") */ -class OnStart extends AnnotationBase +class OnWorkerStart extends AnnotationBase { /** * @var int diff --git a/src/ZM/Command/PureHttpCommand.php b/src/ZM/Command/PureHttpCommand.php index 94002c4d..d30ce4be 100644 --- a/src/ZM/Command/PureHttpCommand.php +++ b/src/ZM/Command/PureHttpCommand.php @@ -4,6 +4,8 @@ namespace ZM\Command; +use Swoole\Atomic; +use Swoole\Coroutine; use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; @@ -15,6 +17,7 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use ZM\Config\ZMConfig; use ZM\Console\Console; +use ZM\Store\ZMBuf; use ZM\Utils\HttpUtil; class PureHttpCommand extends Command @@ -37,9 +40,15 @@ class PureHttpCommand extends Command $host = $input->getOption("host") ?? $global["host"]; $port = $input->getOption("port") ?? $global["port"]; $server = new Server($host, $port); - Console::init(2, $server); + $server->set(ZMConfig::get("global", "swoole")); + Console::init(0, $server); + ZMBuf::$atomics["request"] = []; + for ($i = 0; $i < 32; ++$i) { + ZMBuf::$atomics["request"][$i] = new Atomic(0); + } $index = ["index.html", "index.htm"]; - $server->on("request", function (Request $request, Response $response) use ($input, $index){ + $server->on("request", function (Request $request, Response $response) use ($input, $index, $server) { + ZMBuf::$atomics["request"][$server->worker_id]->add(1); HttpUtil::handleStaticPage( $request->server["request_uri"], $response, @@ -47,10 +56,16 @@ class PureHttpCommand extends Command "document_root" => realpath($input->getArgument('dir') ?? '.'), "document_index" => $index ]); + echo "\r".Coroutine::stats()["coroutine_peak_num"]; }); - $server->on("start", function($server){ - Process::signal(SIGINT, function () use ($server){ + $server->on("start", function ($server) { + Process::signal(SIGINT, function () use ($server) { Console::warning("Server interrupted by keyboard."); + for ($i = 0; $i < 32; ++$i) { + $num = ZMBuf::$atomics["request"][$i]->get(); + if($num != 0) + echo "[$i]: ".$num."\n"; + } $server->shutdown(); $server->stop(); }); diff --git a/src/ZM/ConsoleApplication.php b/src/ZM/ConsoleApplication.php index 9614b11f..67ea073d 100644 --- a/src/ZM/ConsoleApplication.php +++ b/src/ZM/ConsoleApplication.php @@ -7,7 +7,6 @@ namespace ZM; use Exception; use Symfony\Component\Console\Command\Command; use TypeError; -use ZM\Command\BuildCommand; use ZM\Command\InitCommand; use ZM\Command\PureHttpCommand; use ZM\Command\RunServerCommand; @@ -25,6 +24,7 @@ class ConsoleApplication extends Application } public function initEnv() { + $this->selfCheck(); $this->addCommands([ new RunServerCommand(), //运行主服务的指令控制器 new InitCommand(), //初始化用的,用于项目初始化和phar初始化 @@ -77,4 +77,16 @@ class ConsoleApplication extends Application die("{$e->getMessage()} at {$e->getFile()}({$e->getLine()})"); } } + + private function selfCheck() { + if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); + if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); + //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); + if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); + if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); + //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); + //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); + //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); + return true; + } } diff --git a/src/ZM/Context/Context.php b/src/ZM/Context/Context.php index 850a4e43..f7d0ed0e 100644 --- a/src/ZM/Context/Context.php +++ b/src/ZM/Context/Context.php @@ -14,6 +14,7 @@ use ZM\Exception\InvalidArgumentException; use ZM\Exception\WaitTimeoutException; use ZM\Http\Response; use ZM\API\ZMRobot; +use ZM\Store\LightCache; use ZM\Store\ZMBuf; class Context implements ContextInterface @@ -153,17 +154,17 @@ class Context implements ContextInterface if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") { $hang[$hang["message_type"] . "_id"] = $this->getData()[$this->getData()["message_type"] . "_id"]; } - ZMBuf::appendKey("wait_api", $api_id, $hang); + LightCache::set("wait_api_".$api_id, $hang); $id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) { - $r = ZMBuf::get("wait_api")[$api_id] ?? null; - if ($r !== null) { + $r = LightCache::get("wait_api_".$api_id); + if (is_array($r)) { Co::resume($r["coroutine"]); } }); Co::suspend(); - $sess = ZMBuf::get("wait_api")[$api_id]; - ZMBuf::unsetByValue("wait_api", $api_id); + $sess = LightCache::get("wait_api_".$api_id); + LightCache::unset("wait_api_".$api_id); $result = $sess["result"]; if (isset($id)) swoole_timer_clear($id); if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt); diff --git a/src/ZM/DB/DB.php b/src/ZM/DB/DB.php index 6c288471..9163c44d 100644 --- a/src/ZM/DB/DB.php +++ b/src/ZM/DB/DB.php @@ -1,4 +1,4 @@ -get(); if ($conn === false) { @@ -76,13 +72,6 @@ class DB ZMBuf::$sql_pool->put($conn); return $result; } catch (DBException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 5) . - "] " . $line . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } Console::warning($e->getMessage()); throw $e; } @@ -96,9 +85,6 @@ class DB * @throws DbException */ public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) { - if (ZMBuf::get("sql_log") === true) { - $starttime = microtime(true); - } Console::debug("MySQL: ".$line." | ". implode(", ", $params)); try { $conn = ZMBuf::$sql_pool->get(); @@ -126,23 +112,9 @@ class DB //echo json_encode(debug_backtrace(), 128 | 256); } ZMBuf::$sql_pool->put($conn); - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } return $ps->fetchAll($fetch_mode); } } catch (DbException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } if(mb_strpos($e->getMessage(), "has gone away") !== false) { zm_sleep(0.2); Console::warning("Gone away of MySQL! retrying!"); @@ -151,13 +123,6 @@ class DB Console::warning($e->getMessage()); throw $e; } catch (PDOException $e) { - if (ZMBuf::get("sql_log") === true) { - $log = - "[" . date("Y-m-d H:i:s") . - " " . round(microtime(true) - $starttime, 4) . - "] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n"; - Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND); - } if(mb_strpos($e->getMessage(), "has gone away") !== false) { zm_sleep(0.2); Console::warning("Gone away of MySQL! retrying!"); diff --git a/src/ZM/Event/CQ/MessageEvent.php b/src/ZM/Event/CQ/MessageEvent.php index 734671a5..d128af8e 100644 --- a/src/ZM/Event/CQ/MessageEvent.php +++ b/src/ZM/Event/CQ/MessageEvent.php @@ -85,7 +85,6 @@ class MessageEvent } /** - * @throws AnnotationException * @noinspection PhpRedundantCatchClauseInspection */ public function onActivate() { @@ -122,7 +121,7 @@ class MessageEvent EventDispatcher::interrupt(); }); $r = $dispatcher->dispatchEvents($word); - if ($r === false) return; + if ($r === null) return; //分发CQMessage事件 $msg_dispatcher = new EventDispatcher(CQMessage::class); diff --git a/src/ZM/Event/EventDispatcher.php b/src/ZM/Event/EventDispatcher.php index 3ad6121f..2d32c271 100644 --- a/src/ZM/Event/EventDispatcher.php +++ b/src/ZM/Event/EventDispatcher.php @@ -7,7 +7,6 @@ namespace ZM\Event; use Doctrine\Common\Annotations\AnnotationException; use Exception; use ZM\Annotation\AnnotationBase; -use ZM\Annotation\Interfaces\Rule; use ZM\Exception\InterruptException; use ZM\Utils\ZMUtil; @@ -20,6 +19,9 @@ class EventDispatcher /** @var null|callable */ private $return_func = null; + /** + * @throws InterruptException + */ public static function interrupt() { throw new InterruptException('interrupt'); } @@ -46,12 +48,20 @@ class EventDispatcher } return true; } catch (InterruptException $e) { - return false; + return null; } catch (AnnotationException $e) { return false; } } + /** + * @param AnnotationBase|null $v + * @param null $rule_func + * @param mixed ...$params + * @return bool + * @throws AnnotationException + * @throws InterruptException + */ public function dispatchEvent(?AnnotationBase $v, $rule_func = null, ...$params) { $q_c = $v->class; $q_f = $v->method; diff --git a/src/ZM/Event/EventHandler.php b/src/ZM/Event/EventHandler.php index c3cbfcac..6636d2e5 100644 --- a/src/ZM/Event/EventHandler.php +++ b/src/ZM/Event/EventHandler.php @@ -22,6 +22,7 @@ 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; @@ -158,8 +159,8 @@ class EventHandler $status = $req["status"]; $retcode = $req["retcode"]; $data = $req["data"]; - if (isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) { - $origin = ZMBuf::get("sent_api")[$req["echo"]]; + 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, @@ -188,12 +189,12 @@ class EventHandler if (($origin["func"] ?? null) !== null) { call_user_func($origin["func"], $response, $origin["data"]); } elseif (($origin["coroutine"] ?? false) !== false) { - $p = ZMBuf::get("sent_api"); - $p[$req["echo"]]["result"] = $response; - ZMBuf::set("sent_api", $p); + $r = LightCache::get("sent_api_" . $req["echo"]); + $r["result"] = $response; + LightCache::set("sent_api_" . $req["echo"], $r); Co::resume($origin['coroutine']); } - ZMBuf::unsetByValue("sent_api", $req["echo"]); + LightCache::unset("sent_api_" . $req["echo"]); } } diff --git a/src/ZM/Event/ServerEventHandler.php b/src/ZM/Event/ServerEventHandler.php index 931641be..4551fedc 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\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; use ZM\Annotation\Swoole\SwooleEvent; use ZM\Config\ZMConfig; use ZM\ConnectionManager\ManagerGM; @@ -54,21 +54,23 @@ class ServerEventHandler try { Terminal::executeCommand($var, $r); } catch (Exception $e) { - Console::error("Uncaught exception ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + Console::error("Uncaught exception " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"); } catch (Error $e) { - Console::error("Uncaught error ".get_class($e).": ".$e->getMessage()." at ".$e->getFile()."(".$e->getLine().")"); + Console::error("Uncaught error " . get_class($e) . ": " . $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")"); } }); } Process::signal(SIGINT, function () use ($r) { + echo "\r"; Console::warning("Server interrupted by keyboard on Master."); if ((Framework::$server->inotify ?? null) !== null) /** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify); ZMUtil::stop(); }); - if(Framework::$argv["watch"]) { + if (Framework::$argv["watch"]) { if (extension_loaded('inotify')) { Console::warning("Enabled File watcher, do not use in production."); + /** @noinspection PhpUndefinedFieldInspection */ Framework::$server->inotify = $fd = inotify_init(); $this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd); Event::add($fd, function () use ($fd) { @@ -117,7 +119,7 @@ class ServerEventHandler if ($error["type"] != 0) { Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})"); } - DataProvider::saveBuffer(); + //DataProvider::saveBuffer(); /** @var Server $server */ if (server() === null) $server->shutdown(); else server()->shutdown(); @@ -125,7 +127,7 @@ class ServerEventHandler Console::info("Worker #{$server->worker_id} 启动中"); Framework::$server = $server; - ZMBuf::resetCache(); //清空变量缓存 + //ZMBuf::resetCache(); //清空变量缓存 //ZMBuf::set("wait_start", []); //添加队列,在workerStart运行完成前先让其他协程等待执行 foreach ($server->connections as $v) { $server->close($v); @@ -193,12 +195,12 @@ class ServerEventHandler EventManager::registerTimerTick(); //启动计时器 //ZMBuf::unsetCache("wait_start"); set_coroutine_params(["server" => $server, "worker_id" => $worker_id]); - $dispatcher = new EventDispatcher(OnStart::class); + $dispatcher = new EventDispatcher(OnWorkerStart::class); $dispatcher->setRuleFunction(function ($v) { return server()->worker_id === $v->worker_id || $v->worker_id === -1; }); $dispatcher->dispatchEvents($server, $worker_id); - Console::debug("@OnStart 执行完毕"); + Console::debug("@OnWorkerStart 执行完毕"); } catch (Exception $e) { Console::error("Worker加载出错!停止服务!"); Console::error($e->getMessage() . "\n" . $e->getTraceAsString()); @@ -289,9 +291,9 @@ class ServerEventHandler try { $no_interrupt = $dis->dispatchEvents($request, $response); - if (!$no_interrupt) { + if ($no_interrupt !== null) { $result = HttpUtil::parseUri($request, $response, $request->server["request_uri"], $node, $params); - if (!$result) { + if ($result === true) { ctx()->setCache("params", $params); $dispatcher = new EventDispatcher(RequestMapping::class); $div = new RequestMapping(); @@ -300,11 +302,13 @@ class ServerEventHandler $div->method = $node["method"]; $div->request_method = $node["request_method"]; $div->class = $node["class"]; + //Console::success("正在执行路由:".$node["method"]); $r = $dispatcher->dispatchEvent($div, null, $params, $request, $response); if (is_string($r) && !$response->isEnd()) $response->end($r); } } if (!$response->isEnd()) { + //Console::warning('返回了404'); HttpUtil::responseCodePage($response, 404); } } catch (Exception $e) { @@ -380,9 +384,10 @@ class ServerEventHandler * @param $fd */ public function onClose($server, $fd) { - Console::debug("Calling Swoole \"close\" event from fd=" . $fd); unset(Context::$context[Co::getCid()]); $conn = ManagerGM::get($fd); + 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->setRuleFunction(function ($v) { diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 339676e8..9583aa4b 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -65,8 +65,6 @@ class Framework } catch (ConnectionManager\TableException $e) { die($e->getMessage()); } - //start swoole Framework - $this->selfCheck(); try { self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port")); $this->server_set = ZMConfig::get("global", "swoole"); @@ -118,6 +116,7 @@ class Framework "size" => 2048, "max_strlen" => 4096, "hash_conflict_proportion" => 0.6, + "persistence_path" => realpath(DataProvider::getDataFolder()."_cache.json") ]); self::$server->start(); } catch (Exception $e) { @@ -127,18 +126,6 @@ class Framework } } - private function selfCheck() { - if (!extension_loaded("swoole")) die("Can not find swoole extension.\n"); - if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !"); - //if (!extension_loaded("gd")) die("Can not find gd extension.\n"); - if (!extension_loaded("sockets")) die("Can not find sockets extension.\n"); - if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n"); - //if (!function_exists("curl_exec")) die("Can not find curl extension.\n"); - //if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n"); - //if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n"); - return true; - } - /** * 从全局配置文件里读取注入系统事件的类 * @throws ReflectionException diff --git a/src/ZM/Http/StaticFileHandler.php b/src/ZM/Http/StaticFileHandler.php index efc3172e..e31407d0 100644 --- a/src/ZM/Http/StaticFileHandler.php +++ b/src/ZM/Http/StaticFileHandler.php @@ -4,10 +4,9 @@ namespace ZM\Http; +use ZM\Config\ZMConfig; use ZM\Console\Console; -use Framework\ZMBuf; use ZM\Utils\HttpUtil; -use ZM\Utils\ZMUtil; class StaticFileHandler { @@ -23,7 +22,7 @@ class StaticFileHandler } else { if(is_file($full_path)) { $exp = strtolower(pathinfo($full_path)['extension'] ?? "unknown"); - $response->setHeader("Content-Type", ZMBuf::config("file_header")[$exp] ?? "application/octet-stream"); + $response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream"); $response->end(file_get_contents($full_path)); return true; } diff --git a/src/ZM/Module/QQBot.php b/src/ZM/Module/QQBot.php index b50e7706..0de1be68 100644 --- a/src/ZM/Module/QQBot.php +++ b/src/ZM/Module/QQBot.php @@ -5,7 +5,8 @@ namespace ZM\Module; /** * Class QQBot - * @package ZM\Plugins + * @package ZM\Module + * @ExternalModule("qqbot") */ class QQBot { diff --git a/src/ZM/Store/LightCache.php b/src/ZM/Store/LightCache.php index 0de1a01c..964f8f71 100644 --- a/src/ZM/Store/LightCache.php +++ b/src/ZM/Store/LightCache.php @@ -6,6 +6,7 @@ namespace ZM\Store; use Exception; use Swoole\Table; +use ZM\Console\Console; class LightCache { @@ -14,13 +15,31 @@ class LightCache private static $config = []; + public static $last_error = ''; + public static function init($config) { self::$config = $config; self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]); self::$kv_table->column("value", Table::TYPE_STRING, $config["max_strlen"]); self::$kv_table->column("expire", Table::TYPE_INT); self::$kv_table->column("data_type", Table::TYPE_STRING, 12); - return self::$kv_table->create(); + $result = self::$kv_table->create(); + if ($result === true && isset($config["persistence_path"])) { + $r = json_decode(file_get_contents($config["persistence_path"]), true); + if ($r === null) $r = []; + foreach ($r as $k => $v) { + $write = self::set($k, $v); + Console::debug("Writing LightCache: " . $k); + if ($write === false) { + self::$last_error = '可能是由于 Hash 冲突过多导致动态空间无法分配内存'; + return false; + } + } + } + if ($result === false) { + self::$last_error = '系统内存不足,申请失败'; + } + return $result; } /** @@ -56,12 +75,14 @@ class LightCache */ public static function set(string $key, $value, int $expire = -1) { if (self::$kv_table === null) throw new Exception("not initialized LightCache"); - if (is_array($value) || is_int($value)) { + if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); if (strlen($value) >= self::$config["max_strlen"]) return false; $data_type = "json"; } elseif (is_string($value)) { $data_type = ""; + } elseif (is_int($value)) { + $data_type = "int"; } else { throw new Exception("Only can set string, array and int"); } @@ -97,20 +118,31 @@ class LightCache $r = []; $del = []; foreach (self::$kv_table as $k => $v) { - if ($v["expire"] <= time()) { - $del[]=$k; + if ($v["expire"] <= time() && $v["expire"] >= 0) { + $del[] = $k; continue; } $r[$k] = self::parseGet($v); } - foreach($del as $v) { + foreach ($del as $v) { self::unset($v); } return $r; } + public static function savePersistence() { + $r = []; + foreach (self::$kv_table as $k => $v) { + if ($v["expire"] === -2) { + $r[$k] = self::parseGet($v); + } + } + $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) { - if (($expire = self::$kv_table->get($key, "expire")) !== -1) { + if (($expire = self::$kv_table->get($key, "expire")) >= 0) { if ($expire <= time()) { self::$kv_table->del($key); } @@ -120,6 +152,7 @@ class LightCache private static function parseGet($r) { switch ($r["data_type"]) { case "json": + case "int": return json_decode($r["value"], true); case "": default: diff --git a/src/ZM/Store/Redis/Redis.php b/src/ZM/Store/Redis/Redis.php new file mode 100644 index 00000000..b2bce3b0 --- /dev/null +++ b/src/ZM/Store/Redis/Redis.php @@ -0,0 +1,9 @@ += 3) - echo $head; - foreach (self::$buffer_list as $k => $v) { - Console::debug("Saving " . $k . " to " . $v); - self::setJsonData($v, ZMBuf::get($k)); - } - foreach (ZMBuf::$events[OnSave::class] ?? [] as $v) { - $c = $v->class; - $method = $v->method; - $class = new $c(); - Console::debug("Calling @OnSave: $c -> $method"); - $class->$method(); - } - if (Console::getLevel() >= 3) - echo Console::setColor("saved", "blue") . PHP_EOL; - } - public static function getFrameworkLink() { return ZMConfig::get("global", "http_reverse_link"); } - public static function getJsonData(string $string) { - if (!file_exists(self::getDataConfig() . $string)) return []; - return json_decode(file_get_contents(self::getDataConfig() . $string), true); - } - - public static function setJsonData($filename, array $args) { - $pathinfo = pathinfo($filename); - if (!is_dir(self::getDataConfig() . $pathinfo["dirname"])) { - Console::debug("Making Directory: " . self::getDataConfig() . $pathinfo["dirname"]); - mkdir(self::getDataConfig() . $pathinfo["dirname"]); - } - $r = file_put_contents(self::getDataConfig() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING)); - if ($r === false) { - Console::warning("无法保存文件: " . $filename); - } - } - public static function getDataFolder() { return ZM_DATA; } diff --git a/src/ZM/Utils/HttpUtil.php b/src/ZM/Utils/HttpUtil.php index cc2ab53e..9e5ac510 100644 --- a/src/ZM/Utils/HttpUtil.php +++ b/src/ZM/Utils/HttpUtil.php @@ -52,10 +52,10 @@ class HttpUtil } if (ZMConfig::get("global", "static_file_server")["status"]) { HttpUtil::handleStaticPage($request->server["request_uri"], $response); - return true; + return null; } } - return false; + return !isset($node["route"]) ? false : true; } public static function getHttpCodePage(int $http_code) { diff --git a/src/ZM/Utils/Terminal.php b/src/ZM/Utils/Terminal.php index 70bb7f94..142a429b 100644 --- a/src/ZM/Utils/Terminal.php +++ b/src/ZM/Utils/Terminal.php @@ -5,6 +5,7 @@ namespace ZM\Utils; use Exception; +use Psy\Shell; use Swoole\Event; use ZM\Console\Console; use ZM\Framework; @@ -35,8 +36,9 @@ class Terminal $class->$function_name(); return true; case 'psysh': - if (Framework::$argv["disable-coroutine"]) - eval(\Psy\sh()); + if (Framework::$argv["disable-coroutine"]) { + (new Shell())->run(); + } else Console::error("Only \"--disable-coroutine\" mode can use psysh!!!"); return true; diff --git a/src/ZM/Utils/ZMUtil.php b/src/ZM/Utils/ZMUtil.php index 31882c37..8b2e1960 100644 --- a/src/ZM/Utils/ZMUtil.php +++ b/src/ZM/Utils/ZMUtil.php @@ -30,13 +30,14 @@ class ZMUtil public static function reload($delay = 800) { Console::info(Console::setColor("Reloading server...", "gold")); usleep($delay * 1000); - foreach (ZMBuf::get("wait_api", []) as $k => $v) { - if ($v["result"] === null) Co::resume($v["coroutine"]); + foreach (LightCache::getAll() as $k => $v) { + if (mb_substr($k, 0, 8) == "wait_api") + if ($v["result"] === null) Co::resume($v["coroutine"]); } foreach (server()->connections as $v) { server()->close($v); } - DataProvider::saveBuffer(); + //DataProvider::saveBuffer(); Timer::clearAll(); server()->reload(); } diff --git a/test/ZMTest/Mock/Context.php b/test/ZMTest/Mock/Context.php new file mode 100644 index 00000000..5992f718 --- /dev/null +++ b/test/ZMTest/Mock/Context.php @@ -0,0 +1,256 @@ +set参数 */ +$config['swoole'] = [ + 'log_file' => $config['crash_dir'] . 'swoole_error.log', + 'worker_num' => 8, + 'dispatch_mode' => 2, + 'max_coroutine' => 30000, + //'task_worker_num' => 4, + //'task_enable_coroutine' => true +]; + +/** 轻量字符串缓存,默认开启 */ +$config['light_cache'] = [ + "status" => true, + "size" => 2048, //最多允许储存的条数(需要2的倍数) + "max_strlen" => 4096, //单行字符串最大长度(需要2的倍数) + "hash_conflict_proportion" => 0.6 //Hash冲突率(越大越好,但是需要的内存更多) +]; + +/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */ +$config['sql_config'] = [ + 'sql_host' => '', + 'sql_port' => 3306, + 'sql_username' => 'name', + 'sql_database' => 'db_name', + 'sql_password' => '', + 'sql_enable_cache' => true, + 'sql_reset_cache' => '0300', + 'sql_options' => [ + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false + ], + 'sql_no_exception' => false, + 'sql_default_fetch_mode' => PDO::FETCH_ASSOC // added in 1.5.6 +]; + +/** CQHTTP连接约定的token */ +$config["access_token"] = ""; + +/** HTTP服务器固定请求头的返回 */ +$config['http_header'] = [ + 'X-Powered-By' => 'zhamao-framework', + 'Content-Type' => 'text/html; charset=utf-8' +]; + +/** HTTP服务器在指定状态码下回复的页面(默认) */ +$config['http_default_code_page'] = [ + '404' => '404.html' +]; + +/** zhamao-framework在框架启动时初始化的atomic们 */ +$config['init_atomics'] = [ + //'custom_atomic_name' => 0, //自定义添加的Atomic +]; + +/** 终端日志显示等级(0-4) */ +$config["info_level"] = 2; + +/** 自动保存计时器的缓存保存时间(秒) */ +$config['auto_save_interval'] = 900; + +/** 上下文接口类 implemented from ContextInterface */ +$config['context_class'] = \ZMTest\Mock\Context::class; + +/** 静态文件访问 */ +$config['static_file_server'] = [ + 'status' => false, + 'document_root' => realpath(__DIR__ . "/../") . '/resources/html', + 'document_index' => [ + 'index.html' + ] +]; + +/** 注册 Swoole Server 事件注解的类列表 */ +$config['server_event_handler_class'] = [ + \ZM\Event\ServerEventHandler::class, +]; + +/** 注册自定义指令的类 */ +$config['command_register_class'] = [ + //\Custom\Command\CustomCommand::class +]; + +/** 服务器启用的外部第三方和内部插件 */ +$config['plugins'] = [ + 'qqbot' => true, // QQ机器人事件解析器,如果取消此项则默认为 true 开启状态,否则你手动填写 false 才会关闭 +]; + +return $config; diff --git a/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php b/test/ZMTest/PassedTest/AnnotationParserRegisterTest.php index 3ade094b..d09b2d4b 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\OnStart; +use ZM\Annotation\Swoole\OnWorkerStart; 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[OnStart::class][0]->method; - $class = $gen[OnStart::class][0]->class; + $m = $gen[OnWorkerStart::class][0]->method; + $class = $gen[OnWorkerStart::class][0]->class; $c = new $class(); try { $c->$m(); diff --git a/test/ZMTest/Testing/ModuleTest.php b/test/ZMTest/Testing/ModuleTest.php new file mode 100644 index 00000000..a42644f6 --- /dev/null +++ b/test/ZMTest/Testing/ModuleTest.php @@ -0,0 +1,27 @@ +randNum(["随机数", "1", "5"]); + $out = ob_get_clean(); + $this->assertEquals("随机数是:1\n", $out); + } +} diff --git a/test/test.php b/test/test.php index 64918112..13ae52ed 100644 --- a/test/test.php +++ b/test/test.php @@ -25,7 +25,15 @@ $route = new Route( // ... -$routes->add('date', $route); +$routes->add('date', new Route( + '/archive/{month}', // path + array('controller' => 'showArchive', 'asd' => 'feswf'), // default values + array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requirements + array(), // options + '', // host + array(), // schemes + array() // methods +)); $route = new Route('/archive/test');