tmp connect

This commit is contained in:
jerry 2021-03-22 07:44:11 +08:00
parent b6d1f724e9
commit 0ff4e52ed3
26 changed files with 812 additions and 212 deletions

View File

@ -40,7 +40,9 @@
},
"autoload": {
"psr-4": {
"ZM\\": "src/ZM"
"ZM\\": "src/ZM",
"Module\\": "src/Module",
"Custom\\": "src/Custom"
},
"files": [
"src/ZM/global_functions.php"

View File

@ -113,21 +113,18 @@ $config['server_event_handler_class'] = [
];
/** 服务器启用的外部第三方和内部插件 */
$config['modules'] = [
'onebot' => [ // 机器人解析模块,关闭后无法使用如@CQCommand等注解
'status' => true,
'single_bot_mode' => false
],
'http_proxy_server' => [ // 一个内置的简单HTTP代理服务器目前还没有认证功能预计2.4.0版本完成
'status' => false,
'host' => '0.0.0.0',
'port' => 8083,
'swoole_set_override' => [
'backlog' => 128,
'buffer_output_size' => 1024 * 1024 * 128,
'socket_buffer_size' => 1024 * 1024 * 1
]
],
$config['modules']['onebot'] = [
// 机器人解析模块,关闭后无法使用如@CQCommand等注解
'status' => true,
'single_bot_mode' => false
];
$config['modules']['remote_terminal'] = [
// 一个远程简易终端使用nc直接连接即可但是不建议开放host为0.0.0.0(远程连接)
'status' => false,
'host' => '127.0.0.1',
'port' => 20002,
'token' => ''
];
return $config;

View File

@ -27,3 +27,44 @@
TODO先放一放。
```
## ZM\Entity\MatchObject
此类是调用方法 `MessageUtil::matchCommand()` 返回的对象体,含有匹配成功与否和匹配到的注解相关的信息。
### 属性
- `$match``bool` 类型,返回匹配是否成功
- `$object``CQCommand` 注解类,如果匹配成功则返回对应的 `@CQCommand` 信息
- `match``array` 类型,如果匹配成功则返回匹配到的参数
```php
// 假设我有一个注解事件 @CQCommand(match="你好"),绑定的函数是 \Module\Example\Hello 下的 hello123()
$obj = MessageUtil::matchCommand("你好 我叫顺溜 我今年二十八", ctx()->getData());
/* 以下是返回信息,仅供参考
$obj->match ==> true
$obj->object ==> \ZM\Annotation\CQ\CQCommand: (
match: "你好",
pattern: "",
regex: "",
start_with: "",
end_with: "",
keyword: "",
alias: [],
message_type: "",
user_id: 0,
group_id: 0,
discuss_id: 0,
level: 20,
method: "hello123",
class: \Module\Example\Hello::class
)
$obj->match ==> [
"我叫顺溜",
"我今年二十八"
]
*/
```

View File

@ -134,10 +134,6 @@ set_coroutine_params(["data" => [
别名:`context()`,获取当前协程的上下文,见 [上下文](/component/context/)。
## zm_debug()
`Console::debug($msg)`
## zm_sleep()
协程版 `sleep()` 函数。
@ -255,4 +251,58 @@ bot()->sendPrivateMsg(123456, "你好啊!!");
定义:`getAllFdByConnectType(string $type = 'default'): array`
`$type``qq` 时,则返回所有 OneBot 机器人接入的 WebSocket 连接号。
`$type``qq` 时,则返回所有 OneBot 机器人接入的 WebSocket 连接号。
## zm_dump()
更漂亮地输出变量值,可替代 `var_dump()`
```php
class Pass {
public $foo = 123;
public $bar = ["a", "b"];
}
$pass = new Pass();
$pass->obj = true;
zm_dump($pass);
```
<img src="../assets/img/image-20210321193956832.png" alt="image-20210321193956832" style="zoom:50%;" />
## zm_config()
`ZMConfig::get()`
定义:`zm_config($name, $key = null)`
有关 ZMConfig 模块的说明,见 [指南 - 基本配置](/guide/basic-config/)。
```php
zm_config("global"); //等同于 ZMConfig::get("global");
zm_config("global", "swoole"); //等同于 ZMConfig::get("global", "swoole");
```
## zm_info()
`Console::info($msg)`
## zm_debug()
`Console::debug($msg)`
## zm_warning()
`Console::warning($msg)`
## zm_success()
`Console::success($msg)`
## zm_error()
`Console::error($msg)`
## zm_verbose()
`Console::verbose($msg)`

View File

@ -21,3 +21,47 @@ class ASD{
ZMUtil::getModInstance(ASD::class)->test = 5;
```
## ZMUtil::getReloadableFiles()
返回可通过热重启reload来重新加载的 php 文件列表。
以下是示例模块下的例子(直接拉取最新的框架源码并运行框架后获取的)。
```php
array:31 [
94 => "src/ZM/Context/Context.php"
95 => "src/ZM/Context/ContextInterface.php"
96 => "src/ZM/Annotation/AnnotationParser.php"
97 => "src/Custom/Annotation/Example.php"
98 => "src/ZM/Annotation/Interfaces/CustomAnnotation.php"
99 => "src/Module/Example/Hello.php"
100 => "src/ZM/Annotation/Swoole/OnStart.php"
101 => "src/ZM/Annotation/CQ/CQCommand.php"
102 => "src/ZM/Annotation/Interfaces/Level.php"
103 => "src/ZM/Annotation/Command/TerminalCommand.php"
104 => "src/ZM/Annotation/Http/RequestMapping.php"
105 => "src/ZM/Annotation/Http/RequestMethod.php"
106 => "src/ZM/Annotation/Http/Middleware.php"
107 => "src/ZM/Annotation/Interfaces/ErgodicAnnotation.php"
108 => "src/ZM/Annotation/Swoole/OnOpenEvent.php"
109 => "src/ZM/Annotation/Swoole/OnSwooleEventBase.php"
110 => "src/ZM/Annotation/Interfaces/Rule.php"
111 => "src/ZM/Annotation/Swoole/OnCloseEvent.php"
112 => "src/ZM/Annotation/Swoole/OnRequestEvent.php"
113 => "src/ZM/Http/RouteManager.php"
114 => "vendor/symfony/routing/RouteCollection.php"
115 => "vendor/symfony/routing/Route.php"
116 => "src/Module/Middleware/TimerMiddleware.php"
117 => "src/ZM/Http/MiddlewareInterface.php"
118 => "src/ZM/Annotation/Http/MiddlewareClass.php"
119 => "src/ZM/Annotation/Http/HandleBefore.php"
120 => "src/ZM/Annotation/Http/HandleAfter.php"
121 => "src/ZM/Annotation/Http/HandleException.php"
122 => "src/ZM/Event/EventManager.php"
123 => "src/ZM/Annotation/Swoole/OnSwooleEvent.php"
124 => "src/ZM/Event/EventDispatcher.php"
]
```
> 为什么不能重载所有文件?因为框架是多进程模型,而重载相当于只重新启动了一次 Worker 进程Manager 和 Master 进程未重启,所以被 Manager、Master 进程已经加载的 PHP 文件无法使用 reload 命令重新加载。详见 [进阶 - 进程间隔离](/advanced/multi-process/#_5)。

View File

@ -262,6 +262,27 @@
无。
## TerminalCommand()
添加一个远程终端的自定义命令。2.4.0 版本起可用)
### 属性
| 类型 | 值 |
| ---------- | --------------------------------------- |
| 名称 | `@TerminalCommand` |
| 触发前提 | 连接到远程终端可触发 |
| 命名空间 | `ZM\Annotation\Command\TerminalCommand` |
| 适用位置 | 方法 |
| 返回值处理 | 无 |
### 注解参数
| 参数名称 | 参数范围 | 默认 |
| ----------- | ------------------------------ | ---- |
| command | `string`**必填**,命令字符串 | |
| description | `string`,要显示的帮助文本 | 空 |
## 示例1机器人连接框架后输出信息
```php
@ -406,3 +427,6 @@ public function onCrawl() {
}
```
## 示例6创建一个远程终端命令并调试框架
> 开个坑以后填。__填坑标记__

View File

@ -1,5 +1,19 @@
# 更新日志v2 版本)
## v2.4.0390
> 更新时间2021.3.18
- 新增:全局方法(`zm_dump()``zm_error()``zm_warning()``zm_info()``zm_success()``zm_verbose()``zm_debug()``zm_config()`
- 修复:部分内部存储存在的内存泄漏问题
- 新增remote_terminal远程终端内置模块可以用 nc 直接远程连接框架(弥补原先删除掉的终端输入)
- 新增:`DataProvider::getReloadableFiles()` 返回可通过热重启reload来重新加载的 php 文件列表(多用于调试)
- 兼容性变更:**要求最低 Swoole 版本为 4.5.0**
- 优化reload 和 stop 重载和停止框架的逻辑,防止卡死
- 新增:远程终端命令支持自定义添加(`@TerminalCommand` 注解)
-
## v2.3.2
> 更新时间2021.3.18

View File

@ -2,10 +2,16 @@
namespace Module\Example;
use ZM\Annotation\Command\TerminalCommand;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMessage;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Swoole\OnCloseEvent;
use ZM\Annotation\Swoole\OnOpenEvent;
use ZM\Annotation\Swoole\OnRequestEvent;
use ZM\Annotation\Swoole\OnStart;
use ZM\API\CQ;
use ZM\API\TuringAPI;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Annotation\CQ\CQCommand;
@ -13,6 +19,7 @@ use ZM\Annotation\Http\RequestMapping;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Requests\ZMRequest;
use ZM\Utils\MessageUtil;
use ZM\Utils\ZMUtil;
/**
@ -22,6 +29,12 @@ use ZM\Utils\ZMUtil;
*/
class Hello
{
/**
* @OnStart()
*/
public function onStart() {
}
/*
* 默认的图片监听路由对应目录,如需要使用可取消下面的注释,把上面的 /* 换成 /**
* @OnStart(-1)
@ -67,6 +80,34 @@ class Hello
return $obj["hitokoto"] . "\n----「" . $obj["from"] . "";
}
/**
* @CQCommand(start_with="机器人",end_with="机器人",message_type="group")
* @CQMessage(message_type="private",level=1)
*/
public function turingAPI() {
$user_id = ctx()->getUserId();
$api = "83513e3d316f44de9c952cda4c9aed30"; // 请在这里填入你的图灵机器人的apikey
if ($api === "") return false; //如果没有填入apikey则此功能关闭
if (($this->_running_annotation ?? null) instanceof CQCommand) {
$msg = ctx()->getFullArg("我在!有什么事吗?");
} else {
$msg = ctx()->getMessage();
}
zm_dump($msg);
return TuringAPI::getTuringMsg($msg, $user_id, $api);
}
/**
* @CQBefore("message")
*/
public function changeAt() {
if (MessageUtil::isAtMe(ctx()->getMessage(), ctx()->getRobotId())) {
$msg = str_replace(CQ::at(ctx()->getRobotId()), "", ctx()->getMessage());
ctx()->setMessage("机器人 ".$msg);
}
return true;
}
/**
* 一个简单随机数的功能demo
* 问法1随机数 1 20

View File

@ -29,25 +29,15 @@ trait CQAPI
public function processWebsocketAPI($connection, $reply, $function = false) {
$api_id = ZMAtomic::get("wait_msg_id")->add(1);
$reply["echo"] = $api_id;
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
$r[$api_id] = [
"data" => $reply,
"time" => microtime(true),
"self_id" => $connection->getOption("connect_id"),
"echo" => $api_id
];
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
if (server()->push($connection->getFd(), json_encode($reply))) {
if ($function === true) {
return CoMessage::yieldByWS($r[$api_id], ["echo"], 60);
} else {
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
unset($r[$api_id]);
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
$obj = [
"data" => $reply,
"time" => microtime(true),
"self_id" => $connection->getOption("connect_id"),
"echo" => $api_id
];
return CoMessage::yieldByWS($obj, ["echo"], 60);
}
return true;
} else {

118
src/ZM/API/TuringAPI.php Normal file
View 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;
}
}
}

View 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 = "";
}

View File

@ -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;

View File

@ -23,6 +23,7 @@ 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("watch", null, null, "监听 src/ 目录的文件变化并热更新"),

View File

@ -18,7 +18,7 @@ use ZM\Utils\DataProvider;
class ConsoleApplication extends Application
{
const VERSION_ID = 389;
const VERSION_ID = 390;
const VERSION = "2.3.2";
public function __construct(string $name = 'UNKNOWN') {
@ -100,7 +100,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;
}

View 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 = [];
}

View File

@ -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;

View File

@ -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,17 +133,25 @@ 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) {
zm_atomic("_#worker_".$worker_id)->set($server->worker_pid);
//if (ZMBuf::atomic("stop_signal")->get() != 0) return;
Process::signal(SIGUSR1, function() use ($worker_id){
Timer::clearAll();
ProcessManager::resumeAllWorkerCoroutines();
});
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);
});
@ -472,7 +480,6 @@ class ServerEventHandler
Console::error("Uncaught " . get_class($e) . " when calling \"open\": " . $error_msg);
Console::trace();
}
//EventHandler::callSwooleEvent("open", $server, $request);
}
/**
@ -528,70 +535,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");
}
/**

View File

@ -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"]);
@ -82,11 +89,17 @@ class Framework
($o = ZMConfig::get("console_color")) === false ? [] : $o
);
$worker = ZMConfig::get("global", "swoole")["worker_num"] ?? swoole_cpu_num();
define("ZM_WORKER_NUM", $worker);
ZMAtomic::init();
$timezone = ZMConfig::get("global", "timezone") ?? "Asia/Shanghai";
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", "modules")["remote_terminal"]["status"] ?? false;
$this->parseCliArgs(self::$argv, $add_port);
// 打印初始信息
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
@ -114,12 +127,82 @@ class Framework
$out["php_version"] = PHP_VERSION;
$out["swoole_version"] = SWOOLE_VERSION;
}
if ($add_port) {
$conf = ZMConfig::get("global", "modules")["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) {
$welcome_msg = Console::setColor("Welcome! You can use `help` for usage.", "green");
/** @var Port $port */
$port = self::$server->listen("127.0.0.1", 20002, SWOOLE_SOCK_TCP);
$port->set([
'open_http_protocol' => false
]);
$port->on('connect', function (?\Swoole\Server $serv, $fd) use ($port, $welcome_msg) {
ManagerGM::pushConnect($fd, "terminal");
$serv->send($fd, file_get_contents(working_dir() . "/config/motd.txt"));
if (!empty(ZMConfig::get("global", "modules")["remote_terminal"]["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) {
ob_start();
try {
$arr = LightCacheInside::get("light_array", "input_token") ?? [];
if (empty($arr[$fd] ?? '')) {
if (ZMConfig::get("global", "modules")["remote_terminal"]["token"] != '') {
$token = trim($data);
if ($token === ZMConfig::get("global", "modules")["remote_terminal"]["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 +278,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,9 +326,9 @@ 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();
@ -296,6 +379,9 @@ class Framework
Console::$theme = $y;
}
break;
case 'remote-terminal':
$add_port = true;
break;
case 'show-php-ver':
default:
//Console::info("Calculating ".$x);

View File

@ -15,6 +15,7 @@ use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Exception\WaitTimeoutException;
use ZM\Utils\CoMessage;
use ZM\Utils\MessageUtil;
/**
* Class QQBot
@ -76,58 +77,19 @@ 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();
$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);

View File

@ -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) {

View File

@ -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("内存不足,创建静态表失败!");

View File

@ -32,6 +32,9 @@ 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);
}
for ($i = 0; $i < 10; ++$i) {
self::$atomics["_tmp_" . $i] = new Atomic(0);
}

View File

@ -4,9 +4,13 @@
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\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Requests\ZMRequest;
class MessageUtil
@ -55,6 +59,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 +84,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_strpos($msg, $v->start_with) === 0) {
$matched->match = [mb_substr($msg, mb_strlen($v->start_with))];
$matched->object = $v;
$matched->status = true;
break;
} elseif ($v->end_with != "" && strlen($msg) == (strripos($msg, $v->end_with) + strlen($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;
}
}

View File

@ -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"]);
}
}
}
}

View File

@ -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(), ">>> ");
}
}
}

View File

@ -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,20 @@ 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) {
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 +47,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
)
);
}
}