mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-03-17 20:54:52 +08:00
add @CommandArgument annotation
* add @CommandArgument and relevant event changes * fix unknown bug * remove relative constant for CommandArgument
This commit is contained in:
parent
a393b3aff5
commit
d19d7acedd
@ -78,4 +78,5 @@
|
||||
| E00074 | 参数非法 | 检查调用的参数是否正常(此处可能有多处问题,请看具体调用栈炸掉的地方) |
|
||||
| E00075 | Cron表达式非法 | 检查 @Cron 中的表达式是否书写格式正确 |
|
||||
| E00076 | Cron检查间隔非法 | 检查 @Cron 中的检查间隔(毫秒)是否在1000-60000之间 |
|
||||
| E00077 | 输入了非法的消息匹配参数类型 | 检查传入 `@CommandArgument` 或 `checkArguments()` 方法有没有传入非法的 `type` 参数 |
|
||||
| E99999 | 未知错误 | |
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\Annotation\CQ\CQMessage;
|
||||
@ -136,19 +137,18 @@ class Hello
|
||||
* 问法2:从1到20的随机数
|
||||
* @CQCommand("随机数")
|
||||
* @CQCommand(pattern="*从*到*的随机数")
|
||||
*
|
||||
* @CommandArgument(name="num1",type="int",required=true)
|
||||
* @CommandArgument(name="num2",type="int",required=true)
|
||||
* @param mixed $num1
|
||||
* @param mixed $num2
|
||||
* @return string
|
||||
*/
|
||||
public function randNum()
|
||||
public function randNum($num1, $num2)
|
||||
{
|
||||
// 获取第一个数字类型的参数
|
||||
$num1 = ctx()->getNumArg('请输入第一个数字');
|
||||
// 获取第二个数字类型的参数
|
||||
$num2 = ctx()->getNumArg('请输入第二个数字');
|
||||
$a = min(intval($num1), intval($num2));
|
||||
$b = max(intval($num1), intval($num2));
|
||||
$a = min($num1, $num2);
|
||||
$b = max($num1, $num2);
|
||||
// 回复用户结果
|
||||
return '随机数是:' . mt_rand($a, $b);
|
||||
return '从' . $a . '到' . $b . '的随机数是:' . mt_rand($a, $b);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -21,6 +21,7 @@ use ZM\Annotation\Interfaces\Level;
|
||||
use ZM\Annotation\Module\Closed;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Exception\AnnotationException;
|
||||
use ZM\Utils\Manager\RouteManager;
|
||||
use ZM\Utils\ZMUtil;
|
||||
@ -156,11 +157,12 @@ class AnnotationParser
|
||||
}
|
||||
$inserted[$v][$method_name] = true;
|
||||
}
|
||||
}
|
||||
if ($method_anno instanceof RequestMapping) {
|
||||
} elseif ($method_anno instanceof RequestMapping) {
|
||||
RouteManager::importRouteByAnnotation($method_anno, $method_name, $v, $methods_annotations);
|
||||
} elseif ($method_anno instanceof Middleware) {
|
||||
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno;
|
||||
} else {
|
||||
EventManager::$event_map[$method_anno->class][$method_anno->method][] = $method_anno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
146
src/ZM/Annotation/CQ/CommandArgument.php
Normal file
146
src/ZM/Annotation/CQ/CommandArgument.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Annotation\CQ;
|
||||
|
||||
use Attribute;
|
||||
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
use ZM\Annotation\Interfaces\ErgodicAnnotation;
|
||||
use ZM\Exception\InvalidArgumentException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
|
||||
/**
|
||||
* Class CommandArgument
|
||||
* @Annotation
|
||||
* @NamedArgumentConstructor()
|
||||
* @Target("ALL")
|
||||
*/
|
||||
#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_ALL)]
|
||||
class CommandArgument extends AnnotationBase implements ErgodicAnnotation
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $description = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $type = 'string';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $required = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $prompt = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $default = '';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $timeout = 60;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $error_prompt_policy = 1;
|
||||
|
||||
/**
|
||||
* @param string $name 参数名称(可以是中文)
|
||||
* @param string $description 参数描述(默认为空)
|
||||
* @param bool $required 参数是否必需,如果是必需,为true(默认为false)
|
||||
* @param string $prompt 当参数为必需时,返回给用户的提示输入的消息(默认为"请输入$name")
|
||||
* @param string $default 当required为false时,未匹配到参数将自动使用default值(默认为空)
|
||||
* @param int $timeout prompt超时时间(默认为60秒)
|
||||
* @throws InvalidArgumentException|ZMKnownException
|
||||
*/
|
||||
public function __construct(
|
||||
string $name,
|
||||
string $description = '',
|
||||
string $type = 'string',
|
||||
bool $required = false,
|
||||
string $prompt = '',
|
||||
string $default = '',
|
||||
int $timeout = 60,
|
||||
int $error_prompt_policy = 1
|
||||
) {
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->type = $this->fixTypeName($type);
|
||||
$this->required = $required;
|
||||
$this->prompt = $prompt;
|
||||
$this->default = $default;
|
||||
$this->timeout = $timeout;
|
||||
$this->error_prompt_policy = $error_prompt_policy;
|
||||
if ($this->type === 'bool') {
|
||||
if ($this->default === '') {
|
||||
$this->default = 'yes';
|
||||
}
|
||||
if (!in_array($this->default, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为布尔型,检测到非法的默认值 ' . $this->default);
|
||||
}
|
||||
} elseif ($this->type === 'number') {
|
||||
if ($this->default === '') {
|
||||
$this->default = '0';
|
||||
}
|
||||
if (!is_numeric($this->default)) {
|
||||
throw new InvalidArgumentException('CommandArgument参数 ' . $name . ' 类型传入类型应为数字型,检测到非法的默认值 ' . $this->default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getTypeErrorPrompt(): string
|
||||
{
|
||||
return '参数类型错误,请重新输入!';
|
||||
}
|
||||
|
||||
public function getErrorQuitPrompt(): string
|
||||
{
|
||||
return '参数类型错误,停止输入!';
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ZMKnownException
|
||||
*/
|
||||
protected function fixTypeName(string $type): string
|
||||
{
|
||||
$table = [
|
||||
'str' => 'string',
|
||||
'string' => 'string',
|
||||
'strings' => 'string',
|
||||
'byte' => 'string',
|
||||
'num' => 'number',
|
||||
'number' => 'number',
|
||||
'int' => 'number',
|
||||
'float' => 'number',
|
||||
'double' => 'number',
|
||||
'boolean' => 'bool',
|
||||
'bool' => 'bool',
|
||||
'true' => 'bool',
|
||||
'any' => 'any',
|
||||
'all' => 'any',
|
||||
'*' => 'any',
|
||||
];
|
||||
if (array_key_exists($type, $table)) {
|
||||
return $table[$type];
|
||||
}
|
||||
throw new ZMKnownException(zm_internal_errcode('E00077') . 'Invalid argument type: ' . $type . ', only support any, string, number and bool !');
|
||||
}
|
||||
}
|
||||
30
src/ZM/Entity/InputArguments.php
Normal file
30
src/ZM/Entity/InputArguments.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Entity;
|
||||
|
||||
class InputArguments
|
||||
{
|
||||
private $arguments;
|
||||
|
||||
public function __construct(array $arguments)
|
||||
{
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
public function getArguments(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
|
||||
public function getArgument($name)
|
||||
{
|
||||
return $this->arguments[$name] ?? null;
|
||||
}
|
||||
|
||||
public function get($name)
|
||||
{
|
||||
return $this->getArgument($name);
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,8 @@ class EventManager
|
||||
|
||||
public static $middleware_map = [];
|
||||
|
||||
public static $event_map = [];
|
||||
|
||||
public static $middlewares = [];
|
||||
|
||||
public static $req_mapping = [];
|
||||
@ -33,6 +35,7 @@ class EventManager
|
||||
Console::debug("Adding event {$event_name} at @Anonymous");
|
||||
} else {
|
||||
Console::debug("Adding event {$event_name} at " . ($event_obj->class) . ':' . ($event_obj->method));
|
||||
self::$event_map[$event_obj->class][$event_obj->method][] = $event_obj;
|
||||
}
|
||||
self::$events[$event_name][] = $event_obj;
|
||||
(new AnnotationParser())->sortByLevel(self::$events, $event_name);
|
||||
|
||||
61
src/ZM/Event/EventMapIterator.php
Normal file
61
src/ZM/Event/EventMapIterator.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Event;
|
||||
|
||||
use Iterator;
|
||||
use ReturnTypeWillChange;
|
||||
|
||||
class EventMapIterator implements Iterator
|
||||
{
|
||||
private $offset = 0;
|
||||
|
||||
private $class;
|
||||
|
||||
private $method;
|
||||
|
||||
private $event_name;
|
||||
|
||||
public function __construct($class, $method, $event_name)
|
||||
{
|
||||
$this->class = $class;
|
||||
$this->method = $method;
|
||||
$this->event_name = $event_name;
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
public function current()
|
||||
{
|
||||
return EventManager::$event_map[$this->class][$this->method][$this->offset];
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
++$this->offset;
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset(EventManager::$event_map[$this->class][$this->method][$this->offset]);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->offset = 0;
|
||||
}
|
||||
|
||||
private function nextToValid()
|
||||
{
|
||||
while ($this->valid() && !is_a($this->current(), $this->event_name, true)) {
|
||||
++$this->offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -161,10 +161,13 @@ class QQBot
|
||||
}
|
||||
$s = MessageUtil::matchCommand($msg, ctx()->getData());
|
||||
if ($s->status !== false) {
|
||||
if (!empty($s->match)) {
|
||||
ctx()->setCache('match', $s->match);
|
||||
$match = $s->match;
|
||||
|
||||
$input_arguments = MessageUtil::checkArguments($s->object->class, $s->object->method, $match);
|
||||
if (!empty($match)) {
|
||||
ctx()->setCache('match', $match);
|
||||
}
|
||||
$dispatcher->dispatchEvent($s->object, null);
|
||||
$dispatcher->dispatchEvent($s->object, null, ...$input_arguments);
|
||||
if (is_string($dispatcher->store)) {
|
||||
ctx()->reply($dispatcher->store);
|
||||
}
|
||||
|
||||
@ -8,12 +8,16 @@ use Exception;
|
||||
use Iterator;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
use ZM\API\CQ;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\InputArguments;
|
||||
use ZM\Entity\MatchResult;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Event\EventMapIterator;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Requests\ZMRequest;
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Utils\Manager\WorkerManager;
|
||||
@ -327,4 +331,96 @@ class MessageUtil
|
||||
WorkerCache::set('command_helps', $helps);
|
||||
return $helps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws WaitTimeoutException
|
||||
*/
|
||||
public static function checkArguments(string $class, string $method, array &$match): array
|
||||
{
|
||||
$iterator = new EventMapIterator($class, $method, CommandArgument::class);
|
||||
$offset = 0;
|
||||
$arguments = [];
|
||||
foreach ($iterator as $annotation) {
|
||||
/** @var CommandArgument $annotation */
|
||||
switch ($annotation->type) {
|
||||
case 'string':
|
||||
case 'any':
|
||||
if (isset($match[$offset])) {
|
||||
$arguments[$annotation->name] = $match[$offset++];
|
||||
} else {
|
||||
if ($annotation->required) {
|
||||
$value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout);
|
||||
$arguments[$annotation->name] = $value;
|
||||
} else {
|
||||
$arguments[$annotation->name] = $annotation->default;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'number':
|
||||
for ($k = $offset; $k < count($match); ++$k) {
|
||||
$v = $match[$k];
|
||||
if (is_numeric($v)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = $v / 1;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
if (!$annotation->required) {
|
||||
if (is_numeric($annotation->default)) {
|
||||
$arguments[$annotation->name] = $annotation->default / 1;
|
||||
}
|
||||
}
|
||||
if (!isset($arguments[$annotation->name])) {
|
||||
$value = ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout);
|
||||
if (!is_numeric($value)) {
|
||||
if ($annotation->error_prompt_policy === 1) {
|
||||
$value = ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout);
|
||||
if (!is_numeric($value)) {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
} else {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
}
|
||||
$arguments[$annotation->name] = $value / 1;
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
for ($k = $offset; $k < count($match); ++$k) {
|
||||
$v = strtolower($match[$k]);
|
||||
if (in_array(strtolower($v), TRUE_LIST)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = true;
|
||||
break 2;
|
||||
}
|
||||
if (in_array(strtolower($v), FALSE_LIST)) {
|
||||
array_splice($match, $k, 1);
|
||||
$arguments[$annotation->name] = false;
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
if (!$annotation->required) {
|
||||
$default = $annotation->default === '' ? true : 'true';
|
||||
$arguments[$annotation->name] = in_array($default, TRUE_LIST);
|
||||
}
|
||||
if (!isset($arguments[$annotation->name])) {
|
||||
$value = strtolower(ctx()->waitMessage($annotation->prompt === '' ? ('请输入' . $annotation->name) : $annotation->prompt, $annotation->timeout));
|
||||
if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
if ($annotation->error_prompt_policy === 1) {
|
||||
$value = strtolower(ctx()->waitMessage($annotation->getTypeErrorPrompt(), $annotation->timeout));
|
||||
if (!in_array($value, array_merge(TRUE_LIST, FALSE_LIST))) {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
} else {
|
||||
throw new WaitTimeoutException(ctx(), $annotation->getErrorQuitPrompt());
|
||||
}
|
||||
}
|
||||
$arguments[$annotation->name] = in_array($value, TRUE_LIST);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
container()->instance(InputArguments::class, new InputArguments($arguments));
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@ if (!is_dir(CRASH_DIR)) {
|
||||
@mkdir(CRASH_DIR);
|
||||
}
|
||||
|
||||
const TRUE_LIST = ['yes', 'y', 'true', 'on', '是', '对', true];
|
||||
const FALSE_LIST = ['no', 'n', 'false', 'off', '否', '错', false];
|
||||
|
||||
const CONN_WEBSOCKET = 0;
|
||||
const CONN_HTTP = 1;
|
||||
const ZM_MATCH_ALL = 0;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user