mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-14 04:05:35 +08:00
2.8 补充特性更新 #114
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,6 +8,9 @@
|
||||
/temp/
|
||||
/site/
|
||||
|
||||
# 框架审计文件
|
||||
audit.log
|
||||
|
||||
# 进程锁文件
|
||||
.daemon_pid
|
||||
.zm_worker_*.pid
|
||||
|
||||
@@ -15,7 +15,7 @@ cd zhamao-app/
|
||||
bash -c "$(curl -fsSL https://api.zhamao.xin/go.sh)"
|
||||
|
||||
# 安装完成后的启动框架命令(2.5.0 版本后可省略掉 runtime/php 前缀)
|
||||
vendor/bin/start server
|
||||
./zhamao server
|
||||
|
||||
# 扩展用法:使用静态 PHP 版本的 Composer update
|
||||
runtime/composer update
|
||||
@@ -80,3 +80,15 @@ $ vendor/bin/start server
|
||||
|
||||
## 进阶环境部署和开发
|
||||
炸毛框架还支持更多种启动方式,如源码模式、守护进程模式,具体后续有关环境和部署的进阶教程,请查看 [进阶开发](/advanced/) 部分!
|
||||
|
||||
## Windows 注意事项
|
||||
|
||||
由于 Swoole 扩展目前无法原生支持 Windows 环境的 PHP,所以以上方式都是默认在 Linux、macOS 系统下的命令。
|
||||
|
||||
如果需要在 Windows 上开发和运行,可以使用 WSL(1 和 2 均可)、Linux 虚拟机、Docker 或 cygwin。
|
||||
|
||||
如果使用 WSL、虚拟机或 Docker,方式可以直接参考上方相关命令。如果使用 cygwin,可先从 [Swoole 官方仓库](https://github.com/swoole/swoole-src) 下载 swoole 的 cygwin 构建版本,然后下载 Composer 后安装依赖,直接运行框架即可。
|
||||
|
||||
## macOS 注意事项
|
||||
|
||||
macOS 理论上运行环境和 Linux 无异,但 macOS 由于不能静态编译,所以不能使用静态编译的 PHP 直接运行,需自行从 `Homebrew` 下载最新版 PHP(命令 `brew install php`),然后使用命令 `pecl install swoole` 来安装 Swoole 后运行框架。
|
||||
|
||||
@@ -11,3 +11,4 @@ parameters:
|
||||
dynamicConstantNames:
|
||||
- SWOOLE_VERSION
|
||||
- ZM_TEST_LOG_DEBUG
|
||||
- _PHAR_STUB_ID
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
use Stringable;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Entity\CQObject;
|
||||
|
||||
@@ -16,11 +17,7 @@ class CQ
|
||||
*/
|
||||
public static function at($qq): string
|
||||
{
|
||||
if (is_numeric($qq) || $qq === 'all') {
|
||||
return '[CQ:at,qq=' . $qq . ']';
|
||||
}
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的QQ号码({$qq})错误!");
|
||||
return ' ';
|
||||
return self::buildCQ('at', ['qq' => $qq]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,11 +27,7 @@ class CQ
|
||||
*/
|
||||
public static function face($id): string
|
||||
{
|
||||
if (is_numeric($id)) {
|
||||
return '[CQ:face,id=' . $id . ']';
|
||||
}
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的face id({$id})错误!");
|
||||
return ' ';
|
||||
return self::buildCQ('face', ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,13 +41,13 @@ class CQ
|
||||
*/
|
||||
public static function image(string $file, bool $cache = true, bool $flash = false, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:image,file=' . self::encode($file, true) .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
($flash ? ',type=flash' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'flash' => $flash ? 'type=flash' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('image', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,13 +61,13 @@ class CQ
|
||||
*/
|
||||
public static function record(string $file, bool $magic = false, bool $cache = true, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:record,file=' . self::encode($file, true) .
|
||||
($magic ? ',magic=1' : '') .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'magic' => $magic ? 'magic=true' : '',
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('record', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -87,12 +80,12 @@ class CQ
|
||||
*/
|
||||
public static function video(string $file, bool $cache = true, bool $proxy = true, int $timeout = -1): string
|
||||
{
|
||||
return
|
||||
'[CQ:video,file=' . self::encode($file, true) .
|
||||
(!$cache ? ',cache=0' : '') .
|
||||
(!$proxy ? ',proxy=false' : '') .
|
||||
($timeout != -1 ? (',timeout=' . $timeout) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'cache' => !$cache ? 'cache=0' : '',
|
||||
'proxy' => !$proxy ? 'proxy=false' : '',
|
||||
'timeout' => $timeout != -1 ? 'timeout=' . $timeout : '',
|
||||
];
|
||||
return self::buildCQ('video', ['file' => $file], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +124,10 @@ class CQ
|
||||
*/
|
||||
public static function poke($type, $id, string $name = ''): string
|
||||
{
|
||||
return "[CQ:poke,type={$type},id={$id}" . ($name != '' ? (',name=' . self::encode($name, true)) : '') . ']';
|
||||
$optional_values = [
|
||||
'name' => $name ? 'name=' . $name : '',
|
||||
];
|
||||
return self::buildCQ('poke', ['type' => $type, 'id' => $id], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +137,7 @@ class CQ
|
||||
*/
|
||||
public static function anonymous(int $ignore = 1): string
|
||||
{
|
||||
return '[CQ:anonymous' . ($ignore != 1 ? ',ignore=0' : '') . ']';
|
||||
return self::buildCQ('anonymous', [], ['ignore' => $ignore != 1 ? 'ignore=0' : '']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,28 +150,22 @@ class CQ
|
||||
*/
|
||||
public static function share(string $url, string $title, ?string $content = null, ?string $image = null): string
|
||||
{
|
||||
if ($content === null) {
|
||||
$c = '';
|
||||
} else {
|
||||
$c = ',content=' . self::encode($content, true);
|
||||
}
|
||||
if ($image === null) {
|
||||
$i = '';
|
||||
} else {
|
||||
$i = ',image=' . self::encode($image, true);
|
||||
}
|
||||
return '[CQ:share,url=' . self::encode($url, true) . ',title=' . self::encode($title, true) . $c . $i . ']';
|
||||
$optional_values = [
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
'image' => $image ? 'image=' . self::encode($image, true) : '',
|
||||
];
|
||||
return self::buildCQ('share', ['url' => $url, 'title' => $title], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友或群推荐名片
|
||||
* @param string $type 名片类型
|
||||
* @param int|string $type 名片类型
|
||||
* @param int|string $id 好友或群ID
|
||||
* @return string CQ码
|
||||
*/
|
||||
public static function contact(string $type, $id): string
|
||||
public static function contact($type, $id): string
|
||||
{
|
||||
return "[CQ:contact,type={$type},id={$id}]";
|
||||
return self::buildCQ('contact', ['type' => $type, 'id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,12 +178,11 @@ class CQ
|
||||
*/
|
||||
public static function location($lat, $lon, string $title = '', string $content = ''): string
|
||||
{
|
||||
return '[CQ:location' .
|
||||
',lat=' . self::encode((string) $lat, true) .
|
||||
',lon=' . self::encode((string) $lon, true) .
|
||||
($title != '' ? (',title=' . self::encode($title, true)) : '') .
|
||||
($content != '' ? (',content=' . self::encode($content, true)) : '') .
|
||||
']';
|
||||
$optional_values = [
|
||||
'title' => $title ? 'title=' . self::encode($title, true) : '',
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
];
|
||||
return self::buildCQ('location', ['lat' => $lat, 'lon' => $lon], $optional_values);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,26 +204,17 @@ class CQ
|
||||
case 'qq':
|
||||
case '163':
|
||||
case 'xiami':
|
||||
return "[CQ:music,type={$type},id={$id_or_url}]";
|
||||
return self::buildCQ('music', ['type' => $type, 'id' => $id_or_url]);
|
||||
case 'custom':
|
||||
if ($title === null || $audio === null) {
|
||||
Console::warning(zm_internal_errcode('E00035') . '传入CQ码实例的标题和音频链接不能为空!');
|
||||
return ' ';
|
||||
}
|
||||
if ($content === null) {
|
||||
$c = '';
|
||||
} else {
|
||||
$c = ',content=' . self::encode($content, true);
|
||||
}
|
||||
if ($image === null) {
|
||||
$i = '';
|
||||
} else {
|
||||
$i = ',image=' . self::encode($image, true);
|
||||
}
|
||||
return '[CQ:music,type=custom,url=' .
|
||||
self::encode($id_or_url, true) .
|
||||
',audio=' . self::encode($audio, true) . ',title=' . self::encode($title, true) . $c . $i .
|
||||
']';
|
||||
$optional_values = [
|
||||
'content' => $content ? 'content=' . self::encode($content, true) : '',
|
||||
'image' => $image ? 'image=' . self::encode($image, true) : '',
|
||||
];
|
||||
return self::buildCQ('music', ['type' => 'custom', 'url' => $id_or_url, 'audio' => $audio, 'title' => $title], $optional_values);
|
||||
default:
|
||||
Console::warning(zm_internal_errcode('E00035') . "传入的music type({$type})错误!");
|
||||
return ' ';
|
||||
@@ -248,7 +228,7 @@ class CQ
|
||||
*/
|
||||
public static function forward($id): string
|
||||
{
|
||||
return '[CQ:forward,id=' . self::encode((string) $id) . ']';
|
||||
return self::buildCQ('forward', ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -256,15 +236,15 @@ class CQ
|
||||
* 特殊说明: 需要使用单独的API /send_group_forward_msg 发送, 并且由于消息段较为复杂, 仅支持Array形式入参。
|
||||
* 如果引用消息和自定义消息同时出现, 实际查看顺序将取消息段顺序。
|
||||
* 另外按 CQHTTP 文档说明, data 应全为字符串, 但由于需要接收message 类型的消息, 所以 仅限此Type的content字段 支持Array套娃
|
||||
* @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送
|
||||
* @param int|string $user_id 转发消息id
|
||||
* @param string $nickname 发送者显示名字
|
||||
* @param string $content 具体消息
|
||||
* @return string CQ码
|
||||
* @deprecated 这个不推荐使用,因为 go-cqhttp 官方没有对其提供CQ码模式相关支持,仅支持Array模式发送
|
||||
*/
|
||||
public static function node($user_id, string $nickname, string $content): string
|
||||
{
|
||||
return "[CQ:node,user_id={$user_id},nickname=" . self::encode($nickname, true) . ',content=' . self::encode($content, true) . ']';
|
||||
return self::buildCQ('node', ['user_id' => $user_id, 'nickname' => $nickname, 'content' => $content]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,7 +254,7 @@ class CQ
|
||||
*/
|
||||
public static function xml(string $data): string
|
||||
{
|
||||
return '[CQ:xml,data=' . self::encode($data, true) . ']';
|
||||
return self::buildCQ('xml', ['data' => $data]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,7 +265,7 @@ class CQ
|
||||
*/
|
||||
public static function json(string $data, int $resid = 0): string
|
||||
{
|
||||
return '[CQ:json,data=' . self::encode($data, true) . ',resid=' . $resid . ']';
|
||||
return self::buildCQ('json', ['data' => $data, 'resid' => $resid]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,23 +276,18 @@ class CQ
|
||||
*/
|
||||
public static function _custom(string $type_name, array $params): string
|
||||
{
|
||||
$code = '[CQ:' . $type_name;
|
||||
foreach ($params as $k => $v) {
|
||||
$code .= ',' . $k . '=' . self::escape($v, true);
|
||||
}
|
||||
$code .= ']';
|
||||
return $code;
|
||||
return self::buildCQ($type_name, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 反转义字符串中的CQ码敏感符号
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是解码CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function decode(string $msg, bool $is_content = false): string
|
||||
public static function decode($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -321,24 +296,24 @@ class CQ
|
||||
|
||||
/**
|
||||
* 简单反转义替换CQ码的方括号
|
||||
* @param string $str 字符串
|
||||
* @return string 字符串
|
||||
* @param int|string|Stringable $str 字符串
|
||||
* @return string 字符串
|
||||
*/
|
||||
public static function replace(string $str): string
|
||||
public static function replace($str): string
|
||||
{
|
||||
$str = str_replace('{{', '[', $str);
|
||||
$str = str_replace('{{', '[', (string) $str);
|
||||
return str_replace('}}', ']', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义CQ码的特殊字符,同encode
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function escape(string $msg, bool $is_content = false): string
|
||||
public static function escape($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -347,13 +322,13 @@ class CQ
|
||||
|
||||
/**
|
||||
* 转义CQ码的特殊字符
|
||||
* @param string $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
* @param int|string|Stringable $msg 字符串
|
||||
* @param bool $is_content 如果是转义CQ码本体内容,则为false(默认),如果是参数内的字符串,则为true
|
||||
* @return string 转义后的CQ码
|
||||
*/
|
||||
public static function encode(string $msg, bool $is_content = false): string
|
||||
public static function encode($msg, bool $is_content = false): string
|
||||
{
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], $msg);
|
||||
$msg = str_replace(['&', '[', ']'], ['&', '[', ']'], (string) $msg);
|
||||
if ($is_content) {
|
||||
$msg = str_replace(',', ',', $msg);
|
||||
}
|
||||
@@ -438,4 +413,22 @@ class CQ
|
||||
}
|
||||
return $cqs;
|
||||
}
|
||||
|
||||
private static function buildCQ(string $cq, array $array, array $optional_values = []): string
|
||||
{
|
||||
$str = '[CQ:' . $cq;
|
||||
foreach ($array as $k => $v) {
|
||||
if ($v === null) {
|
||||
Console::warning('param ' . $k . ' cannot be set with null, empty CQ will returned!');
|
||||
return ' ';
|
||||
}
|
||||
$str .= ',' . $k . '=' . self::encode($v);
|
||||
}
|
||||
foreach ($optional_values as $v) {
|
||||
if ($v !== '') {
|
||||
$str .= ',' . $v;
|
||||
}
|
||||
}
|
||||
return $str . ']';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class RunServerCommand extends Command
|
||||
new InputOption('polling-watch', null, null, '强制启用轮询模式监听'),
|
||||
new InputOption('no-state-check', null, null, '关闭启动前框架运行状态检查'),
|
||||
new InputOption('private-mode', null, null, '启动时隐藏MOTD和敏感信息'),
|
||||
new InputOption('audit-mode', null, null, '启动时开启审计模式,独立将所有日志输出到文件供开发人员审计'),
|
||||
]);
|
||||
$this->setDescription('Run zhamao-framework | 启动框架');
|
||||
$this->setHelp('直接运行可以启动');
|
||||
|
||||
@@ -9,6 +9,7 @@ use Closure;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Error;
|
||||
use Exception;
|
||||
use Throwable;
|
||||
use ZM\Config\ZMConfig;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\InterruptException;
|
||||
@@ -110,7 +111,7 @@ class EventDispatcher
|
||||
|
||||
/**
|
||||
* @param mixed ...$params
|
||||
* @throws Exception
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function dispatchEvents(...$params)
|
||||
{
|
||||
@@ -137,7 +138,7 @@ class EventDispatcher
|
||||
} catch (InterruptException $e) {
|
||||
$this->store = $e->return_var;
|
||||
$this->status = self::STATUS_INTERRUPTED;
|
||||
} catch (Exception|Error $e) {
|
||||
} catch (Throwable $e) {
|
||||
$this->status = self::STATUS_EXCEPTION;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace ZM\Event;
|
||||
|
||||
use Iterator;
|
||||
use ReturnTypeWillChange;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class EventMapIterator implements Iterator
|
||||
{
|
||||
@@ -22,40 +23,56 @@ class EventMapIterator implements Iterator
|
||||
$this->class = $class;
|
||||
$this->method = $method;
|
||||
$this->event_name = $event_name;
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
Console::debug('从 [' . $this->offset . '] 开始获取');
|
||||
return EventManager::$event_map[$this->class][$this->method][$this->offset];
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
++$this->offset;
|
||||
Console::debug('下一个offset为 [' . ++$this->offset . ']');
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
#[ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->offset;
|
||||
Console::debug('返回key:' . $this->offset);
|
||||
return isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? $this->offset : null;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
public function valid($s = false): bool
|
||||
{
|
||||
return isset(EventManager::$event_map[$this->class][$this->method][$this->offset]);
|
||||
Console::debug(
|
||||
"[{$this->offset}] " .
|
||||
($s ? 'valid' : '') . '存在:' .
|
||||
(!isset(EventManager::$event_map[$this->class][$this->method][$this->offset]) ? Console::setColor('false', 'red') : ('true' .
|
||||
(is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true) ? ',是目标对象' : ',不是目标对象')))
|
||||
);
|
||||
return
|
||||
isset(EventManager::$event_map[$this->class][$this->method][$this->offset])
|
||||
&& is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
Console::debug('回到0');
|
||||
$this->offset = 0;
|
||||
$this->nextToValid();
|
||||
}
|
||||
|
||||
private function nextToValid()
|
||||
{
|
||||
while ($this->valid() && !is_a($this->current(), $this->event_name, true)) {
|
||||
while (
|
||||
isset(EventManager::$event_map[$this->class][$this->method][$this->offset])
|
||||
&& !is_a(EventManager::$event_map[$this->class][$this->method][$this->offset], $this->event_name, true)
|
||||
) {
|
||||
++$this->offset;
|
||||
}
|
||||
Console::debug('内部偏移offset为 [' . $this->offset . ']');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -744,6 +744,17 @@ class Framework
|
||||
case 'log-debug':
|
||||
Console::setLevel(4);
|
||||
break;
|
||||
case 'audit-mode':
|
||||
Console::warning('审计模式已开启,请正常执行需要审计的流程,然后Ctrl+C正常结束框架');
|
||||
Console::warning('审计的日志文件将存放到:' . DataProvider::getWorkingDir() . '/audit.log');
|
||||
if (file_exists(DataProvider::getWorkingDir() . '/audit.log')) {
|
||||
unlink(DataProvider::getWorkingDir() . '/audit.log');
|
||||
}
|
||||
Console::info('框架将于5秒后开始启动...');
|
||||
Console::setOutputFile(DataProvider::getWorkingDir() . '/audit.log');
|
||||
Console::setLevel(4);
|
||||
sleep(5);
|
||||
break;
|
||||
case 'log-theme':
|
||||
Console::$theme = $y;
|
||||
break;
|
||||
|
||||
@@ -9,23 +9,32 @@ namespace ZM\Module;
|
||||
*/
|
||||
abstract class ModuleBase
|
||||
{
|
||||
/** @var string 模块名称 */
|
||||
protected $module_name;
|
||||
|
||||
/** @var array 事件列表 */
|
||||
protected $events = [];
|
||||
|
||||
public function __construct($module_name)
|
||||
/**
|
||||
* @param string $module_name 模块名称
|
||||
*/
|
||||
public function __construct(string $module_name)
|
||||
{
|
||||
$this->module_name = $module_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* 获取模块名称
|
||||
* @return string
|
||||
*/
|
||||
public function getModuleName()
|
||||
{
|
||||
return $this->module_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取事件列表
|
||||
*/
|
||||
public function getEvents(): array
|
||||
{
|
||||
return $this->events;
|
||||
|
||||
@@ -197,6 +197,10 @@ class ModuleUnpacker
|
||||
$prompt = !is_string($this->module['unpack']['global-config-override']) ? '请根据模块提供者提供的要求进行修改 global.php 中对应的配置项' : $this->module['unpack']['global-config-override'];
|
||||
Console::warning('模块作者要求用户手动修改 global.php 配置文件中的项目:');
|
||||
Console::warning('*' . $prompt);
|
||||
if (STDIN === false) {
|
||||
Console::warning('检测到终端无法输入,请手动修改 global.php 配置文件中的项目');
|
||||
return;
|
||||
}
|
||||
echo Console::setColor('请输入修改模式,y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改):[y/e/N] ', 'gold');
|
||||
$r = strtolower(trim(fgets(STDIN)));
|
||||
switch ($r) {
|
||||
|
||||
@@ -7,10 +7,10 @@ declare(strict_types=1);
|
||||
namespace ZM\Utils;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use Error;
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use Swoole\Process;
|
||||
use Throwable;
|
||||
use ZM\Annotation\Command\TerminalCommand;
|
||||
use ZM\ConnectionManager\ManagerGM;
|
||||
use ZM\Console\Console;
|
||||
@@ -22,8 +22,7 @@ class Terminal
|
||||
public static $default_commands = false;
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
* @throws Error
|
||||
* @throws Throwable
|
||||
* @return bool
|
||||
* @noinspection PhpMissingReturnTypeInspection
|
||||
* @noinspection PhpUnused
|
||||
|
||||
@@ -15,21 +15,31 @@ use ZM\Module\ModuleBase;
|
||||
*/
|
||||
class ZMServer
|
||||
{
|
||||
/** @var string App名称 */
|
||||
protected $app_name;
|
||||
|
||||
/** @var ModuleBase[] */
|
||||
protected $modules = [];
|
||||
|
||||
public function __construct($app_name)
|
||||
/**
|
||||
* @param string $app_name App名称
|
||||
*/
|
||||
public function __construct(string $app_name)
|
||||
{
|
||||
$this->app_name = $app_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $module_class
|
||||
*/
|
||||
public function addModule($module_class)
|
||||
{
|
||||
$this->modules[] = $module_class;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws InitException
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
Console::setLevel(4);
|
||||
@@ -39,7 +49,7 @@ class ZMServer
|
||||
}
|
||||
}
|
||||
echo "Running...\n";
|
||||
if (defined('WORKDING_DIR')) {
|
||||
if (defined('WORKING_DIR')) {
|
||||
throw new InitException();
|
||||
}
|
||||
|
||||
@@ -57,10 +67,7 @@ class ZMServer
|
||||
(new Framework($options, true))->start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAppName()
|
||||
public function getAppName(): string
|
||||
{
|
||||
return $this->app_name;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ use ZM\Context\Context;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Event\EventManager;
|
||||
use ZM\Exception\RobotNotFoundException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
use ZM\Framework;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\ZMAtomic;
|
||||
@@ -183,8 +182,7 @@ function match_args(string $pattern, string $subject)
|
||||
/**
|
||||
* 判断当前连接类型是否为传入的$type
|
||||
*
|
||||
* @param string $type 连接类型
|
||||
* @throws ZMKnownException
|
||||
* @param string $type 连接类型
|
||||
*/
|
||||
function current_connection_is(string $type): bool
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
const _PHAR_STUB_ID = '__generated_id__';
|
||||
|
||||
function loader__generated_id__()
|
||||
{
|
||||
$obj = json_decode(file_get_contents(__DIR__ . '/zmplugin.json'), true);
|
||||
@@ -12,4 +14,8 @@ function loader__generated_id__()
|
||||
require_once Phar::running() . '/' . $v;
|
||||
}
|
||||
}
|
||||
if ('__generate' . 'd_id__' === _PHAR_STUB_ID) {
|
||||
echo 'Cannot execute this file directly!' . PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
return json_decode(file_get_contents(__DIR__ . '/zmplugin.json'), true) ?? ['zm_module' => false];
|
||||
|
||||
31
tests/ZM/Event/EventMapIteratorTest.php
Normal file
31
tests/ZM/Event/EventMapIteratorTest.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Event;
|
||||
|
||||
use Module\Example\Hello;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZM\Annotation\CQ\CommandArgument;
|
||||
use ZM\Event\EventMapIterator;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class EventMapIteratorTest extends TestCase
|
||||
{
|
||||
public function testIterator(): void
|
||||
{
|
||||
$iterator = new EventMapIterator(Hello::class, 'randNum', CommandArgument::class);
|
||||
$arr = iterator_to_array($iterator);
|
||||
$this->assertArrayNotHasKey(0, $arr);
|
||||
$this->assertArrayNotHasKey(1, $arr);
|
||||
$this->assertArrayHasKey(2, $arr);
|
||||
$this->assertArrayHasKey(3, $arr);
|
||||
$this->assertInstanceOf(CommandArgument::class, $arr[2]);
|
||||
$this->assertInstanceOf(CommandArgument::class, $arr[3]);
|
||||
$iterator = new EventMapIterator(Hello::class, 'closeUnknownConn', CommandArgument::class);
|
||||
$ls = iterator_to_array($iterator);
|
||||
$this->assertCount(0, $ls);
|
||||
}
|
||||
}
|
||||
35
tests/ZM/Utils/TerminalTest.php
Normal file
35
tests/ZM/Utils/TerminalTest.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\ZM\Utils;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Throwable;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Utils\Terminal;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class TerminalTest extends TestCase
|
||||
{
|
||||
public function testInit()
|
||||
{
|
||||
Console::setLevel(4);
|
||||
Terminal::init();
|
||||
Console::setLevel(0);
|
||||
$this->expectOutputRegex('/Initializing\ Terminal/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function testExecuteCommand()
|
||||
{
|
||||
Console::setLevel(2);
|
||||
Terminal::executeCommand('echo zhamao-framework');
|
||||
Console::setLevel(0);
|
||||
$this->expectOutputRegex('/zhamao-framework/');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user