Compare commits

..

5 Commits
2.2.0 ... 2.2.2

Author SHA1 Message Date
jerry
61e3818563 update to 2.2.2 version finally
clean redundant code
fix API reply in @OnTick for multi-process
fix loop error reporting
2021-01-29 23:34:34 +08:00
jerry
776ec98a3e fix waitMessage timeout bug 2021-01-29 22:32:29 +08:00
jerry
f3e844bb0a update to 2.2.2 version
fix QQBot error
clean code
2021-01-29 22:27:10 +08:00
jerry
a55cd4ed05 update docs 2021-01-29 21:37:02 +08:00
jerry
8a985620f9 update to 2.2.1 version
fix a compatibility bug
2021-01-29 21:36:14 +08:00
23 changed files with 123 additions and 150 deletions

View File

@@ -3,7 +3,7 @@
"description": "High performance QQ robot and web server development framework",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "2.2.0",
"version": "2.2.2",
"extra": {
"exclude_annotate": [
"src/ZM"

View File

@@ -1,5 +1,20 @@
# 更新日志v2 版本)
## v2.2.2
> 更新时间2021.1.29
- 修复:模块文件错误时避免循环报错
- 优化:代码结构
- 修复:在不同进程时调用机器人 API 无法返回且报错的 bug
- **修复机器人无法连接的问题2.1.6 ~ 2.2.1 受影响)**
## v2.2.1
> 更新时间2021.1.29
- 修复:配置文件兼容性问题
## v2.2.0
> 更新时间2021.1.29

View File

@@ -1,31 +0,0 @@
<?php
namespace Custom\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class CustomCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'custom';
protected function configure() {
$this->setDescription("custom description | 自定义命令的描述字段");
$this->addOption("failure", null, null, "以错误码为1返回结果");
// ...
}
protected function execute(InputInterface $input, OutputInterface $output) {
if ($input->getOption("failure")) {
$output->writeln("<error>Hello error! I am wrong message.</error>");
return Command::FAILURE;
} else {
$output->writeln("<comment>Hello world! I am successful message.</comment>");
return Command::SUCCESS;
}
}
}

View File

@@ -1,4 +1,4 @@
<?php #plain
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */ #plain
//这里写你的全局函数
function pgo(callable $func, $name = "default") {

View File

@@ -3,17 +3,16 @@
namespace ZM\API;
use Co;
use ZM\ConnectionManager\ConnectionObject;
use ZM\Console\Console;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
use ZM\Store\ZMAtomic;
use ZM\Utils\CoMessage;
trait CQAPI
{
/**
* @param ConnectionObject $connection
* @param $connection
* @param $reply
* @param |null $function
* @return bool|array
@@ -35,21 +34,20 @@ trait CQAPI
$r[$api_id] = [
"data" => $reply,
"time" => microtime(true),
"self_id" => $connection->getOption("connect_id")
"self_id" => $connection->getOption("connect_id"),
"echo" => $api_id
];
if ($function === true) $r[$api_id]["coroutine"] = Co::getuid();
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
if (server()->push($connection->getFd(), json_encode($reply))) {
if ($function === true) {
Co::suspend();
return CoMessage::yieldByWS($r[$api_id], ["echo"], 60);
} else {
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
$data = $r[$api_id];
unset($r[$api_id]);
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
return isset($data['result']) ? $data['result'] : null;
}
return true;
} else {

View File

@@ -228,9 +228,9 @@ class ZMRobot
/**
* 群组单人禁言
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
* @param int $group_id
* @param int $user_id
* @param int $duration
* @param $group_id
* @param $user_id
* @param $duration
* @return array|bool|null
*/
public function setGroupBan($group_id, $user_id, $duration = 1800) {

View File

@@ -9,11 +9,10 @@ use ZM\Console\Console;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\Http\{HandleAfter, HandleBefore, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping};
use ZM\Annotation\Http\{HandleAfter, HandleBefore, HandleException, Middleware, MiddlewareClass, RequestMapping};
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed;
use ZM\Http\RouteManager;
use ZM\Utils\DataProvider;
class AnnotationParser
{
@@ -91,6 +90,7 @@ class AnnotationParser
if ($vs instanceof ErgodicAnnotation) {
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
$copy = clone $vs;
/** @noinspection PhpUndefinedFieldInspection */
$copy->method = $method->getName();
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
}

View File

@@ -3,7 +3,6 @@
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;

View File

@@ -4,7 +4,6 @@
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;

View File

@@ -5,7 +5,6 @@ namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\Interfaces\Rule;
/**
* @Annotation

View File

@@ -6,7 +6,6 @@ namespace ZM\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Utils\DataProvider;
class DaemonStatusCommand extends DaemonCommand
{

View File

@@ -145,7 +145,7 @@ class Context implements ContextInterface
if ($prompt != "") $this->reply($prompt);
try {
$r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]);
$r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())], $timeout);
} catch (Exception $e) {
$r = false;
}

View File

@@ -31,7 +31,6 @@ class DB
/**
* @param $table_name
* @param bool $enable_cache
* @return Table
* @throws DbException
*/
@@ -95,6 +94,7 @@ class DB
$ps = $conn->prepare($line);
if ($ps === false) {
SqlPoolStorage::$sql_pool->put(null);
/** @noinspection PhpUndefinedFieldInspection */
throw new DbException("SQL语句查询错误" . $line . ",错误信息:" . $conn->error);
} else {
if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) {

View File

@@ -47,7 +47,7 @@ class Table
return new DeleteBody($this);
}
public function statement($line){
public function statement(){
$this->cache = [];
//TODO: 无返回的statement语句
}

View File

@@ -87,7 +87,6 @@ class EventDispatcher
/**
* @param mixed ...$params
* @throws Exception
* @throws InterruptException
*/
public function dispatchEvents(...$params) {
try {
@@ -104,10 +103,7 @@ class EventDispatcher
} catch (InterruptException $e) {
$this->store = $e->return_var;
$this->status = self::STATUS_INTERRUPTED;
} catch (Exception $e) {
$this->status = self::STATUS_EXCEPTION;
throw $e;
} catch (Error $e) {
} catch (Exception | Error $e) {
$this->status = self::STATUS_EXCEPTION;
throw $e;
}

View File

@@ -74,17 +74,17 @@ class ServerEventHandler
}
Process::signal(SIGINT, function () use ($r) {
echo "\r";
Console::warning("Server interrupted by keyboard on Master.");
Console::warning("Server interrupted(SIGINT) on Master.");
if ((Framework::$server->inotify ?? null) !== null)
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
ZMUtil::stop();
});
if(Framework::$argv["daemon"]) {
if (Framework::$argv["daemon"]) {
$daemon_data = json_encode([
"pid" => \server()->master_pid,
"stdout" => ZMConfig::get("global")["swoole"]["log_file"]
],128|256);
file_put_contents(DataProvider::getWorkingDir()."/.daemon_pid", $daemon_data);
], 128 | 256);
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
}
if (Framework::$argv["watch"]) {
if (extension_loaded('inotify')) {
@@ -238,7 +238,7 @@ class ServerEventHandler
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray');
ZMUtil::stop();
posix_kill($server->master_pid, SIGINT);
}
} else {
// 这里是TaskWorker初始化的内容部分
@@ -255,7 +255,7 @@ class ServerEventHandler
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
Console::error("Maybe it caused by your own code if in your own Module directory.");
Console::log($e->getTraceAsString(), 'gray');
ZMUtil::stop();
posix_kill($server->master_pid, SIGINT);
}
}
}
@@ -314,7 +314,7 @@ class ServerEventHandler
*/
public function onRequest(?Request $request, ?\Swoole\Http\Response $response) {
$response = new Response($response);
foreach(ZMConfig::get("global")["http_header"] as $k => $v) {
foreach (ZMConfig::get("global")["http_header"] as $k => $v) {
$response->setHeader($k, $v);
}
unset(Context::$context[Co::getCid()]);
@@ -491,16 +491,20 @@ class ServerEventHandler
/**
* @SwooleHandler("pipeMessage")
* @param $server
* @param Server $server
* @param $src_worker_id
* @param $data
* @throws InterruptException
* @throws Exception
*/
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];
@@ -557,10 +561,11 @@ class ServerEventHandler
* @param Server|null $server
* @param Server\Task $task
* @return mixed
* @noinspection PhpUnusedParameterInspection
*/
public function onTask(?Server $server, Server\Task $task) {
$data = $task->data;
switch($data["action"]) {
switch ($data["action"]) {
case "runMethod":
$c = $data["class"];
$ss = new $c();

View File

@@ -4,8 +4,6 @@
namespace ZM\Http;
use ZM\Console\Console;
class Response
{

View File

@@ -3,7 +3,7 @@
namespace ZM\Module;
use Swoole\Coroutine;
use Exception;
use ZM\Annotation\CQ\CQAPIResponse;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
@@ -14,8 +14,6 @@ use ZM\Annotation\CQ\CQRequest;
use ZM\Event\EventDispatcher;
use ZM\Exception\InterruptException;
use ZM\Exception\WaitTimeoutException;
use ZM\Store\LightCacheInside;
use ZM\Store\Lock\SpinLock;
use ZM\Utils\CoMessage;
/**
@@ -26,22 +24,25 @@ class QQBot
{
/**
* @throws InterruptException
* @throws Exception
*/
public function handle() {
try {
$data = json_decode(context()->getFrame()->data, true);
set_coroutine_params(["data" => $data]);
if (isset($data["echo"])) {
if (CoMessage::resumeByWS()) {
EventDispatcher::interrupt();
}
}
if (isset($data["post_type"])) {
//echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
set_coroutine_params(["data" => $data]);
ctx()->setCache("level", 0);
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
if ($data["post_type"] != "meta_event") {
$r = $this->dispatchBeforeEvents($data); // before在这里执行元事件不执行before为减少不必要的调试日志
if ($r->store === "block") EventDispatcher::interrupt();
}
if (CoMessage::resumeByWS()) {
EventDispatcher::interrupt();
}
//Console::warning("最上数据包:".json_encode($data));
$this->dispatchEvents($data);
} else {
@@ -55,7 +56,7 @@ class QQBot
/**
* @param $data
* @return EventDispatcher
* @throws InterruptException
* @throws Exception
*/
public function dispatchBeforeEvents($data) {
$before = new EventDispatcher(CQBefore::class);
@@ -177,45 +178,16 @@ class QQBot
}
}
/**
* @param $req
* @throws Exception
*/
private function dispatchAPIResponse($req) {
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
if (isset($req["echo"]) && is_numeric($req["echo"])) {
$r = LightCacheInside::get("wait_api", "wait_api");
if (isset($r[$req["echo"]])) {
$origin = $r[$req["echo"]];
$self_id = $origin["self_id"];
$response = [
"status" => $status,
"retcode" => $retcode,
"data" => $data,
"self_id" => $self_id,
"echo" => $req["echo"]
];
set_coroutine_params(["cq_response" => $response]);
$dispatcher = new EventDispatcher(CQAPIResponse::class);
$dispatcher->setRuleFunction(function (CQAPIResponse $response) {
return $response->retcode == ctx()->getCQResponse()["retcode"];
});
$dispatcher->dispatchEvents($response);
$origin_ctx = ctx()->copy();
set_coroutine_params($origin_ctx);
if (($origin["coroutine"] ?? false) !== false) {
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
$r[$req["echo"]]["result"] = $response;
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
Coroutine::resume($origin['coroutine']);
}
SpinLock::lock("wait_api");
$r = LightCacheInside::get("wait_api", "wait_api");
unset($r[$req["echo"]]);
LightCacheInside::set("wait_api", "wait_api", $r);
SpinLock::unlock("wait_api");
}
}
set_coroutine_params(["cq_response" => $req]);
$dispatcher = new EventDispatcher(CQAPIResponse::class);
$dispatcher->setRuleFunction(function (CQAPIResponse $response) {
return $response->retcode == ctx()->getCQResponse()["retcode"];
});
$dispatcher->dispatchEvents($req);
}
}

View File

@@ -23,9 +23,10 @@ class LightCacheInside
$result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create();
if ($result === false) {
self::$last_error = '系统内存不足,申请失败';
return $result;
return false;
} else {
return true;
}
return $result;
}
/**

View File

@@ -15,7 +15,7 @@ class WorkerCache
public static $transfer = [];
public static function get($key) {
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
$config = self::$config ?? ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
if ($config["worker"] === server()->worker_id) {
return self::$store[$key] ?? null;
} else {
@@ -35,15 +35,19 @@ class WorkerCache
return true;
} else {
$action = ["action" => $async ? "asyncSetWorkerCache" : "setWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
if(!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
unset(self::$transfer[zm_cid()]);
return $p;
return self::processRemote($action, $async, $config);
}
}
private static function processRemote($action, $async, $config) {
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
if(!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
unset(self::$transfer[zm_cid()]);
return $p;
}
public static function unset($key, $async = false) {
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
@@ -52,13 +56,7 @@ class WorkerCache
return true;
} else {
$action = ["action" => $async ? "asyncUnsetWorkerCache" : "unsetWorkerCache", "key" => $key, "cid" => zm_cid()];
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
if(!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
unset(self::$transfer[zm_cid()]);
return $p;
return self::processRemote($action, $async, $config);
}
}
@@ -70,13 +68,7 @@ class WorkerCache
return true;
} else {
$action = ["action" => $async ? "asyncAddWorkerCache" : "addWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
// if(!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
unset(self::$transfer[zm_cid()]);
return $p;
return self::processRemote($action, $async, $config);
}
}
@@ -88,13 +80,7 @@ class WorkerCache
return true;
} else {
$action = ["action" => $async ? "asyncSubWorkerCache" : "subWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
// if(!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
unset(self::$transfer[zm_cid()]);
return $p;
return self::processRemote($action, $async, $config);
}
}
}

View File

@@ -16,7 +16,7 @@ class CoMessage
* @param array $hang
* @param array $compare
* @param int $timeout
* @return bool
* @return mixed
* @throws Exception
*/
public static function yieldByWS(array $hang, array $compare, $timeout = 600) {
@@ -48,4 +48,35 @@ class CoMessage
if ($result === null) return false;
return $result;
}
public static function resumeByWS() {
$dat = ctx()->getData();
$last = null;
SpinLock::lock("wait_api");
$all = LightCacheInside::get("wait_api", "wait_api") ?? [];
foreach ($all as $k => $v) {
if(!isset($v["compare"])) continue;
foreach ($v["compare"] as $vs) {
if (!isset($v[$vs], $dat[$vs])) continue 2;
if ($v[$vs] != $dat[$vs]) {
continue 2;
}
}
$last = $k;
}
if($last !== null) {
$all[$last]["result"] = $dat;
LightCacheInside::set("wait_api", "wait_api", $all);
SpinLock::unlock("wait_api");
if ($all[$last]["worker_id"] != server()->worker_id) {
ZMUtil::sendActionToWorker($all[$last]["worker_id"], "resume_ws_message", $all[$last]);
} else {
Co::resume($all[$last]["coroutine"]);
}
return true;
} else {
SpinLock::unlock("wait_api");
return false;
}
}
}

View File

@@ -51,4 +51,8 @@ class ZMUtil
return ZMBuf::$instance[$class];
}
}
public static function sendActionToWorker($target_id, $action, $data) {
server()->sendMessage(json_encode(["action" => $action, "data" => $data]), $target_id);
}
}

View File

@@ -24,6 +24,7 @@ function phar_classloader($p) {
return;
}
try {
/** @noinspection PhpIncludeInspection */
require_once $filepath;
} catch (Exception $e) {
echo "Error when finding class: " . $p . PHP_EOL;
@@ -75,7 +76,7 @@ function unicode_decode($str) {
/**
* 获取模块文件夹下的每个类文件的类名称
* @param $dir
* @param string $indoor_name
* @param $indoor_name
* @return array
*/
function getAllClasses($dir, $indoor_name) {
@@ -95,6 +96,7 @@ function getAllClasses($dir, $indoor_name) {
continue 2;
}
}
if ($v == "global_function.php") continue;
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}