mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-25 01:25:34 +08:00
update to build 427
This commit is contained in:
@@ -39,7 +39,9 @@ $config['swoole'] = [
|
|||||||
$config['runtime'] = [
|
$config['runtime'] = [
|
||||||
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
'swoole_coroutine_hook_flags' => SWOOLE_HOOK_ALL & (~SWOOLE_HOOK_CURL),
|
||||||
'swoole_server_mode' => SWOOLE_PROCESS,
|
'swoole_server_mode' => SWOOLE_PROCESS,
|
||||||
'middleware_error_policy' => 1
|
'middleware_error_policy' => 1,
|
||||||
|
'reload_delay_time' => 800,
|
||||||
|
'global_middleware_binding' => []
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 轻量字符串缓存,默认开启 */
|
/** 轻量字符串缓存,默认开启 */
|
||||||
@@ -120,7 +122,8 @@ $config['static_file_server'] = [
|
|||||||
$config['onebot'] = [
|
$config['onebot'] = [
|
||||||
'status' => true,
|
'status' => true,
|
||||||
'single_bot_mode' => false,
|
'single_bot_mode' => false,
|
||||||
'message_level' => 99
|
'message_level' => 99,
|
||||||
|
'message_convert_string' => true
|
||||||
];
|
];
|
||||||
|
|
||||||
/** 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */
|
/** 一个远程简易终端,使用nc直接连接即可,但是不建议开放host为0.0.0.0(远程连接) */
|
||||||
|
|||||||
104
docs/component/common/event-tracer.md
Normal file
104
docs/component/common/event-tracer.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# 事件跟踪器及调试
|
||||||
|
|
||||||
|
众所周知,炸毛框架中的事件由内置的事件分发器(EventDispatcher)负责分发,但调试事件分发在之前的版本比较困难,例如不能获取到事件如何被调用,以及事件如何被捕获。
|
||||||
|
|
||||||
|
EventTracer 的作用是记录事件的调用顺序,以便于调试。
|
||||||
|
|
||||||
|
命名空间使用指南:`use ZM\Event\EventTracer;`
|
||||||
|
|
||||||
|
## EventTracer::getCurrentEvent() - 获取当前注解事件对象
|
||||||
|
|
||||||
|
```php
|
||||||
|
/**
|
||||||
|
* @OnStart()
|
||||||
|
*/
|
||||||
|
public function onStart() {
|
||||||
|
zm_dump(EventTracer::getCurrentEvent());
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
^ ZM\Annotation\Swoole\OnStart^ {#192
|
||||||
|
+worker_id: 0
|
||||||
|
+method: "onStart"
|
||||||
|
+class: "Module\Example\Hello"
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
这里这个方法必须在注解事件内执行,如果在注解事件外执行,将会返回 `null`。
|
||||||
|
|
||||||
|
## EventTracer::getCurrentEventMiddlewares() - 获取当前注解事件的中间件们
|
||||||
|
|
||||||
|
```php
|
||||||
|
/**
|
||||||
|
* @OnStart()
|
||||||
|
* @Middleware("timer")
|
||||||
|
*/
|
||||||
|
public function onStart() {
|
||||||
|
zm_dump(EventTracer::getCurrentEventMiddlewares());
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
^ array:1 [
|
||||||
|
0 => ZM\Annotation\Http\Middleware^ {#194
|
||||||
|
+middleware: "timer"
|
||||||
|
+params: []
|
||||||
|
+method: "onStart"
|
||||||
|
+class: "Module\Example\Hello"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
返回值为当前注解事件的中间件们,如果没有注解中间件,返回 `[]`。
|
||||||
|
|
||||||
|
## EventTracer::getEventTraceList() - 获取注解事件的列表
|
||||||
|
|
||||||
|
此处返回的是 `getCurrentEvent()` 相同的对象,但是返回的是一个数组,数组中的元素是注解事件。
|
||||||
|
|
||||||
|
```php
|
||||||
|
/**
|
||||||
|
* 一个简单随机数的功能demo
|
||||||
|
* 问法1:随机数 1 20
|
||||||
|
* 问法2:从1到20的随机数
|
||||||
|
* @CQCommand("随机数")
|
||||||
|
* @Middleware("timer")
|
||||||
|
* @CQCommand(pattern="*从*到*的随机数")
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function randNum() {
|
||||||
|
// 此处为随机数代码
|
||||||
|
zm_dump(EventTracer::getEventTraceList());
|
||||||
|
return "随机数:" . rand(1, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
^ array:2 [
|
||||||
|
0 => ZM\Annotation\CQ\CQCommand^ {#193
|
||||||
|
+match: ""
|
||||||
|
+pattern: "*从*到*的随机数"
|
||||||
|
+regex: ""
|
||||||
|
+start_with: ""
|
||||||
|
+end_with: ""
|
||||||
|
+keyword: ""
|
||||||
|
+alias: []
|
||||||
|
+message_type: ""
|
||||||
|
+user_id: 0
|
||||||
|
+group_id: 0
|
||||||
|
+discuss_id: 0
|
||||||
|
+level: 20
|
||||||
|
+method: "randNum"
|
||||||
|
+class: "Module\Example\Hello"
|
||||||
|
}
|
||||||
|
1 => ZM\Annotation\Swoole\OnMessageEvent^ {#165
|
||||||
|
+connect_type: "default"
|
||||||
|
+rule: "connectIsQQ()"
|
||||||
|
+level: 99
|
||||||
|
+method: "handleByEvent"
|
||||||
|
+class: "ZM\Module\QQBot"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
```
|
||||||
|
|
||||||
|
## EventDispatcher::enableEventTrace() - 启用事件跟踪器
|
||||||
|
|
||||||
|
还没写完,不着急。
|
||||||
@@ -4,6 +4,21 @@
|
|||||||
|
|
||||||
同时此处将只使用 build 版本号进行区分。
|
同时此处将只使用 build 版本号进行区分。
|
||||||
|
|
||||||
|
## build 427 (2021-11-16)
|
||||||
|
|
||||||
|
- 新增全局中间件,可在全局配置文件中设置
|
||||||
|
- 修复部分 Typo
|
||||||
|
- 新增指令 `server:status`、`server:reload`、`server:stop` 可用在新开终端中查看框架运行状态、重启和退出
|
||||||
|
- 新增支持 `array` 格式的消息
|
||||||
|
- 上下文 Context 对象新增 `getOriginMessage()` 用于获取原消息,`getMessage()` 如果在设置了转换后,将默认转换消息为字符串格式保持与旧模块兼容
|
||||||
|
- OneBot API 新增全局过滤器,可用作 Action 过滤重写等操作
|
||||||
|
- 配置文件新增 `runtime.reload_delay_time`,用于可配置重载 Worker 等待的时间(毫秒)
|
||||||
|
- 配置文件新增 `runtime.global_middleware_binding`,用于配置全局中间件
|
||||||
|
- 配置文件新增 `onebot.message_convert_string`,用于配置是否转换数组格式为字符串,保证与前版本的兼容性(默认为 true)
|
||||||
|
- MessageUtil 消息工具类新增方法:`strToArray($msg, bool $ignore_space = true, bool $trim_text = false)`
|
||||||
|
- MessageUtil 消息工具类新增方法:`arrayToStr(array $array)`
|
||||||
|
- 新增框架启动多次监测功能,无法使用同一个框架项目同时启动两个框架
|
||||||
|
|
||||||
## build 426 (2021-11-10)
|
## build 426 (2021-11-10)
|
||||||
|
|
||||||
- 修复 CQ 码的解析函数 Bug(#52)
|
- 修复 CQ 码的解析函数 Bug(#52)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class TimerMiddleware implements MiddlewareInterface
|
|||||||
* @HandleAfter()
|
* @HandleAfter()
|
||||||
*/
|
*/
|
||||||
public function onAfter() {
|
public function onAfter() {
|
||||||
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms.");
|
Console::info("Using " . round((microtime(true) - $this->starttime) * 1000, 3) . " ms.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +42,7 @@ class TimerMiddleware implements MiddlewareInterface
|
|||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function onException(Exception $e) {
|
public function onException(Exception $e) {
|
||||||
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 2) . " ms but an Exception occurred.");
|
Console::error("Using " . round((microtime(true) - $this->starttime) * 1000, 3) . " ms but an Exception occurred.");
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
namespace ZM\API;
|
namespace ZM\API;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
use ZM\Store\LightCacheInside;
|
use ZM\Store\LightCacheInside;
|
||||||
use ZM\Store\Lock\SpinLock;
|
use ZM\Store\Lock\SpinLock;
|
||||||
@@ -11,6 +12,13 @@ use ZM\Utils\CoMessage;
|
|||||||
|
|
||||||
trait CQAPI
|
trait CQAPI
|
||||||
{
|
{
|
||||||
|
/** @var null|Closure */
|
||||||
|
private static $filter = null;
|
||||||
|
|
||||||
|
public static function registerFilter(callable $callable) {
|
||||||
|
self::$filter = $callable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $connection
|
* @param $connection
|
||||||
* @param $reply
|
* @param $reply
|
||||||
@@ -18,6 +26,11 @@ trait CQAPI
|
|||||||
* @return bool|array
|
* @return bool|array
|
||||||
*/
|
*/
|
||||||
private function processAPI($connection, $reply, $function = null) {
|
private function processAPI($connection, $reply, $function = null) {
|
||||||
|
if (is_callable(self::$filter)) {
|
||||||
|
$reply2 = call_user_func(self::$filter, $reply);
|
||||||
|
if (is_bool($reply2)) return $reply2;
|
||||||
|
else $reply = $reply2;
|
||||||
|
}
|
||||||
if ($connection->getOption("type") === CONN_WEBSOCKET)
|
if ($connection->getOption("type") === CONN_WEBSOCKET)
|
||||||
return $this->processWebsocketAPI($connection, $reply, $function);
|
return $this->processWebsocketAPI($connection, $reply, $function);
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
namespace ZM\Annotation;
|
namespace ZM\Annotation;
|
||||||
|
|
||||||
use Doctrine\Common\Annotations\AnnotationReader;
|
use Doctrine\Common\Annotations\AnnotationReader;
|
||||||
|
use ZM\Annotation\CQ\CQCommand;
|
||||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||||
use ZM\Config\ZMConfig;
|
use ZM\Config\ZMConfig;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
@@ -98,7 +99,6 @@ class AnnotationParser
|
|||||||
if ($vs instanceof ErgodicAnnotation) {
|
if ($vs instanceof ErgodicAnnotation) {
|
||||||
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
|
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
|
||||||
$copy = clone $vs;
|
$copy = clone $vs;
|
||||||
/** @noinspection PhpUndefinedFieldInspection */
|
|
||||||
$copy->method = $method->getName();
|
$copy->method = $method->getName();
|
||||||
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
|
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
|
||||||
}
|
}
|
||||||
@@ -116,12 +116,27 @@ class AnnotationParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$inserted = [];
|
||||||
|
|
||||||
//预处理3:处理每个函数上面的特殊注解,就是需要操作一些东西的
|
//预处理3:处理每个函数上面的特殊注解,就是需要操作一些东西的
|
||||||
foreach (($this->annotation_map[$v]["methods_annotations"] ?? []) as $method_name => $methods_annotations) {
|
foreach (($this->annotation_map[$v]["methods_annotations"] ?? []) as $method_name => $methods_annotations) {
|
||||||
foreach ($methods_annotations as $method_anno) {
|
foreach ($methods_annotations as $method_anno) {
|
||||||
/** @var AnnotationBase $method_anno */
|
/** @var AnnotationBase $method_anno */
|
||||||
$method_anno->class = $v;
|
$method_anno->class = $v;
|
||||||
$method_anno->method = $method_name;
|
$method_anno->method = $method_name;
|
||||||
|
if (!($method_anno instanceof Middleware) && ($middlewares = ZMConfig::get("global", "runtime")["global_middleware_binding"][get_class($method_anno)] ?? []) !== []) {
|
||||||
|
if (!isset($inserted[$v][$method_name])) {
|
||||||
|
// 在这里在其他中间件前插入插入全局的中间件
|
||||||
|
foreach ($middlewares as $middleware) {
|
||||||
|
$mid_class = new Middleware();
|
||||||
|
$mid_class->middleware = $middleware;
|
||||||
|
$mid_class->class = $v;
|
||||||
|
$mid_class->method = $method_name;
|
||||||
|
$this->middleware_map[$v][$method_name][] = $mid_class;
|
||||||
|
}
|
||||||
|
$inserted[$v][$method_name] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($method_anno instanceof RequestMapping) {
|
if ($method_anno instanceof RequestMapping) {
|
||||||
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
|
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
|
||||||
} elseif ($method_anno instanceof Middleware) {
|
} elseif ($method_anno instanceof Middleware) {
|
||||||
@@ -134,9 +149,10 @@ class AnnotationParser
|
|||||||
Console::debug("解析注解完毕!");
|
Console::debug("解析注解完毕!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generateAnnotationEvents() {
|
public function generateAnnotationEvents(): array {
|
||||||
$o = [];
|
$o = [];
|
||||||
foreach ($this->annotation_map as $obj) {
|
foreach ($this->annotation_map as $obj) {
|
||||||
|
// 这里的ErgodicAnnotation是为了解决类上的注解可穿透到方法上的问题
|
||||||
foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
|
foreach (($obj["class_annotations"] ?? []) as $class_annotation) {
|
||||||
if ($class_annotation instanceof ErgodicAnnotation) continue;
|
if ($class_annotation instanceof ErgodicAnnotation) continue;
|
||||||
else $o[get_class($class_annotation)][] = $class_annotation;
|
else $o[get_class($class_annotation)][] = $class_annotation;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class DaemonReloadCommand extends DaemonCommand
|
|||||||
protected static $defaultName = 'daemon:reload';
|
protected static $defaultName = 'daemon:reload';
|
||||||
|
|
||||||
protected function configure() {
|
protected function configure() {
|
||||||
$this->setDescription("重载守护进程下的用户代码(仅限--daemon模式可用)");
|
$this->setDescription("重载框架");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
|||||||
@@ -11,18 +11,20 @@ class DaemonStatusCommand extends DaemonCommand
|
|||||||
protected static $defaultName = 'daemon:status';
|
protected static $defaultName = 'daemon:status';
|
||||||
|
|
||||||
protected function configure() {
|
protected function configure() {
|
||||||
$this->setDescription("查看守护进程框架的运行状态(仅限--daemon模式可用)");
|
$this->setDescription("查看框架的运行状态");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
parent::execute($input, $output);
|
parent::execute($input, $output);
|
||||||
$output->writeln("<info>框架运行中,pid:" . $this->daemon_file["pid"] . "</info>");
|
$output->writeln("<info>框架" . ($this->daemon_file["daemon"] ? "以守护进程模式" : "") . "运行中,pid:" . $this->daemon_file["pid"] . "</info>");
|
||||||
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
|
if ($this->daemon_file["daemon"]) {
|
||||||
$stdout = file_get_contents($this->daemon_file["stdout"]);
|
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
|
||||||
$stdout = explode("\n", $stdout);
|
$stdout = file_get_contents($this->daemon_file["stdout"]);
|
||||||
for ($i = 15; $i > 0; --$i) {
|
$stdout = explode("\n", $stdout);
|
||||||
if (isset($stdout[count($stdout) - $i]))
|
for ($i = 15; $i > 0; --$i) {
|
||||||
echo $stdout[count($stdout) - $i] . PHP_EOL;
|
if (isset($stdout[count($stdout) - $i]))
|
||||||
|
echo $stdout[count($stdout) - $i] . PHP_EOL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class DaemonStopCommand extends DaemonCommand
|
|||||||
protected static $defaultName = 'daemon:stop';
|
protected static $defaultName = 'daemon:stop';
|
||||||
|
|
||||||
protected function configure() {
|
protected function configure() {
|
||||||
$this->setDescription("停止守护进程下运行的框架(仅限--daemon模式可用)");
|
$this->setDescription("停止运行的框架");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function execute(InputInterface $input, OutputInterface $output): int {
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use ZM\Framework;
|
use ZM\Framework;
|
||||||
|
use ZM\Utils\DataProvider;
|
||||||
|
|
||||||
class RunServerCommand extends Command
|
class RunServerCommand extends Command
|
||||||
{
|
{
|
||||||
protected static $defaultName = 'server';
|
protected static $defaultName = 'server';
|
||||||
|
|
||||||
protected function configure() {
|
protected function configure() {
|
||||||
|
$this->setAliases(['server:start']);
|
||||||
$this->setDefinition([
|
$this->setDefinition([
|
||||||
new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"),
|
new InputOption("debug-mode", "D", null, "开启调试模式 (这将关闭协程化)"),
|
||||||
new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"),
|
new InputOption("log-debug", null, null, "调整消息等级到debug (log-level=4)"),
|
||||||
@@ -47,6 +49,17 @@ class RunServerCommand extends Command
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
|
||||||
|
if (file_exists($pid_path)) {
|
||||||
|
$pid = json_decode(file_get_contents($pid_path), true)["pid"] ?? null;
|
||||||
|
if ($pid !== null && posix_getsid($pid) !== false) {
|
||||||
|
$output->writeln("<error>检测到已经在 pid: $pid 进程启动了框架!</error>");
|
||||||
|
$output->writeln("<error>不可以同时启动两个框架!</error>");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
unlink($pid_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
(new Framework($input->getOptions()))->start();
|
(new Framework($input->getOptions()))->start();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/ZM/Command/Server/ServerReloadCommand.php
Normal file
25
src/ZM/Command/Server/ServerReloadCommand.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZM\Command\Server;
|
||||||
|
|
||||||
|
use Swoole\Process;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Command\Daemon\DaemonCommand;
|
||||||
|
|
||||||
|
class ServerReloadCommand extends DaemonCommand
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'server:reload';
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setDescription("重载框架");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
parent::execute($input, $output);
|
||||||
|
Process::kill(intval($this->daemon_file["pid"]), SIGUSR1);
|
||||||
|
$output->writeln("<info>成功重载!</info>");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/ZM/Command/Server/ServerStatusCommand.php
Normal file
34
src/ZM/Command/Server/ServerStatusCommand.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace ZM\Command\Server;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Command\Daemon\DaemonCommand;
|
||||||
|
|
||||||
|
class ServerStatusCommand extends DaemonCommand
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'server:status';
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setDescription("查看框架的运行状态");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
parent::execute($input, $output);
|
||||||
|
$output->writeln("<info>框架" . ($this->daemon_file["daemon"] ? "以守护进程模式" : "") . "运行中,pid:" . $this->daemon_file["pid"] . "</info>");
|
||||||
|
if ($this->daemon_file["daemon"]) {
|
||||||
|
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
|
||||||
|
$stdout = file_get_contents($this->daemon_file["stdout"]);
|
||||||
|
$stdout = explode("\n", $stdout);
|
||||||
|
for ($i = 15; $i > 0; --$i) {
|
||||||
|
if (isset($stdout[count($stdout) - $i]))
|
||||||
|
echo $stdout[count($stdout) - $i] . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
35
src/ZM/Command/Server/ServerStopCommand.php
Normal file
35
src/ZM/Command/Server/ServerStopCommand.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace ZM\Command\Server;
|
||||||
|
|
||||||
|
use Swoole\Process;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Command\Daemon\DaemonCommand;
|
||||||
|
use ZM\Utils\DataProvider;
|
||||||
|
|
||||||
|
class ServerStopCommand extends DaemonCommand
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'server:stop';
|
||||||
|
|
||||||
|
protected function configure() {
|
||||||
|
$this->setDescription("停止运行的框架");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int {
|
||||||
|
parent::execute($input, $output);
|
||||||
|
Process::kill(intval($this->daemon_file["pid"]), SIGTERM);
|
||||||
|
$i = 10;
|
||||||
|
while (file_exists(DataProvider::getWorkingDir() . "/.daemon_pid") && $i > 0) {
|
||||||
|
sleep(1);
|
||||||
|
--$i;
|
||||||
|
}
|
||||||
|
if ($i === 0) {
|
||||||
|
$output->writeln("<error>停止失败,请检查进程pid #" . $this->daemon_file["pid"] . " 是否响应!</error>");
|
||||||
|
} else {
|
||||||
|
$output->writeln("<info>成功停止!</info>");
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,14 +21,17 @@ use ZM\Command\RunServerCommand;
|
|||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use ZM\Command\Server\ServerReloadCommand;
|
||||||
|
use ZM\Command\Server\ServerStatusCommand;
|
||||||
|
use ZM\Command\Server\ServerStopCommand;
|
||||||
use ZM\Exception\InitException;
|
use ZM\Exception\InitException;
|
||||||
|
|
||||||
class ConsoleApplication extends Application
|
class ConsoleApplication extends Application
|
||||||
{
|
{
|
||||||
private static $obj = null;
|
private static $obj = null;
|
||||||
|
|
||||||
const VERSION_ID = 426;
|
const VERSION_ID = 427;
|
||||||
const VERSION = "2.5.8";
|
const VERSION = "2.6.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws InitException
|
* @throws InitException
|
||||||
@@ -46,8 +49,8 @@ class ConsoleApplication extends Application
|
|||||||
* @return ConsoleApplication
|
* @return ConsoleApplication
|
||||||
* @throws InitException
|
* @throws InitException
|
||||||
*/
|
*/
|
||||||
public function initEnv($with_default_cmd = ""): ConsoleApplication {
|
public function initEnv(string $with_default_cmd = ""): ConsoleApplication {
|
||||||
if (defined("WORKDING_DIR")) throw new InitException();
|
if (defined("WORKING_DIR")) throw new InitException();
|
||||||
|
|
||||||
_zm_env_check();
|
_zm_env_check();
|
||||||
|
|
||||||
@@ -90,6 +93,9 @@ class ConsoleApplication extends Application
|
|||||||
new DaemonReloadCommand(),
|
new DaemonReloadCommand(),
|
||||||
new DaemonStopCommand(),
|
new DaemonStopCommand(),
|
||||||
new RunServerCommand(), //运行主服务的指令控制器
|
new RunServerCommand(), //运行主服务的指令控制器
|
||||||
|
new ServerStatusCommand(),
|
||||||
|
new ServerStopCommand(),
|
||||||
|
new ServerReloadCommand(),
|
||||||
new PureHttpCommand(), //纯HTTP服务器指令
|
new PureHttpCommand(), //纯HTTP服务器指令
|
||||||
new SystemdGenerateCommand()
|
new SystemdGenerateCommand()
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Swoole\Coroutine;
|
|||||||
use Swoole\Http\Request;
|
use Swoole\Http\Request;
|
||||||
use Swoole\WebSocket\Frame;
|
use Swoole\WebSocket\Frame;
|
||||||
use Swoole\WebSocket\Server;
|
use Swoole\WebSocket\Server;
|
||||||
|
use ZM\Config\ZMConfig;
|
||||||
use ZM\ConnectionManager\ConnectionObject;
|
use ZM\ConnectionManager\ConnectionObject;
|
||||||
use ZM\ConnectionManager\ManagerGM;
|
use ZM\ConnectionManager\ManagerGM;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
@@ -19,6 +20,7 @@ use ZM\Exception\WaitTimeoutException;
|
|||||||
use ZM\Http\Response;
|
use ZM\Http\Response;
|
||||||
use ZM\API\ZMRobot;
|
use ZM\API\ZMRobot;
|
||||||
use ZM\Utils\CoMessage;
|
use ZM\Utils\CoMessage;
|
||||||
|
use ZM\Utils\MessageUtil;
|
||||||
|
|
||||||
class Context implements ContextInterface
|
class Context implements ContextInterface
|
||||||
{
|
{
|
||||||
@@ -56,8 +58,8 @@ class Context implements ContextInterface
|
|||||||
*/
|
*/
|
||||||
public function getResponse(): ?Response { return self::$context[$this->cid]["response"] ?? null; }
|
public function getResponse(): ?Response { return self::$context[$this->cid]["response"] ?? null; }
|
||||||
|
|
||||||
/** @return ConnectionObject|null|Response */
|
/** @return ConnectionObject|null */
|
||||||
public function getConnection() { return ManagerGM::get($this->getFd()); }
|
public function getConnection(): ?ConnectionObject { return ManagerGM::get($this->getFd()); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return int|null
|
* @return int|null
|
||||||
@@ -72,9 +74,20 @@ class Context implements ContextInterface
|
|||||||
return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
|
return $conn instanceof ConnectionObject ? new ZMRobot($conn) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
|
public function getMessage() {
|
||||||
|
if ((ZMConfig::get("global", "onebot")["message_convert_string"] ?? true) === true && is_array($msg = $this->getOriginMessage())) {
|
||||||
|
return MessageUtil::arrayToStr($msg);
|
||||||
|
} else {
|
||||||
|
return self::$context[$this->cid]["data"]["message"] ?? null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function setMessage($msg) { self::$context[$this->cid]["data"]["message"] = $msg; }
|
public function setMessage($msg) {
|
||||||
|
if (is_string($msg) && is_array($this->getOriginMessage())) {
|
||||||
|
$msg = MessageUtil::strToArray($msg);
|
||||||
|
}
|
||||||
|
self::$context[$this->cid]["data"]["message"] = $msg;
|
||||||
|
}
|
||||||
|
|
||||||
public function getUserId() { return $this->getData()["user_id"] ?? null; }
|
public function getUserId() { return $this->getData()["user_id"] ?? null; }
|
||||||
|
|
||||||
@@ -239,4 +252,6 @@ class Context implements ContextInterface
|
|||||||
public function copy() { return self::$context[$this->cid]; }
|
public function copy() { return self::$context[$this->cid]; }
|
||||||
|
|
||||||
public function getOption() { return self::getCache("match"); }
|
public function getOption() { return self::getCache("match"); }
|
||||||
|
|
||||||
|
public function getOriginMessage() { return self::$context[$this->cid]["data"]["message"] ?? null; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use ZM\Annotation\AnnotationParser;
|
|||||||
use ZM\Annotation\Swoole\OnTick;
|
use ZM\Annotation\Swoole\OnTick;
|
||||||
use ZM\Config\ZMConfig;
|
use ZM\Config\ZMConfig;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
|
use ZM\Exception\AnnotationException;
|
||||||
use ZM\Store\LightCache;
|
use ZM\Store\LightCache;
|
||||||
use ZM\Store\ZMAtomic;
|
use ZM\Store\ZMAtomic;
|
||||||
|
|
||||||
@@ -32,6 +33,9 @@ class EventManager
|
|||||||
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
|
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws AnnotationException
|
||||||
|
*/
|
||||||
public static function loadEventByParser(AnnotationParser $parser) {
|
public static function loadEventByParser(AnnotationParser $parser) {
|
||||||
self::$events = $parser->generateAnnotationEvents();
|
self::$events = $parser->generateAnnotationEvents();
|
||||||
self::$middlewares = $parser->getMiddlewares();
|
self::$middlewares = $parser->getMiddlewares();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace ZM\Event\SwooleEvent;
|
|||||||
use Swoole\Process;
|
use Swoole\Process;
|
||||||
use Swoole\WebSocket\Server;
|
use Swoole\WebSocket\Server;
|
||||||
use ZM\Annotation\Swoole\SwooleHandler;
|
use ZM\Annotation\Swoole\SwooleHandler;
|
||||||
|
use ZM\Config\ZMConfig;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
use ZM\Event\SwooleEvent;
|
use ZM\Event\SwooleEvent;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ class OnBeforeReload implements SwooleEvent
|
|||||||
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
for ($i = 0; $i < ZM_WORKER_NUM; ++$i) {
|
||||||
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1);
|
Process::kill(zm_atomic("_#worker_" . $i)->get(), SIGUSR1);
|
||||||
}
|
}
|
||||||
|
$conf = ZMConfig::get("global", "runtime")["reload_delay_time"] ?? 800;
|
||||||
usleep(800 * 1000);
|
if ($conf !== 0) usleep($conf * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ use Swoole\Server;
|
|||||||
use ZM\Annotation\Swoole\SwooleHandler;
|
use ZM\Annotation\Swoole\SwooleHandler;
|
||||||
use ZM\Console\Console;
|
use ZM\Console\Console;
|
||||||
use ZM\Event\SwooleEvent;
|
use ZM\Event\SwooleEvent;
|
||||||
|
use ZM\Utils\DataProvider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class OnShutdown
|
* Class OnShutdown
|
||||||
@@ -18,5 +19,9 @@ class OnShutdown implements SwooleEvent
|
|||||||
{
|
{
|
||||||
public function onCall(Server $server) {
|
public function onCall(Server $server) {
|
||||||
Console::verbose("正在关闭 Master 进程,pid=" . posix_getpid());
|
Console::verbose("正在关闭 Master 进程,pid=" . posix_getpid());
|
||||||
|
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
|
||||||
|
if (file_exists($pid_path)) {
|
||||||
|
unlink($pid_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,13 +25,12 @@ class OnStart implements SwooleEvent
|
|||||||
if (!Framework::$argv["disable-safe-exit"]) {
|
if (!Framework::$argv["disable-safe-exit"]) {
|
||||||
SignalListener::signalMaster($server);
|
SignalListener::signalMaster($server);
|
||||||
}
|
}
|
||||||
if (Framework::$argv["daemon"]) {
|
$daemon_data = json_encode([
|
||||||
$daemon_data = json_encode([
|
"pid" => $server->master_pid,
|
||||||
"pid" => $server->master_pid,
|
"stdout" => ZMConfig::get("global")["swoole"]["log_file"],
|
||||||
"stdout" => ZMConfig::get("global")["swoole"]["log_file"]
|
"daemon" => (bool)Framework::$argv["daemon"]
|
||||||
], 128 | 256);
|
], 128 | 256);
|
||||||
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
|
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class MessageUtil
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isAtMe($msg, $me_id) {
|
public static function isAtMe($msg, $me_id): bool {
|
||||||
return strpos($msg, CQ::at($me_id)) !== false;
|
return strpos($msg, CQ::at($me_id)) !== false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class MessageUtil
|
|||||||
* @param int $type
|
* @param int $type
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function getImageCQFromLocal($file, $type = 0): string {
|
public static function getImageCQFromLocal($file, int $type = 0): string {
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 0:
|
case 0:
|
||||||
return CQ::image("base64://" . base64_encode(file_get_contents($file)));
|
return CQ::image("base64://" . base64_encode(file_get_contents($file)));
|
||||||
@@ -90,7 +90,7 @@ class MessageUtil
|
|||||||
* @param $msg
|
* @param $msg
|
||||||
* @return array|string[]
|
* @return array|string[]
|
||||||
*/
|
*/
|
||||||
public static function splitCommand($msg) {
|
public static function splitCommand($msg): array {
|
||||||
$word = explodeMsg(str_replace("\r", "", $msg));
|
$word = explodeMsg(str_replace("\r", "", $msg));
|
||||||
if (empty($word)) $word = [""];
|
if (empty($word)) $word = [""];
|
||||||
if (count(explode("\n", $word[0])) >= 2) {
|
if (count(explode("\n", $word[0])) >= 2) {
|
||||||
@@ -98,7 +98,7 @@ class MessageUtil
|
|||||||
$first = split_explode(" ", array_shift($enter));
|
$first = split_explode(" ", array_shift($enter));
|
||||||
$word = array_merge($first, $enter);
|
$word = array_merge($first, $enter);
|
||||||
foreach ($word as $k => $v) {
|
foreach ($word as $k => $v) {
|
||||||
$word[$k] = trim($word[$k]);
|
$word[$k] = trim($v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $word;
|
return $word;
|
||||||
@@ -109,15 +109,15 @@ class MessageUtil
|
|||||||
* @param $obj
|
* @param $obj
|
||||||
* @return MatchResult
|
* @return MatchResult
|
||||||
*/
|
*/
|
||||||
public static function matchCommand($msg, $obj) {
|
public static function matchCommand($msg, $obj): MatchResult {
|
||||||
$ls = EventManager::$events[CQCommand::class] ?? [];
|
$ls = EventManager::$events[CQCommand::class] ?? [];
|
||||||
$word = self::splitCommand($msg);
|
$word = self::splitCommand($msg);
|
||||||
$matched = new MatchResult();
|
$matched = new MatchResult();
|
||||||
foreach ($ls as $v) {
|
foreach ($ls as $v) {
|
||||||
if (array_diff([$v->match, $v->pattern, $v->regex, $v->keyword, $v->end_with, $v->start_with], [""]) == []) continue;
|
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"])) &&
|
elseif (($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->group_id == 0 || ($v->group_id == ($obj["group_id"] ?? 0))) &&
|
||||||
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $obj["message_type"]))
|
($v->message_type == '' || ($v->message_type == $obj["message_type"]))
|
||||||
) {
|
) {
|
||||||
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
|
if (($word[0] != "" && $v->match == $word[0]) || in_array($word[0], $v->alias)) {
|
||||||
array_shift($word);
|
array_shift($word);
|
||||||
@@ -166,4 +166,61 @@ class MessageUtil
|
|||||||
ProcessManager::sendActionToWorker($i, "add_short_command", [$command, $reply]);
|
ProcessManager::sendActionToWorker($i, "add_short_command", [$command, $reply]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字符串转数组
|
||||||
|
* @param $msg
|
||||||
|
* @param bool $ignore_space
|
||||||
|
* @param false $trim_text
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function strToArray($msg, bool $ignore_space = true, bool $trim_text = false): array {
|
||||||
|
$arr = [];
|
||||||
|
while (($rear = mb_strstr($msg, '[CQ:')) !== false && ($end = mb_strstr($rear, ']', true)) !== false) {
|
||||||
|
// 把 [CQ: 前面的文字生成段落
|
||||||
|
$front = mb_strstr($msg, '[CQ:', true);
|
||||||
|
// 如果去掉空格都还有文字,或者不去掉空格有字符,且不忽略空格,则生成段落,否则不生成
|
||||||
|
if (($trim_front = trim($front)) !== '' || ($front !== '' && !$ignore_space)) {
|
||||||
|
$arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_front : $front)]];
|
||||||
|
}
|
||||||
|
// 处理 CQ 码
|
||||||
|
$content = mb_substr($end, 4);
|
||||||
|
$cq = explode(",", $content);
|
||||||
|
$object_type = array_shift($cq);
|
||||||
|
$object_params = [];
|
||||||
|
foreach ($cq as $v) {
|
||||||
|
$key = mb_strstr($v, "=", true);
|
||||||
|
$object_params[$key] = CQ::decode(mb_substr(mb_strstr($v, "="), 1), true);
|
||||||
|
}
|
||||||
|
$arr[] = ["type" => $object_type, "data" => $object_params];
|
||||||
|
$msg = mb_substr(mb_strstr($rear, ']'), 1);
|
||||||
|
}
|
||||||
|
if (($trim_msg = trim($msg)) !== '' || ($msg !== '' && !$ignore_space)) {
|
||||||
|
$arr[] = ['type' => 'text', 'data' => ['text' => CQ::decode($trim_text ? $trim_msg : $msg)]];
|
||||||
|
}
|
||||||
|
return $arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数组转字符串
|
||||||
|
* 纪念一下,这段代码完全由AI生成,没有人知道它是怎么写的,这句话是我自己写的,不知道是不是有人知道的
|
||||||
|
* @param array $array
|
||||||
|
* @return string
|
||||||
|
* @author Copilot
|
||||||
|
*/
|
||||||
|
public static function arrayToStr(array $array): string {
|
||||||
|
$str = "";
|
||||||
|
foreach ($array as $v) {
|
||||||
|
if ($v['type'] == 'text') {
|
||||||
|
$str .= $v['data']['text'];
|
||||||
|
} else {
|
||||||
|
$str .= "[CQ:" . $v['type'];
|
||||||
|
foreach ($v['data'] as $key => $value) {
|
||||||
|
$str .= "," . $key . "=" . CQ::encode($value, true);
|
||||||
|
}
|
||||||
|
$str .= "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $str;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user