mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-22 16:15:34 +08:00
update to v2.4.0 (build 399)
add CheckConfigCommand.php add config update record docs adjust swoole version to 4.5.0 fix stop and reload bugs add $_running_annotation add remote terminal update global config add timer tick exception handler add zm_xxx global functions add isAtMe(), splitCommand(), matchCommand() function for MessageUtil add workerAction(), sendActionToWorker(), resumeAllWorkerCoroutines() functions for ProcessManager optimize CQCommand match function add custom TerminalCommand annotation add TuringAPI add getReloadableFiles() function for ZMUtil
This commit is contained in:
118
src/ZM/API/TuringAPI.php
Normal file
118
src/ZM/API/TuringAPI.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class TuringAPI
|
||||
{
|
||||
/**
|
||||
* 请求图灵API,返回图灵的消息
|
||||
* @param $msg
|
||||
* @param $user_id
|
||||
* @param $api
|
||||
* @return string
|
||||
*/
|
||||
public static function getTuringMsg($msg, $user_id, $api) {
|
||||
$origin = $msg;
|
||||
if (($cq = CQ::getCQ($msg)) !== null) {//如有CQ码则去除
|
||||
if ($cq["type"] == "image") {
|
||||
$url = $cq["params"]["url"];
|
||||
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
|
||||
}
|
||||
$msg = trim($msg);
|
||||
}
|
||||
//构建将要发送的json包给图灵
|
||||
$content = [
|
||||
"reqType" => 0,
|
||||
"userInfo" => [
|
||||
"apiKey" => $api,
|
||||
"userId" => $user_id
|
||||
]
|
||||
];
|
||||
if ($msg != "") {
|
||||
$content["perception"]["inputText"]["text"] = $msg;
|
||||
}
|
||||
$msg = trim($msg);
|
||||
if (mb_strlen($msg) < 1 && !isset($url)) return "请说出你想说的话";
|
||||
if (isset($url)) {
|
||||
$content["perception"]["inputImage"]["url"] = $url;
|
||||
$content["reqType"] = 1;
|
||||
}
|
||||
if (!isset($content["perception"])) return "请说出你想说的话";
|
||||
$client = new Client("openapi.tuling123.com", 80);
|
||||
$client->setHeaders(["Content-type" => "application/json"]);
|
||||
$client->post("/openapi/api/v2", json_encode($content, JSON_UNESCAPED_UNICODE));
|
||||
$api_return = json_decode($client->body, true);
|
||||
if (!isset($api_return["intent"]["code"])) return "XD 哎呀,我脑子突然短路了,请稍后再问我吧!";
|
||||
$status = self::getResultStatus($api_return);
|
||||
if ($status !== true) {
|
||||
if ($status == "err:输入文本内容超长(上限150)") return "你的话太多了!!!";
|
||||
if ($api_return["intent"]["code"] == 4003) {
|
||||
return "哎呀,我刚才有点走神了,可能忘记你说什么了,可以重说一遍吗";
|
||||
}
|
||||
Console::error("图灵机器人发送错误!\n错误原始内容:" . $origin . "\n来自:" . $user_id . "\n错误信息:" . $status);
|
||||
//echo json_encode($r, 128|256);
|
||||
return "哎呀,我刚才有点走神了,要不一会儿换一种问题试试?";
|
||||
}
|
||||
$result = $api_return["results"];
|
||||
//Console::info(Console::setColor(json_encode($result, 128 | 256), "green"));
|
||||
$final = "";
|
||||
foreach ($result as $k => $v) {
|
||||
switch ($v["resultType"]) {
|
||||
case "url":
|
||||
$final .= "\n" . $v["values"]["url"];
|
||||
break;
|
||||
case "text":
|
||||
$final .= "\n" . $v["values"]["text"];
|
||||
break;
|
||||
case "image":
|
||||
$final .= "\n" . CQ::image($v["values"]["image"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trim($final);
|
||||
}
|
||||
|
||||
public static function getResultStatus($r) {
|
||||
switch ($r["intent"]["code"]) {
|
||||
case 5000:
|
||||
return "err:无解析结果";
|
||||
case 4000:
|
||||
case 6000:
|
||||
return "err:暂不支持该功能";
|
||||
case 4001:
|
||||
return "err:加密方式错误";
|
||||
case 4005:
|
||||
case 4002:
|
||||
return "err:无功能权限";
|
||||
case 4003:
|
||||
return "err:该apikey没有可用请求次数";
|
||||
case 4007:
|
||||
return "err:apikey不合法";
|
||||
case 4100:
|
||||
return "err:userid获取失败";
|
||||
case 4200:
|
||||
return "err:上传格式错误";
|
||||
case 4300:
|
||||
return "err:批量操作超过限制";
|
||||
case 4400:
|
||||
return "err:没有上传合法userid";
|
||||
case 4500:
|
||||
return "err:userid申请个数超过限制";
|
||||
case 4600:
|
||||
return "err:输入内容为空";
|
||||
case 4602:
|
||||
return "err:输入文本内容超长(上限150)";
|
||||
case 7002:
|
||||
return "err:上传信息失败";
|
||||
case 8008:
|
||||
return "err:服务器错误";
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/ZM/Annotation/Command/TerminalCommand.php
Normal file
27
src/ZM/Annotation/Command/TerminalCommand.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Command;
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
|
||||
/**
|
||||
* Class TerminalCommand
|
||||
* @package ZM\Annotation\Command
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class TerminalCommand
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $command;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $description = "";
|
||||
}
|
||||
56
src/ZM/Command/CheckConfigCommand.php
Normal file
56
src/ZM/Command/CheckConfigCommand.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CheckConfigCommand extends Command
|
||||
{
|
||||
protected static $defaultName = 'check-config';
|
||||
|
||||
private $need_update = false;
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("检查配置文件是否和框架当前版本有更新");
|
||||
}
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
if (LOAD_MODE !== 1) {
|
||||
$output->writeln("<error>仅限在Composer依赖模式中使用此命令!");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$current_cfg = WORKING_DIR . "/config/";
|
||||
$remote_cfg = include_once $current_cfg . "/vendor/zhamao/framework/config/global.php";
|
||||
if (file_exists($current_cfg . "global.php")) {
|
||||
$this->check($remote_cfg, "global.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.development.php")) {
|
||||
$this->check($remote_cfg, "global.development.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.staging.php")) {
|
||||
$this->check($remote_cfg, "global.staging.php", $output);
|
||||
}
|
||||
if (file_exists($current_cfg . "global.production.php")) {
|
||||
$this->check($remote_cfg, "global.production.php", $output);
|
||||
}
|
||||
if ($this->need_update === true) {
|
||||
$output->writeln("<comment>有配置文件需要更新,详情见文档 https://framework.zhamao.xin/update/config.md</comment>");
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function check($remote, $local, OutputInterface $out) {
|
||||
$local_file = include_once WORKING_DIR . "/config/".$local;
|
||||
foreach($remote as $k => $v) {
|
||||
if (!isset($local_file[$k])) {
|
||||
$out->writeln("<info>配置文件 ".$local . " 需要更新!</info>");
|
||||
$this->need_update = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class DaemonStopCommand extends DaemonCommand
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||
parent::execute($input, $output);
|
||||
system("kill -TERM " . intval($this->daemon_file["pid"]));
|
||||
system("kill -INT " . intval($this->daemon_file["pid"]));
|
||||
unlink(DataProvider::getWorkingDir() . "/.daemon_pid");
|
||||
$output->writeln("<info>成功停止!</info>");
|
||||
return Command::SUCCESS;
|
||||
|
||||
@@ -23,8 +23,11 @@ class RunServerCommand extends Command
|
||||
new InputOption("log-error", null, null, "调整消息等级到error (log-level=0)"),
|
||||
new InputOption("log-theme", null, InputOption::VALUE_REQUIRED, "改变终端的主题配色"),
|
||||
new InputOption("disable-console-input", null, null, "禁止终端输入内容 (废弃)"),
|
||||
new InputOption("remote-terminal", null, null, "启用远程终端,配置使用global.php中的"),
|
||||
new InputOption("disable-coroutine", null, null, "关闭协程Hook"),
|
||||
new InputOption("daemon", null, null, "以守护进程的方式运行框架"),
|
||||
new InputOption("worker-num", null, InputOption::VALUE_REQUIRED, "启动框架时运行的 Worker 进程数量"),
|
||||
new InputOption("task-worker-num", null, InputOption::VALUE_REQUIRED, "启动框架时运行的 TaskWorker 进程数量"),
|
||||
new InputOption("watch", null, null, "监听 src/ 目录的文件变化并热更新"),
|
||||
new InputOption("show-php-ver", null, null, "启动时显示PHP和Swoole版本"),
|
||||
new InputOption("env", null, InputOption::VALUE_REQUIRED, "设置环境类型 (production, development, staging)"),
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace ZM;
|
||||
|
||||
|
||||
use Exception;
|
||||
use ZM\Command\CheckConfigCommand;
|
||||
use ZM\Command\DaemonReloadCommand;
|
||||
use ZM\Command\DaemonStatusCommand;
|
||||
use ZM\Command\DaemonStopCommand;
|
||||
@@ -18,8 +19,8 @@ use ZM\Utils\DataProvider;
|
||||
|
||||
class ConsoleApplication extends Application
|
||||
{
|
||||
const VERSION_ID = 398;
|
||||
const VERSION = "2.3.5";
|
||||
const VERSION_ID = 399;
|
||||
const VERSION = "2.4.0";
|
||||
|
||||
public function __construct(string $name = 'UNKNOWN') {
|
||||
define("ZM_VERSION_ID", self::VERSION_ID);
|
||||
@@ -75,6 +76,9 @@ class ConsoleApplication extends Application
|
||||
new InitCommand(), //初始化用的,用于项目初始化和phar初始化
|
||||
new PureHttpCommand() //纯HTTP服务器指令
|
||||
]);
|
||||
if (LOAD_MODE === 1) {
|
||||
$this->add(new CheckConfigCommand());
|
||||
}
|
||||
/*
|
||||
$command_register = ZMConfig::get("global", "command_register_class") ?? [];
|
||||
foreach ($command_register as $v) {
|
||||
@@ -100,7 +104,7 @@ class ConsoleApplication extends Application
|
||||
|
||||
private function selfCheck(): bool {
|
||||
if (!extension_loaded("swoole")) die("Can not find swoole extension.\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/19\n");
|
||||
if (version_compare(SWOOLE_VERSION, "4.4.13") == -1) die("You must install swoole version >= 4.4.13 !");
|
||||
if (version_compare(SWOOLE_VERSION, "4.5.0") == -1) die("You must install swoole version >= 4.5.0 !");
|
||||
if (version_compare(PHP_VERSION, "7.2") == -1) die("PHP >= 7.2 required.");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ class Context implements ContextInterface
|
||||
* @throws WaitTimeoutException
|
||||
*/
|
||||
public function getArgs($mode, $prompt_msg) {
|
||||
$arg = ctx()->getCache("match");
|
||||
$arg = ctx()->getCache("match") ?? [];
|
||||
switch ($mode) {
|
||||
case ZM_MATCH_ALL:
|
||||
$p = $arg;
|
||||
|
||||
17
src/ZM/Entity/MatchResult.php
Normal file
17
src/ZM/Entity/MatchResult.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Entity;
|
||||
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class MatchResult
|
||||
{
|
||||
/** @var bool */
|
||||
public $status = false;
|
||||
/** @var CQCommand|null */
|
||||
public $object = null;
|
||||
/** @var array */
|
||||
public $match = [];
|
||||
}
|
||||
@@ -152,6 +152,7 @@ class EventDispatcher
|
||||
if ($before_result) {
|
||||
try {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
$q_o->_running_annotation = $v;
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
|
||||
$this->store = $q_o->$q_f(...$params);
|
||||
} catch (Exception $e) {
|
||||
@@ -188,6 +189,7 @@ class EventDispatcher
|
||||
return false;
|
||||
} else {
|
||||
$q_o = ZMUtil::getModInstance($q_c);
|
||||
$q_o->_running_annotation = $v;
|
||||
if ($this->log) Console::verbose("[事件分发{$this->eid}] 正在执行方法 " . $q_c . "::" . $q_f . " ...");
|
||||
$this->store = $q_o->$q_f(...$params);
|
||||
$this->status = self::STATUS_NORMAL;
|
||||
|
||||
@@ -17,13 +17,13 @@ use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Timer;
|
||||
use Throwable;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnPipeMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
@@ -49,9 +49,9 @@ use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\HttpUtil;
|
||||
use ZM\Utils\ProcessManager;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class ServerEventHandler
|
||||
@@ -83,7 +83,7 @@ class ServerEventHandler
|
||||
Process::signal(SIGINT, function () use ($r) {
|
||||
if (zm_atomic("_int_is_reload")->get() === 1) {
|
||||
zm_atomic("_int_is_reload")->set(0);
|
||||
ZMUtil::reload();
|
||||
\server()->reload();
|
||||
} else {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted(SIGINT) on Master.");
|
||||
@@ -133,22 +133,31 @@ class ServerEventHandler
|
||||
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
|
||||
LightCache::savePersistence();
|
||||
}
|
||||
Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
Console::verbose(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
|
||||
}
|
||||
|
||||
/**
|
||||
* @SwooleHandler("WorkerStart")
|
||||
* @param Server $server
|
||||
* @param $worker_id
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onWorkerStart(Server $server, $worker_id) {
|
||||
//if (ZMBuf::atomic("stop_signal")->get() != 0) return;
|
||||
|
||||
Process::signal(SIGINT, function () use ($worker_id, $server) {
|
||||
Timer::clearAll();
|
||||
ProcessManager::resumeAllWorkerCoroutines();
|
||||
Console::debug("正在关闭 " . ($server->taskworker ? "Task" : "") . "Worker 进程 " . Console::setColor("#" . \server()->worker_id, "gold") . TermColor::frontColor256(59) . ", pid=" . posix_getpid());
|
||||
server()->stop($worker_id);
|
||||
});
|
||||
unset(Context::$context[Coroutine::getCid()]);
|
||||
if ($server->taskworker === false) {
|
||||
Process::signal(SIGUSR1, function() use ($worker_id){
|
||||
Timer::clearAll();
|
||||
ProcessManager::resumeAllWorkerCoroutines();
|
||||
});
|
||||
zm_atomic("_#worker_".$worker_id)->set($server->worker_pid);
|
||||
try {
|
||||
register_shutdown_function(function () use ($server) {
|
||||
$error = error_get_last();
|
||||
@@ -456,8 +465,12 @@ class ServerEventHandler
|
||||
}
|
||||
});
|
||||
try {
|
||||
if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
|
||||
if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
$onebot_status = $obb_onebot["status"];
|
||||
if ($conn->getName() === 'qq' && $onebot_status === true) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", $request->fd);
|
||||
}
|
||||
}
|
||||
@@ -472,7 +485,6 @@ class ServerEventHandler
|
||||
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
//EventHandler::callSwooleEvent("open", $server, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -503,8 +515,11 @@ class ServerEventHandler
|
||||
}
|
||||
});
|
||||
try {
|
||||
if ($conn->getName() === 'qq' && ZMConfig::get("global", "modules")["onebot"]["status"] === true) {
|
||||
if (ZMConfig::get("global", "modules")["onebot"]["single_bot_mode"]) {
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
if ($conn->getName() === 'qq' && $obb_onebot["status"] === true) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", -1);
|
||||
}
|
||||
}
|
||||
@@ -528,70 +543,27 @@ class ServerEventHandler
|
||||
* @param $src_worker_id
|
||||
* @param $data
|
||||
* @throws Exception
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function onPipeMessage(Server $server, $src_worker_id, $data) {
|
||||
//var_dump($data, $server->worker_id);
|
||||
//unset(Context::$context[Co::getCid()]);
|
||||
$data = json_decode($data, true);
|
||||
switch ($data["action"] ?? '') {
|
||||
case "resume_ws_message":
|
||||
$obj = $data["data"];
|
||||
Co::resume($obj["coroutine"]);
|
||||
break;
|
||||
case "getWorkerCache":
|
||||
$r = WorkerCache::get($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "setWorkerCache":
|
||||
$r = WorkerCache::set($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "unsetWorkerCache":
|
||||
$r = WorkerCache::unset($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "hasKeyWorkerCache":
|
||||
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "asyncAddWorkerCache":
|
||||
WorkerCache::add($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSubWorkerCache":
|
||||
WorkerCache::sub($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSetWorkerCache":
|
||||
WorkerCache::set($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncUnsetWorkerCache":
|
||||
WorkerCache::unset($data["key"], true);
|
||||
break;
|
||||
case "addWorkerCache":
|
||||
$r = WorkerCache::add($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "subWorkerCache":
|
||||
$r = WorkerCache::sub($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "returnWorkerCache":
|
||||
WorkerCache::$transfer[$data["cid"]] = $data["value"];
|
||||
zm_resume($data["cid"]);
|
||||
break;
|
||||
default:
|
||||
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
|
||||
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
|
||||
return $v->action == $data["action"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($data);
|
||||
break;
|
||||
ProcessManager::workerAction($src_worker_id, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @SwooleHandler("beforeReload")
|
||||
*/
|
||||
public function onBeforeReload() {
|
||||
for($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
$pid = zm_atomic("_#worker_".$i)->get();
|
||||
Process::kill($pid, SIGUSR1);
|
||||
}
|
||||
|
||||
Console::info(Console::setColor("Reloading server...", "gold"));
|
||||
usleep(800 * 1000);
|
||||
LightCacheInside::unset("wait_api", "wait_api");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -693,18 +665,19 @@ class ServerEventHandler
|
||||
}
|
||||
|
||||
//加载插件
|
||||
$plugins = ZMConfig::get("global", "modules") ?? [];
|
||||
if (!isset($plugins["onebot"])) $plugins["onebot"] = ["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
$obb_onebot = ZMConfig::get("global", "onebot") ??
|
||||
ZMConfig::get("global", "modules")["onebot"] ??
|
||||
["status" => true, "single_bot_mode" => false, "message_level" => 99999];
|
||||
|
||||
if ($plugins["onebot"]) {
|
||||
if ($obb_onebot["status"]) {
|
||||
$obj = new OnSwooleEvent();
|
||||
$obj->class = QQBot::class;
|
||||
$obj->method = 'handle';
|
||||
$obj->method = 'handleByEvent';
|
||||
$obj->type = 'message';
|
||||
$obj->level = $plugins["onebot"]["message_level"] ?? 99999;
|
||||
$obj->level = $obb_onebot["message_level"] ?? 99999;
|
||||
$obj->rule = 'connectIsQQ()';
|
||||
EventManager::addEvent(OnSwooleEvent::class, $obj);
|
||||
if ($plugins["onebot"]["single_bot_mode"]) {
|
||||
if ($obb_onebot["single_bot_mode"]) {
|
||||
LightCacheInside::set("connect", "conn_fd", -1);
|
||||
} else {
|
||||
LightCacheInside::set("connect", "conn_fd", -2);
|
||||
|
||||
@@ -5,7 +5,9 @@ namespace ZM;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Swoole\Server\Port;
|
||||
use ZM\Annotation\Swoole\OnSetup;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
@@ -23,6 +25,7 @@ use Swoole\Runtime;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Annotation\Swoole\SwooleHandler;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\Terminal;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class Framework
|
||||
@@ -35,11 +38,16 @@ class Framework
|
||||
* @var Server
|
||||
*/
|
||||
public static $server;
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public static $loaded_files = [];
|
||||
/**
|
||||
* @var array|bool|mixed|null
|
||||
*/
|
||||
private $server_set;
|
||||
|
||||
/** @noinspection PhpUnusedParameterInspection */
|
||||
public function __construct($args = []) {
|
||||
$tty_width = $this->getTtyWidth();
|
||||
|
||||
@@ -54,7 +62,6 @@ class Framework
|
||||
//定义常量
|
||||
include_once "global_defines.php";
|
||||
|
||||
ZMAtomic::init();
|
||||
try {
|
||||
$sw = ZMConfig::get("global");
|
||||
if (!is_dir($sw["zm_data"])) mkdir($sw["zm_data"]);
|
||||
@@ -86,18 +93,23 @@ class Framework
|
||||
date_default_timezone_set($timezone);
|
||||
|
||||
$this->server_set = ZMConfig::get("global", "swoole");
|
||||
$this->parseCliArgs(self::$argv);
|
||||
$this->server_set["log_level"] = SWOOLE_LOG_DEBUG;
|
||||
$add_port = ZMConfig::get("global", "remote_terminal")["status"] ?? false;
|
||||
|
||||
$this->parseCliArgs(self::$argv, $add_port);
|
||||
$worker = $this->server_set["worker_num"] ?? swoole_cpu_num();
|
||||
define("ZM_WORKER_NUM", $worker);
|
||||
ZMAtomic::init();
|
||||
// 打印初始信息
|
||||
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
|
||||
if (!isset(ZMConfig::get("global", "swoole")["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
else $out["worker"] = ZMConfig::get("global", "swoole")["worker_num"];
|
||||
if (!isset($this->server_set["worker_num"])) $out["worker"] = swoole_cpu_num() . " (auto)";
|
||||
else $out["worker"] = $this->server_set["worker_num"];
|
||||
$out["environment"] = $args["env"] === null ? "default" : $args["env"];
|
||||
$out["log_level"] = Console::getLevel();
|
||||
$out["version"] = ZM_VERSION . (LOAD_MODE == 0 ? (" (build " . ZM_VERSION_ID . ")") : "");
|
||||
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
|
||||
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
|
||||
$out["task_worker"] = ZMConfig::get("global", "swoole")["task_worker_num"];
|
||||
if (isset($this->server_set["task_worker_num"])) {
|
||||
$out["task_worker"] = $this->server_set["task_worker_num"];
|
||||
}
|
||||
if (ZMConfig::get("global", "sql_config")["sql_host"] !== "") {
|
||||
$conf = ZMConfig::get("global", "sql_config");
|
||||
@@ -114,12 +126,88 @@ class Framework
|
||||
$out["php_version"] = PHP_VERSION;
|
||||
$out["swoole_version"] = SWOOLE_VERSION;
|
||||
}
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal");
|
||||
$out["terminal"] = $conf["host"] . ":" . $conf["port"];
|
||||
}
|
||||
|
||||
$out["working_dir"] = DataProvider::getWorkingDir();
|
||||
self::printProps($out, $tty_width, $args["log-theme"] === null);
|
||||
|
||||
self::$server = new Server(ZMConfig::get("global", "host"), ZMConfig::get("global", "port"));
|
||||
|
||||
if ($add_port) {
|
||||
$conf = ZMConfig::get("global", "remote_terminal") ?? [
|
||||
'status' => true,
|
||||
'host' => '127.0.0.1',
|
||||
'port' => 20002,
|
||||
'token' => ''
|
||||
];
|
||||
$welcome_msg = Console::setColor("Welcome! You can use `help` for usage.", "green");
|
||||
/** @var Port $port */
|
||||
$port = self::$server->listen($conf["host"], $conf["port"], SWOOLE_SOCK_TCP);
|
||||
$port->set([
|
||||
'open_http_protocol' => false
|
||||
]);
|
||||
$port->on('connect', function (?\Swoole\Server $serv, $fd) use ($port, $welcome_msg, $conf) {
|
||||
ManagerGM::pushConnect($fd, "terminal");
|
||||
$serv->send($fd, file_get_contents(working_dir() . "/config/motd.txt"));
|
||||
if (!empty($conf["token"])) {
|
||||
$serv->send($fd, "Please input token: ");
|
||||
} else {
|
||||
$serv->send($fd, $welcome_msg . "\n>>> ");
|
||||
}
|
||||
});
|
||||
|
||||
$port->on('receive', function ($serv, $fd, $reactor_id, $data) use ($welcome_msg, $conf) {
|
||||
ob_start();
|
||||
try {
|
||||
$arr = LightCacheInside::get("light_array", "input_token") ?? [];
|
||||
if (empty($arr[$fd] ?? '')) {
|
||||
if ($conf["token"] != '') {
|
||||
$token = trim($data);
|
||||
if ($token === $conf["token"]) {
|
||||
SpinLock::transaction("input_token", function () use ($fd, $token) {
|
||||
$arr = LightCacheInside::get("light_array", "input_token");
|
||||
$arr[$fd] = $token;
|
||||
LightCacheInside::set("light_array", "input_token", $arr);
|
||||
});
|
||||
$serv->send($fd, Console::setColor("Auth success!!\n", "green"));
|
||||
$serv->send($fd, $welcome_msg . "\n>>> ");
|
||||
} else {
|
||||
$serv->send($fd, Console::setColor("Auth failed!!\n", "red"));
|
||||
$serv->close($fd);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (trim($data) == "exit" || trim($data) == "q") {
|
||||
$serv->send($fd, Console::setColor("Bye!\n", "blue"));
|
||||
$serv->close($fd);
|
||||
return;
|
||||
}
|
||||
Terminal::executeCommand(trim($data));
|
||||
} catch (Exception $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught exception " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
} catch (Error $e) {
|
||||
$error_msg = $e->getMessage() . " at " . $e->getFile() . "(" . $e->getLine() . ")";
|
||||
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
|
||||
Console::trace();
|
||||
}
|
||||
|
||||
$r = ob_get_clean();
|
||||
if (!empty($r)) $serv->send($fd, $r);
|
||||
if (!in_array(trim($data), ['r', 'reload', 'stop'])) $serv->send($fd, ">>> ");
|
||||
});
|
||||
|
||||
$port->on('close', function ($serv, $fd) {
|
||||
ManagerGM::popConnect($fd);
|
||||
//echo "Client: Close.\n";
|
||||
});
|
||||
}
|
||||
|
||||
self::$server->set($this->server_set);
|
||||
Console::setServer(self::$server);
|
||||
self::printMotd($tty_width);
|
||||
@@ -195,9 +283,9 @@ class Framework
|
||||
}
|
||||
|
||||
public function start() {
|
||||
self::$loaded_files = get_included_files();
|
||||
self::$server->start();
|
||||
zm_atomic("server_is_stopped")->set(1);
|
||||
Console::setLevel(0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -243,14 +331,29 @@ class Framework
|
||||
/**
|
||||
* 解析命令行的 $argv 参数们
|
||||
* @param $args
|
||||
* @throws Exception
|
||||
* @param $add_port
|
||||
*/
|
||||
private function parseCliArgs($args) {
|
||||
private function parseCliArgs($args, &$add_port) {
|
||||
$coroutine_mode = true;
|
||||
global $terminal_id;
|
||||
$terminal_id = uuidgen();
|
||||
foreach ($args as $x => $y) {
|
||||
switch ($x) {
|
||||
case 'worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set["worker_num"] = intval($y);
|
||||
} else {
|
||||
Console::warning("Invalid worker num! Turn to default value (".($this->server_set["worker_num"] ?? swoole_cpu_num()).")");
|
||||
}
|
||||
break;
|
||||
case 'task-worker-num':
|
||||
if (intval($y) >= 1 && intval($y) <= 1024) {
|
||||
$this->server_set["task_worker_num"] = intval($y);
|
||||
$this->server_set["task_enable_coroutine"] = true;
|
||||
} else {
|
||||
Console::warning("Invalid worker num! Turn to default value (0)");
|
||||
}
|
||||
break;
|
||||
case 'disable-coroutine':
|
||||
if ($y) {
|
||||
$coroutine_mode = false;
|
||||
@@ -296,6 +399,9 @@ class Framework
|
||||
Console::$theme = $y;
|
||||
}
|
||||
break;
|
||||
case 'remote-terminal':
|
||||
$add_port = true;
|
||||
break;
|
||||
case 'show-php-ver':
|
||||
default:
|
||||
//Console::info("Calculating ".$x);
|
||||
@@ -304,6 +410,7 @@ class Framework
|
||||
}
|
||||
}
|
||||
if ($coroutine_mode) Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
|
||||
else Runtime::enableCoroutine(false, SWOOLE_HOOK_ALL);
|
||||
}
|
||||
|
||||
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
|
||||
|
||||
@@ -15,6 +15,8 @@ use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Utils\CoMessage;
|
||||
use ZM\Utils\MessageUtil;
|
||||
use ZM\Utils\SingletonTrait;
|
||||
|
||||
/**
|
||||
* Class QQBot
|
||||
@@ -22,20 +24,28 @@ use ZM\Utils\CoMessage;
|
||||
*/
|
||||
class QQBot
|
||||
{
|
||||
use SingletonTrait;
|
||||
|
||||
public function handleByEvent() {
|
||||
$data = json_decode(context()->getFrame()->data, true);
|
||||
$this->handle($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param int $level
|
||||
* @throws InterruptException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle() {
|
||||
public function handle($data, $level = 0) {
|
||||
try {
|
||||
$data = json_decode(context()->getFrame()->data, true);
|
||||
if ($level > 10) return;
|
||||
set_coroutine_params(["data" => $data]);
|
||||
if (isset($data["post_type"])) {
|
||||
//echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
|
||||
ctx()->setCache("level", 0);
|
||||
ctx()->setCache("level", $level);
|
||||
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
|
||||
if ($data["post_type"] != "meta_event") {
|
||||
$r = $this->dispatchBeforeEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
$r = $this->dispatchBeforeEvents($data, "pre"); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
if ($r->store === "block") EventDispatcher::interrupt();
|
||||
}
|
||||
//Console::warning("最上数据包:".json_encode($data));
|
||||
@@ -43,6 +53,10 @@ class QQBot
|
||||
if (isset($data["echo"]) || isset($data["post_type"])) {
|
||||
if (CoMessage::resumeByWS()) EventDispatcher::interrupt();
|
||||
}
|
||||
if (($data["post_type"] ?? "meta_event") != "meta_event") {
|
||||
$r = $this->dispatchBeforeEvents($data, "post"); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
if ($r->store === "block") EventDispatcher::interrupt();
|
||||
}
|
||||
if (isset($data["post_type"])) $this->dispatchEvents($data);
|
||||
else $this->dispatchAPIResponse($data);
|
||||
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
|
||||
@@ -52,14 +66,21 @@ class QQBot
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @param $time
|
||||
* @return EventDispatcher
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dispatchBeforeEvents($data): EventDispatcher {
|
||||
public function dispatchBeforeEvents($data, $time): EventDispatcher {
|
||||
$before = new EventDispatcher(CQBefore::class);
|
||||
$before->setRuleFunction(function ($v) use ($data) {
|
||||
return $v->cq_event == $data["post_type"];
|
||||
});
|
||||
if ($time === "pre") {
|
||||
$before->setRuleFunction(function($v) use ($data){
|
||||
return $v->level >= 200 && $v->cq_event == $data["post_type"];
|
||||
});
|
||||
} else {
|
||||
$before->setRuleFunction(function($v) use ($data){
|
||||
return $v->level < 200 && $v->cq_event == $data["post_type"];
|
||||
});
|
||||
}
|
||||
$before->setReturnFunction(function ($result) {
|
||||
if (!$result) EventDispatcher::interrupt("block");
|
||||
});
|
||||
@@ -76,58 +97,20 @@ class QQBot
|
||||
//Console::warning("最xia数据包:".json_encode($data));
|
||||
switch ($data["post_type"]) {
|
||||
case "message":
|
||||
$word = explodeMsg(str_replace("\r", "", context()->getMessage()));
|
||||
if (empty($word)) $word = [""];
|
||||
if (count(explode("\n", $word[0])) >= 2) {
|
||||
$enter = explode("\n", context()->getMessage());
|
||||
$first = split_explode(" ", array_shift($enter));
|
||||
$word = array_merge($first, $enter);
|
||||
foreach ($word as $k => $v) {
|
||||
$word[$k] = trim($word[$k]);
|
||||
}
|
||||
}
|
||||
//分发CQCommand事件
|
||||
$dispatcher = new EventDispatcher(CQCommand::class);
|
||||
$dispatcher->setRuleFunction(function (CQCommand $v) use ($word) {
|
||||
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) {
|
||||
ctx()->setCache("match", [mb_substr(ctx()->getMessage(), mb_strlen($v->start_with))]);
|
||||
return true;
|
||||
} 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) {
|
||||
ctx()->setCache("match", $match);
|
||||
return true;
|
||||
}
|
||||
} elseif ($v->regex != "") {
|
||||
if (preg_match("/" . $v->regex . "/u", ctx()->getMessage(), $word2) != 0) {
|
||||
ctx()->setCache("match", $word2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
$dispatcher->setReturnFunction(function ($result) {
|
||||
if (is_string($result)) ctx()->reply($result);
|
||||
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
|
||||
});
|
||||
$dispatcher->dispatchEvents();
|
||||
if ($dispatcher->status == EventDispatcher::STATUS_INTERRUPTED) EventDispatcher::interrupt();
|
||||
zm_dump(ctx()->getData());
|
||||
$s = MessageUtil::matchCommand(ctx()->getMessage(), ctx()->getData());
|
||||
if ($s->status !== false) {
|
||||
if (!empty($s->match)) ctx()->setCache("match", $s->match);
|
||||
$dispatcher->dispatchEvent($s->object, null);
|
||||
if (is_string($dispatcher->store)) ctx()->reply($dispatcher->store);
|
||||
if (ctx()->getCache("has_reply") === true) EventDispatcher::interrupt();
|
||||
}
|
||||
|
||||
//分发CQMessage事件
|
||||
$msg_dispatcher = new EventDispatcher(CQMessage::class);
|
||||
|
||||
@@ -7,7 +7,6 @@ namespace ZM\Store;
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
use ZM\Annotation\Swoole\OnSave;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\ZMException;
|
||||
@@ -182,17 +181,12 @@ class LightCache
|
||||
}
|
||||
|
||||
/**
|
||||
* @param false $only_worker
|
||||
* 这个只能在唯一一个工作进程中执行
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function savePersistence($only_worker = false) {
|
||||
// 下面将OnSave激活一下
|
||||
if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) {
|
||||
$dispatcher = new EventDispatcher(OnSave::class);
|
||||
$dispatcher->dispatchEvents();
|
||||
}
|
||||
|
||||
if($only_worker) return;
|
||||
public static function savePersistence() {
|
||||
$dispatcher = new EventDispatcher(OnSave::class);
|
||||
$dispatcher->dispatchEvents();
|
||||
|
||||
if (self::$kv_table === null) return;
|
||||
$r = [];
|
||||
@@ -207,8 +201,7 @@ class LightCache
|
||||
$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\"!");
|
||||
}
|
||||
|
||||
|
||||
Console::verbose("Saved.");
|
||||
}
|
||||
|
||||
private static function checkExpire($key) {
|
||||
|
||||
@@ -18,6 +18,7 @@ class LightCacheInside
|
||||
self::createTable("wait_api", 3, 65536);
|
||||
self::createTable("connect", 3, 64); //用于存单机器人模式下的机器人fd的
|
||||
self::createTable("static_route", 64, 256);//用于存储
|
||||
self::createTable("light_array", 8, 512, 0.6);
|
||||
} catch (ZMException $e) {
|
||||
return false;
|
||||
} //用于存协程等待的状态内容的
|
||||
@@ -59,10 +60,11 @@ class LightCacheInside
|
||||
* @param $name
|
||||
* @param $size
|
||||
* @param $str_size
|
||||
* @param int $conflict_proportion
|
||||
* @throws ZMException
|
||||
*/
|
||||
private static function createTable($name, $size, $str_size) {
|
||||
self::$kv_table[$name] = new Table($size, 0);
|
||||
private static function createTable($name, $size, $str_size, $conflict_proportion = 0) {
|
||||
self::$kv_table[$name] = new Table($size, $conflict_proportion);
|
||||
self::$kv_table[$name]->column("value", Table::TYPE_STRING, $str_size);
|
||||
$r = self::$kv_table[$name]->create();
|
||||
if ($r === false) throw new ZMException("内存不足,创建静态表失败!");
|
||||
|
||||
@@ -32,6 +32,10 @@ class ZMAtomic
|
||||
self::$atomics["wait_msg_id"] = new Atomic(0);
|
||||
self::$atomics["_event_id"] = new Atomic(0);
|
||||
self::$atomics["server_is_stopped"] = new Atomic(0);
|
||||
for($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
self::$atomics["_#worker_".$i] = new Atomic(0);
|
||||
}
|
||||
echo ("初始化工作进程数量:".ZM_WORKER_NUM.PHP_EOL);
|
||||
for ($i = 0; $i < 10; ++$i) {
|
||||
self::$atomics["_tmp_" . $i] = new Atomic(0);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\MatchResult;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Requests\ZMRequest;
|
||||
|
||||
class MessageUtil
|
||||
@@ -55,6 +58,10 @@ class MessageUtil
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isAtMe($msg, $me_id) {
|
||||
return strpos($msg, CQ::at($me_id)) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过本地地址返回图片的 CQ 码
|
||||
* type == 0 : 返回图片的 base64 CQ 码
|
||||
@@ -76,4 +83,80 @@ class MessageUtil
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割字符,将用户消息通过空格或换行分割为数组
|
||||
* @param $msg
|
||||
* @return array|string[]
|
||||
*/
|
||||
public static function splitCommand($msg) {
|
||||
$word = explodeMsg(str_replace("\r", "", $msg));
|
||||
if (empty($word)) $word = [""];
|
||||
if (count(explode("\n", $word[0])) >= 2) {
|
||||
$enter = explode("\n", $msg);
|
||||
$first = split_explode(" ", array_shift($enter));
|
||||
$word = array_merge($first, $enter);
|
||||
foreach ($word as $k => $v) {
|
||||
$word[$k] = trim($word[$k]);
|
||||
}
|
||||
}
|
||||
return $word;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $msg
|
||||
* @param $obj
|
||||
* @return MatchResult
|
||||
*/
|
||||
public static function matchCommand($msg, $obj) {
|
||||
$ls = EventManager::$events[CQCommand::class] ?? [];
|
||||
$word = self::splitCommand($msg);
|
||||
$matched = new MatchResult();
|
||||
foreach ($ls as $k => $v) {
|
||||
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue;
|
||||
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $obj["user_id"])) &&
|
||||
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($obj["group_id"] ?? 0))) &&
|
||||
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $obj["message_type"]))
|
||||
) {
|
||||
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
|
||||
array_shift($word);
|
||||
$matched->match = $word;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->start_with != "" && mb_substr($msg, 0, mb_strlen($v->start_with)) === $v->start_with) {
|
||||
$matched->match = [mb_substr($msg, mb_strlen($v->start_with))];
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->end_with != "" && mb_substr($msg, 0 - mb_strlen($v->end_with)) === $v->end_with) {
|
||||
$matched->match = [substr($msg, 0, strripos($msg, $v->end_with))];
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->keyword != "" && mb_strpos($msg, $v->keyword) !== false) {
|
||||
$matched->match = explode($v->keyword, $msg);
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
} elseif ($v->pattern != "") {
|
||||
$match = matchArgs($v->pattern, $msg);
|
||||
if ($match !== false) {
|
||||
$matched->match = $match;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
}
|
||||
} elseif ($v->regex != "") {
|
||||
if (preg_match("/" . $v->regex . "/u", $msg, $word2) != 0) {
|
||||
$matched->match = $word2;
|
||||
$matched->object = $v;
|
||||
$matched->status = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $matched;
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,110 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Co;
|
||||
use ZM\Annotation\Swoole\OnPipeMessageEvent;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\WorkerCache;
|
||||
|
||||
class ProcessManager
|
||||
{
|
||||
public static function runOnTask($param, $timeout = 0.5, $dst_worker_id = -1) {
|
||||
return server()->taskwait([
|
||||
"action" => "runMethod",
|
||||
"class" => $param["class"],
|
||||
"method" => $param["method"],
|
||||
"params" => $param["params"]
|
||||
], $timeout, $dst_worker_id);
|
||||
public static function workerAction($src_worker_id, $data) {
|
||||
$server = server();
|
||||
switch ($data["action"] ?? '') {
|
||||
case "eval":
|
||||
eval($data["data"]);
|
||||
break;
|
||||
case "call_static":
|
||||
call_user_func_array([$data["data"]["class"], $data["data"]["method"]], $data["data"]["params"]);
|
||||
break;
|
||||
case "save_persistence":
|
||||
LightCache::savePersistence();
|
||||
break;
|
||||
case "resume_ws_message":
|
||||
$obj = $data["data"];
|
||||
Co::resume($obj["coroutine"]);
|
||||
break;
|
||||
case "getWorkerCache":
|
||||
$r = WorkerCache::get($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "setWorkerCache":
|
||||
$r = WorkerCache::set($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "unsetWorkerCache":
|
||||
$r = WorkerCache::unset($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "hasKeyWorkerCache":
|
||||
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "asyncAddWorkerCache":
|
||||
WorkerCache::add($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSubWorkerCache":
|
||||
WorkerCache::sub($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSetWorkerCache":
|
||||
WorkerCache::set($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncUnsetWorkerCache":
|
||||
WorkerCache::unset($data["key"], true);
|
||||
break;
|
||||
case "addWorkerCache":
|
||||
$r = WorkerCache::add($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "subWorkerCache":
|
||||
$r = WorkerCache::sub($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "returnWorkerCache":
|
||||
WorkerCache::$transfer[$data["cid"]] = $data["value"];
|
||||
zm_resume($data["cid"]);
|
||||
break;
|
||||
default:
|
||||
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
|
||||
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
|
||||
return $v->action == $data["action"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function sendActionToWorker($worker_id, $action, $data) {
|
||||
$obj = ["action" => $action, "data" => $data];
|
||||
if (server()->worker_id === -1 && server()->getManagerPid() != posix_getpid()) {
|
||||
Console::warning("Cannot send worker action from master or manager process!");
|
||||
return;
|
||||
}
|
||||
if (server()->worker_id == $worker_id) {
|
||||
self::workerAction($worker_id, $obj);
|
||||
} else {
|
||||
server()->sendMessage(json_encode($obj), $worker_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function resumeAllWorkerCoroutines() {
|
||||
if (server()->worker_id === -1) {
|
||||
Console::warning("Cannot call '".__FUNCTION__."' in non-worker process!");
|
||||
return;
|
||||
}
|
||||
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {
|
||||
if (($v["result"] ?? false) === null && isset($v["coroutine"], $v["worker_id"])) {
|
||||
if (server()->worker_id == $v["worker_id"]) Co::resume($v["coroutine"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,22 +6,38 @@ namespace ZM\Utils;
|
||||
|
||||
use Exception;
|
||||
use Psy\Shell;
|
||||
use Swoole\Event;
|
||||
use ZM\Annotation\Command\TerminalCommand;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Framework;
|
||||
|
||||
class Terminal
|
||||
{
|
||||
/**
|
||||
* @param string $cmd
|
||||
* @param $resource
|
||||
* @return bool
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection PhpUnused
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function executeCommand(string $cmd, $resource) {
|
||||
public static function executeCommand(string $cmd) {
|
||||
$it = explodeMsg($cmd);
|
||||
switch ($it[0] ?? '') {
|
||||
case 'help':
|
||||
$help[] = "exit | q:\t断开远程终端";
|
||||
$help[] = "logtest:\t输出所有可以打印的log等级示例消息,用于测试Console";
|
||||
$help[] = "call:\t\t用于执行不需要参数的动态函数,比如 `call \Module\Example\Hello hitokoto`";
|
||||
$help[] = "level:\t\t设置log等级,例如 `level 0|1|2|3|4`";
|
||||
$help[] = "bc:\t\teval执行代码,但输入必须是将代码base64之后的,如 `bc em1faW5mbygn5L2g5aW9Jyk7`";
|
||||
$help[] = "stop:\t\t停止服务器";
|
||||
$help[] = "reload | r:\t热重启用户编写的模块代码";
|
||||
foreach((EventManager::$events[TerminalCommand::class] ?? []) as $v) {
|
||||
$help[]=$v->command.":\t\t".(empty($v->description) ? "<用户自定义指令>" : $v->description);
|
||||
}
|
||||
echo implode("\n", $help) . PHP_EOL;
|
||||
return true;
|
||||
case 'logtest':
|
||||
Console::log(date("[H:i:s]") . " [L] This is normal msg. (0)");
|
||||
Console::error("This is error msg. (0)");
|
||||
@@ -35,7 +51,8 @@ class Terminal
|
||||
$class_name = $it[1];
|
||||
$function_name = $it[2];
|
||||
$class = new $class_name([]);
|
||||
$class->$function_name();
|
||||
$r = $class->$function_name();
|
||||
if (is_string($r)) Console::success($r);
|
||||
return true;
|
||||
case 'psysh':
|
||||
if (Framework::$argv["disable-coroutine"]) {
|
||||
@@ -43,6 +60,11 @@ class Terminal
|
||||
} else
|
||||
Console::error("Only \"--disable-coroutine\" mode can use psysh!!!");
|
||||
return true;
|
||||
case 'level':
|
||||
$level = intval(is_numeric($it[1] ?? 99) ? ($it[1] ?? 99) : 99);
|
||||
if ($level > 4 || $level < 0) Console::warning("Usage: 'level 0|1|2|3|4'");
|
||||
else Console::setLevel($level) || Console::success("Success!!");
|
||||
break;
|
||||
case 'bc':
|
||||
$code = base64_decode($it[1] ?? '', true);
|
||||
try {
|
||||
@@ -57,7 +79,6 @@ class Terminal
|
||||
Console::log($it[2], $it[1]);
|
||||
return true;
|
||||
case 'stop':
|
||||
Event::del($resource);
|
||||
ZMUtil::stop();
|
||||
return false;
|
||||
case 'reload':
|
||||
@@ -67,8 +88,35 @@ class Terminal
|
||||
case '':
|
||||
return true;
|
||||
default:
|
||||
Console::info("Command not found: " . $cmd);
|
||||
return true;
|
||||
$dispatcher = new EventDispatcher(TerminalCommand::class);
|
||||
$dispatcher->setRuleFunction(function ($v) use ($it) {
|
||||
/** @var TerminalCommand $v */
|
||||
return $v->command == $it[0];
|
||||
});
|
||||
$dispatcher->setReturnFunction(function () {
|
||||
EventDispatcher::interrupt('none');
|
||||
});
|
||||
$dispatcher->dispatchEvents($it);
|
||||
if ($dispatcher->store !== 'none') {
|
||||
Console::info("Command not found: " . $cmd);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function log($type, $log_msg) {
|
||||
ob_start();
|
||||
if (!in_array($type, ["log", "info", "debug", "success", "warning", "error", "verbose"])) {
|
||||
ob_get_clean();
|
||||
return;
|
||||
}
|
||||
Console::$type($log_msg);
|
||||
$r = ob_get_clean();
|
||||
$all = ManagerGM::getAllByName("terminal");
|
||||
foreach ($all as $k => $v) {
|
||||
server()->send($v->getFd(), "\r" . $r);
|
||||
server()->send($v->getFd(), ">>> ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,10 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Co;
|
||||
use Exception;
|
||||
use Swoole\Event;
|
||||
use Swoole\Timer;
|
||||
use Swoole\Process;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCache;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Store\ZMBuf;
|
||||
@@ -24,39 +21,21 @@ class ZMUtil
|
||||
if (SpinLock::tryLock("_stop_signal") === false) return;
|
||||
Console::warning(Console::setColor("Stopping server...", "red"));
|
||||
if (Console::getLevel() >= 4) Console::trace();
|
||||
LightCache::savePersistence();
|
||||
if (ZMBuf::$terminal !== null)
|
||||
Event::del(ZMBuf::$terminal);
|
||||
ZMAtomic::get("stop_signal")->set(1);
|
||||
try {
|
||||
LightCache::set('stop', 'OK');
|
||||
} catch (Exception $e) {
|
||||
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||
if (Process::kill(zm_atomic("_#worker_" . $i)->get(), 0))
|
||||
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1);
|
||||
}
|
||||
server()->shutdown();
|
||||
server()->stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $delay
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function reload($delay = 800) {
|
||||
if (server()->worker_id !== -1) {
|
||||
Console::info(server()->worker_id);
|
||||
zm_atomic("_int_is_reload")->set(1);
|
||||
system("kill -INT " . intval(server()->master_pid));
|
||||
return;
|
||||
}
|
||||
Console::info(Console::setColor("Reloading server...", "gold"));
|
||||
usleep($delay * 1000);
|
||||
foreach ((LightCacheInside::get("wait_api", "wait_api") ?? []) as $k => $v) {
|
||||
if (($v["result"] ?? false) === null && isset($v["coroutine"])) Co::resume($v["coroutine"]);
|
||||
}
|
||||
LightCacheInside::unset("wait_api", "wait_api");
|
||||
LightCache::savePersistence();
|
||||
//DataProvider::saveBuffer();
|
||||
Timer::clearAll();
|
||||
server()->reload();
|
||||
public static function reload() {
|
||||
zm_atomic("_int_is_reload")->set(1);
|
||||
system("kill -INT " . intval(server()->master_pid));
|
||||
}
|
||||
|
||||
public static function getModInstance($class) {
|
||||
@@ -69,6 +48,22 @@ class ZMUtil
|
||||
}
|
||||
|
||||
public static function sendActionToWorker($target_id, $action, $data) {
|
||||
Console::verbose($action . ": " . $data);
|
||||
server()->sendMessage(json_encode(["action" => $action, "data" => $data]), $target_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在工作进程中返回可以通过reload重新加载的php文件列表
|
||||
* @return string[]|string[][]
|
||||
*/
|
||||
public static function getReloadableFiles() {
|
||||
return array_map(
|
||||
function ($x) {
|
||||
return str_replace(DataProvider::getWorkingDir() . "/", "", $x);
|
||||
}, array_diff(
|
||||
get_included_files(),
|
||||
Framework::$loaded_files
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use Swoole\Atomic;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\WebSocket\Server;
|
||||
use Symfony\Component\VarDumper\VarDumper;
|
||||
use ZM\API\ZMRobot;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
@@ -243,8 +244,6 @@ function ctx(): ?ContextInterface {
|
||||
}
|
||||
}
|
||||
|
||||
function zm_debug($msg) { Console::debug($msg); }
|
||||
|
||||
function onebot_target_id_name($message_type): string {
|
||||
return ($message_type == "group" ? "group_id" : "user_id");
|
||||
}
|
||||
@@ -255,13 +254,21 @@ function zm_sleep($s = 1): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
function zm_exec($cmd): array { return System::exec($cmd); }
|
||||
function zm_exec($cmd): array {
|
||||
return System::exec($cmd);
|
||||
}
|
||||
|
||||
function zm_cid() { return Co::getCid(); }
|
||||
function zm_cid() {
|
||||
return Co::getCid();
|
||||
}
|
||||
|
||||
function zm_yield() { Co::yield(); }
|
||||
function zm_yield() {
|
||||
Co::yield();
|
||||
}
|
||||
|
||||
function zm_resume(int $cid) { Co::resume($cid); }
|
||||
function zm_resume(int $cid) {
|
||||
Co::resume($cid);
|
||||
}
|
||||
|
||||
function zm_timer_after($ms, callable $callable) {
|
||||
Swoole\Timer::after($ms, function () use ($callable) {
|
||||
@@ -287,10 +294,14 @@ function zm_timer_tick($ms, callable $callable) {
|
||||
if (zm_cid() === -1) {
|
||||
return go(function () use ($ms, $callable) {
|
||||
Console::debug("Adding extra timer tick of " . $ms . " ms");
|
||||
Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
|
||||
Swoole\Timer::tick($ms, function () use ($callable) {
|
||||
call_with_catch($callable);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return Swoole\Timer::tick($ms, function () use ($callable) {call_with_catch($callable);});
|
||||
return Swoole\Timer::tick($ms, function () use ($callable) {
|
||||
call_with_catch($callable);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,3 +365,32 @@ function working_dir() {
|
||||
elseif (LOAD_MODE == 2) return realpath('.');
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @noinspection PhpMissingReturnTypeInspection */
|
||||
function zm_dump($var, ...$moreVars) {
|
||||
VarDumper::dump($var);
|
||||
|
||||
foreach ($moreVars as $v) {
|
||||
VarDumper::dump($v);
|
||||
}
|
||||
|
||||
if (1 < func_num_args()) {
|
||||
return func_get_args();
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
function zm_info($obj) { Console::info($obj); }
|
||||
|
||||
function zm_warning($obj) { Console::warning($obj); }
|
||||
|
||||
function zm_success($obj) { Console::success($obj); }
|
||||
|
||||
function zm_debug($obj) { Console::debug($obj); }
|
||||
|
||||
function zm_verbose($obj) { Console::verbose($obj); }
|
||||
|
||||
function zm_error($obj) { Console::error($obj); }
|
||||
|
||||
function zm_config($name, $key = null) { return ZMConfig::get($name, $key); }
|
||||
|
||||
Reference in New Issue
Block a user