update to 2.5.0-b2 (build 409)

This commit is contained in:
crazywhalecc 2021-07-04 15:45:30 +08:00
parent 4ee16d4fc6
commit 7ec847e576
28 changed files with 467 additions and 87 deletions

View File

@ -33,8 +33,8 @@
"zhamao/request": "^1.1",
"zhamao/connection-manager": "^1.0",
"jelix/version": "^2.0",
"league/climate": "^3.7",
"psy/psysh": "^0.10.8"
"league/climate": "^3.6",
"psy/psysh": "^0.10"
},
"suggest": {
"ext-ctype": "Use C/C++ extension instead of polyfill will be more efficient",

View File

@ -129,7 +129,7 @@ $config['remote_terminal'] = [
/** 模块(插件)加载器的相关设置 */
$config['module_loader'] = [
'enable_hotload' => true,
'enable_hotload' => false,
'load_path' => $config['zm_data'] . 'modules'
];

View File

@ -39,17 +39,17 @@ public function index() {
1. Linux 命令行(会跑 Linux 程序)
2. php 7.2+ 开发环境(项目会持续支持最新的 PHP 版本)
3. HTTP/WebSocket 协议
4. OneBot 机器人聊天接口标准
需要值得注意的是,本教程中所涉及的内容均为尽可能翻译为白话的方式进行描述,但对于框架的组件或事件等需要单独拆分说明文档的部分则需要足够详细,所以本教程提供一个快速上手的教程,并且会将最典型的安装方式写到快速教程篇。
!!! bug "文档提示"
此文档采用 MkDocs 驱动,但因为本文档的搜索组件原生不支持中文搜索,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西
此文档采用 MkDocs 驱动,文档的搜索组件原生不支持中文搜索,且分词很难控制,所以搜索体验会大打折扣,敬请谅解!搜不到不是没这个东西,建议这种情况可以自行翻阅目录查看
## 框架特色
- 支持MySQL数据库连接池自带查询缓存提高多查询时的效率
- Websocket 服务器、HTTP 服务器兼容运行,一个框架多个用处
- 支持命令、自然语言处理等多种插件形式
@ -61,6 +61,7 @@ public function index() {
## 文档主题
### 主题
<div class="tx-switch">
<button data-md-color-scheme="default"><code>默认模式</code></button>
<button data-md-color-scheme="slate"><code>暗黑模式</code></button>
@ -80,6 +81,7 @@ public function index() {
</script>
### 主色调
<div class="tx-switch">
<button data-md-color-primary="red"><code>red</code></button>
<button data-md-color-primary="pink"><code>pink</code></button>
@ -105,6 +107,7 @@ public function index() {
</div>
### 辅色调
<div class="tx-switch"> <button data-md-color-accent="red"><code>red</code></button> <button data-md-color-accent="pink"><code>pink</code></button> <button data-md-color-accent="purple"><code>purple</code></button> <button data-md-color-accent="deep-purple"><code>deep purple</code></button> <button data-md-color-accent="indigo"><code>indigo</code></button> <button data-md-color-accent="blue"><code>blue</code></button> <button data-md-color-accent="light-blue"><code>light blue</code></button> <button data-md-color-accent="cyan"><code>cyan</code></button> <button data-md-color-accent="teal"><code>teal</code></button> <button data-md-color-accent="green"><code>green</code></button> <button data-md-color-accent="light-green"><code>light green</code></button> <button data-md-color-accent="lime"><code>lime</code></button> <button data-md-color-accent="yellow"><code>yellow</code></button> <button data-md-color-accent="amber"><code>amber</code></button> <button data-md-color-accent="orange"><code>orange</code></button> <button data-md-color-accent="deep-orange"><code>deep orange</code></button> </div>
<script>

View File

@ -11,6 +11,7 @@
- 全局配置文件新增 `runtime` 配置项,可自定义配置 Swoole 的一些运行时参数,目前可配置一键协程化的 Hook 参数。
- 新增 `module:list` 命令,用于查看未打包和已打包的模块列表。
- 新增 `module:pack` 命令,用于打包现有 src 目录下的模块。
- 新增 `module:unpack` 命令,用于解包现有的 phar 模块包。
- 新增打包框架功能,支持将用户的整个项目连同炸毛框架打包为一个 phar 便携运行,使用命令 `build`
- 新增快捷脚本 `./zhamao`,效果同 `vendor/bin/start``bin/start`
- 新增启动参数 `--interact`:又重新支持交互终端了,但还是有点问题,不推荐使用。
@ -24,11 +25,22 @@
- `DataProvider` 新增 `isRelativePath()` 方法,检查路径是否为相对路径(根据第一个字符是否是 '/' 来判断)。
- `ZMUtil` 新增 `getClassesPsr4()` 方法,用于根据 Psr-4 标准来获取目录下的所有类文件。
- 新增全局错误码,可以根据错误码在文档内快速定位和解决问题。
- 中间件和注解事件支持回溯,可以快速查看调用栈(比如中间件可以知道自己是在哪个注解事件中被调用)。
- 使用 `./zhamao build` 来构建框架的 phar 包时增加显示进度条。
- EventDispatcher 新增方法 `getEid()``getClass()`,分别用于获取事件分发 ID 和注解事件的注解类名称。
- 新增 EventTracer用于追踪事件的调用栈。
- 中间件支持传参。
以下是版本**修改内容**
- 启动文件 `vendor/bin/start` 修改为 shell 脚本,可自动寻找 PHP 环境。
- 全局强制依赖 `league/climate` 组件。
- 修复框架启动时的信息显示换行问题。
- 修复框架使用 Phar 方式启动时导致的报错。
- 修复使用 Ctrl+C 结束时一部分用户卡住的 bug。
- 远程和本地终端去掉 stop 命令,建议直接使用发 SIGTERM 方式结束框架。
- 全局配置文件的 `zm_data` 根目录默认修改为 `WORKING_DIR`
- 命令 `systemd:generate` 修改为 `generate:systemd`
- 全局配置文件删除 `server_event_handler_class` 项,此项废弃。
- 修复部分 CQ 码解析过程中没有转义的问题。
- 将 `ZMRobot` 类转移为 `OneBotV11` 类,但提供兼容。
@ -63,6 +75,15 @@
如果最后一种归档方式启动的框架是从源码模式打包而来,那么 `FrameworkRootDir` 就与 `SourceRootDir` 相同。
**版本部分兼容问题变化**
理论上如果不使用框架内部未开放的接口方法的话,从 2.4 升级到 2.5 是非常自然的,但是也有一部分可能会造成不兼容的问题。
- 生成 systemd 配置文件的命令 `systemd:generate` 变成 `generate:systemd`
- 全局配置文件中的 `zm_data` 的父目录由 `__DIR__ . "/../"` 改为 `WORKING_DIR`
- 2.5 版本将 ZMRobot 类中的所有函数方法都移动到了 `OneBotV11` 类中,但原先的 ZMRobot 还可以使用。
-
## v2.4.4 (build 405)
> 更新时间2021.3.29

View File

@ -20,6 +20,9 @@ extra_javascript:
extra_css:
- assets/css/extra.css
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.1.1/styles/default.min.css
plugins:
- search:
lang: ja
markdown_extensions:
- admonition
- pymdownx.tabbed
@ -62,6 +65,7 @@ nav:
- 基本配置: guide/basic-config.md
- 编写模块: guide/write-module.md
- 注册事件响应: guide/register-event.md
- 错误码对照表: guide/errcode.md
- 事件和注解:
- 事件和注解: event/index.md
- 机器人注解事件: event/robot-annotations.md

View File

@ -50,9 +50,6 @@ class OneBotV11
return new ZMRobot($r[array_rand($r)]);
}
public static function getFirst() {
}
/**
* @return ZMRobot[]
*/

View File

@ -122,7 +122,7 @@ class AnnotationParser
if ($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->middleware;
$this->middleware_map[$method_anno->class][$method_anno->method][] = $method_anno;
}
}
}

View File

@ -22,4 +22,9 @@ class Middleware extends AnnotationBase implements ErgodicAnnotation
* @Required()
*/
public $middleware;
/**
* @var string[]
*/
public $params = [];
}

View File

@ -3,6 +3,7 @@
namespace ZM\Command;
use League\CLImate\CLImate;
use Phar;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
@ -56,22 +57,22 @@ class BuildCommand extends Command
@unlink($target_dir . $filename);
$phar = new Phar($target_dir . $filename);
$phar->startBuffering();
$climate = new CLImate();
$allow_dir = ["bin", "config", "resources", "src", "vendor", "composer.json", "README.md", "zhamao"];
$all = DataProvider::scanDirFiles(DataProvider::getSourceRootDir(), true, true);
$all = array_filter($all, function ($x) {
$dirs = preg_match("/(^(bin|config|resources|src|vendor)\/|^(composer\.json|README\.md)$)/", $x);
return !($dirs !== 1);
});
sort($all);
$progress = $climate->progress()->total(count($all));
$archive_dir = DataProvider::getSourceRootDir();
$scan = scandir($archive_dir);
if ($scan[0] == ".") {
unset($scan[0], $scan[1]);
}
foreach ($scan as $v) {
if (in_array($v, $allow_dir)) {
if (is_dir($archive_dir . "/" . $v)) {
$this->addDirectory($phar, $archive_dir . "/" . $v, $v);
} elseif (is_file($archive_dir . "/" . $v)) {
$phar->addFile($archive_dir . "/" . $v, $v);
}
}
foreach ($all as $k => $v) {
$phar->addFile($archive_dir . "/" . $v, $v);
$progress->current($k + 1, "Adding " . $v);
}
$phar->setStub(
@ -81,18 +82,4 @@ class BuildCommand extends Command
$phar->stopBuffering();
$this->output->writeln("Successfully built. Location: " . $target_dir . "$filename");
}
private function addDirectory(Phar $phar, $dir, $local_dir) {
$o = scandir($dir);
if ($o[0] == ".") {
unset($o[0], $o[1]);
}
foreach ($o as $v) {
if (is_dir($dir . "/" . $v)) {
$this->addDirectory($phar, $dir . "/" . $v, $local_dir . "/" . $v);
} elseif (is_file($dir . "/" . $v)) {
$phar->addFile($dir . "/" . $v, $local_dir . "/" . $v);
}
}
}
}

View File

@ -1,25 +1,28 @@
<?php
namespace ZM\Command;
namespace ZM\Command\Generate;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Config\ZMConfig;
use ZM\Utils\DataProvider;
class SystemdCommand extends Command
class SystemdGenerateCommand extends Command
{
// the name of the command (the part after "bin/console")
protected static $defaultName = 'systemd:generate';
protected static $defaultName = 'generate:systemd';
protected function configure() {
$this->setDescription("生成框架的 systemd 配置文件");
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$path = $this->generate();
$output->writeln("<info>成功生成 systemd 文件,位置:".$path."</info>");
$output->writeln("<info>成功生成 systemd 文件,位置:" . $path . "</info>");
$output->writeln("<info>有关如何使用 systemd 配置文件,请访问 `https://github.com/zhamao-robot/zhamao-framework/issues/36`</info>");
return 0;
}
@ -30,10 +33,10 @@ class SystemdCommand extends Command
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
$s .= "\nWorkingDirectory=" . getcwd();
global $argv;
$s .= "\nExecStart=".PHP_BINARY." {$argv[0]} server";
$s .= "\nExecStart=" . PHP_BINARY . " {$argv[0]} server";
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
@mkdir(getcwd() . "/resources/");
file_put_contents(getcwd() . "/resources/zhamao.service", $s);
return getcwd() . "/resources/zhamao.service";
file_put_contents(ZMConfig::get("global", "zm_data") . "zhamao.service", $s);
return ZMConfig::get("global", "zm_data") . "zhamao.service";
}
}

View File

@ -23,7 +23,7 @@ class ModuleListCommand extends Command
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量
@ -52,6 +52,9 @@ class ModuleListCommand extends Command
$out_list["目录"] = str_replace(DataProvider::getSourceRootDir() . "/", "", $v["module-path"]);
$this->printList($out_list);
}
if ($list === []) {
echo Console::setColor("没有发现已编写打包配置文件zm.json的模块", "yellow") . PHP_EOL;
}
$list = ModuleManager::getPackedModules();
foreach ($list as $v) {
echo "[" . Console::setColor($v["name"], "gold") . "]" . PHP_EOL;
@ -62,7 +65,7 @@ class ModuleListCommand extends Command
$this->printList($out_list);
}
if ($list === []) {
echo Console::setColor("没有发现已编写打包配置文件zm.json的模块和已打包且装载的模块!", "yellow") . PHP_EOL;
echo Console::setColor("没有发现已打包且装载的模块!", "yellow") . PHP_EOL;
}
return 0;
}

View File

@ -27,7 +27,7 @@ class ModulePackCommand extends Command
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量

View File

@ -7,6 +7,7 @@ namespace ZM\Command\Module;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
@ -19,20 +20,25 @@ class ModuleUnpackCommand extends Command
protected static $defaultName = 'module:unpack';
protected function configure() {
$this->addArgument("module-name", InputArgument::REQUIRED);
$this->setDefinition([
new InputArgument("module-name", InputArgument::REQUIRED),
new InputOption("override-light-cache", null, null, "覆盖现有的LightCache项目"),
new InputOption("override-zm-data", null, null, "覆盖现有的zm_data文件"),
new InputOption("override-source", null, null, "覆盖现有的源码文件")
]);
$this->setDescription("Unpack a phar module into src directory");
$this->setHelp("此功能将phar格式的模块包解包到src目录下。");
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量
include_once DataProvider::getFrameworkRootDir()."/src/ZM/global_defines.php";
Console::init(
ZMConfig::get("global", "info_level") ?? 2,
ZMConfig::get("global", "info_level") ?? 4,
null,
$args["log-theme"] ?? "default",
($o = ZMConfig::get("console_color")) === false ? [] : $o
@ -49,7 +55,7 @@ class ModuleUnpackCommand extends Command
$output->writeln("<error>不存在打包的模块 ".$input->getArgument("module-name")." !</error>");
return 1;
}
$result = ModuleManager::unpackModule($list[$input->getArgument("module-name")]);
$result = ModuleManager::unpackModule($list[$input->getArgument("module-name")], $input->getOptions());
if ($result) Console::success("解压完成!");
else Console::error("解压失败!");
return 0;

View File

@ -11,6 +11,7 @@ use ZM\Command\CheckConfigCommand;
use ZM\Command\Daemon\DaemonReloadCommand;
use ZM\Command\Daemon\DaemonStatusCommand;
use ZM\Command\Daemon\DaemonStopCommand;
use ZM\Command\Generate\SystemdGenerateCommand;
use ZM\Command\InitCommand;
use ZM\Command\Module\ModuleListCommand;
use ZM\Command\Module\ModulePackCommand;
@ -20,21 +21,32 @@ use ZM\Command\RunServerCommand;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use ZM\Command\SystemdCommand;
use ZM\Console\Console;
use ZM\Exception\InitException;
class ConsoleApplication extends Application
{
const VERSION_ID = 408;
const VERSION = "2.5.0-b1";
private static $obj = null;
const VERSION_ID = 409;
const VERSION = "2.5.0-b2";
/**
* @throws InitException
*/
public function __construct(string $name = 'UNKNOWN') {
if (self::$obj !== null) throw new InitException(zm_internal_errcode("E00069") . "Initializing another Application is not allowed!");
define("ZM_VERSION_ID", self::VERSION_ID);
define("ZM_VERSION", self::VERSION);
self::$obj = $this;
parent::__construct($name, ZM_VERSION);
}
/**
* @throws InitException
*/
public function initEnv($with_default_cmd = ""): ConsoleApplication {
if (defined("WORKDING_DIR")) throw new InitException();
$this->selfCheck();
define("WORKING_DIR", getcwd());
@ -77,7 +89,7 @@ class ConsoleApplication extends Application
new DaemonStopCommand(),
new RunServerCommand(), //运行主服务的指令控制器
new PureHttpCommand(), //纯HTTP服务器指令
new SystemdCommand()
new SystemdGenerateCommand()
]);
if (LOAD_MODE === 1) {
$this->add(new CheckConfigCommand());

View File

@ -137,17 +137,18 @@ class EventDispatcher
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法下的 ruleFunc 为真,继续执行方法本身 ...");
if (isset(EventManager::$middleware_map[$q_c][$q_f])) {
$middlewares = EventManager::$middleware_map[$q_c][$q_f];
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware" . implode(", ", $middlewares));
if ($this->log) Console::verbose("[事件分发{$this->eid}] " . $q_c . "::" . $q_f . " 方法还绑定了 Middleware" . implode(", ", array_map(function($x){ return $x->middleware; }, $middlewares)));
$before_result = true;
$r = [];
foreach ($middlewares as $k => $middleware) {
if (!isset(EventManager::$middlewares[$middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware}\"!");
$middleware_obj = EventManager::$middlewares[$middleware];
if (!isset(EventManager::$middlewares[$middleware->middleware])) throw new AnnotationException("Annotation parse error: Unknown MiddlewareClass named \"{$middleware->middleware}\"!");
$middleware_obj = EventManager::$middlewares[$middleware->middleware];
$before = $middleware_obj["class"];
//var_dump($middleware_obj);
$r[$k] = new $before();
$r[$k]->class = $q_c;
$r[$k]->method = $q_f;
$r[$k]->middleware = $middleware;
if (isset($middleware_obj["before"])) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在前置事件,执行中 ...");
$rs = $middleware_obj["before"];
@ -173,7 +174,7 @@ class EventDispatcher
}
if ($this->log) Console::verbose("[事件分发{$this->eid}] 方法 " . $q_c . "::" . $q_f . " 执行过程中抛出了异常,正在倒序查找 Middleware 中的捕获方法 ...");
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
$middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware];
if (!isset($middleware_obj["exceptions"])) continue;
foreach ($middleware_obj["exceptions"] as $name => $method) {
if ($e instanceof $name) {
@ -186,7 +187,7 @@ class EventDispatcher
throw $e;
}
for ($i = count($middlewares) - 1; $i >= 0; --$i) {
$middleware_obj = EventManager::$middlewares[$middlewares[$i]];
$middleware_obj = EventManager::$middlewares[$middlewares[$i]->middleware];
if (isset($middleware_obj["after"], $r[$i])) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] Middleware 存在后置事件,执行中 ...");
$r[$i]->{$middleware_obj["after"]}(...$params);
@ -207,4 +208,18 @@ class EventDispatcher
return true;
}
}
/**
* @return int
*/
public function getEid(): int {
return $this->eid;
}
/**
* @return string
*/
public function getClass(): string {
return $this->class;
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace ZM\Event;
use ZM\Annotation\AnnotationBase;
class EventTracer
{
/**
* 获取当前注解事件的注解类如CQCommand对象
* @return AnnotationBase|null
*/
public static function getCurrentEvent() {
$list = debug_backtrace();
foreach ($list as $v) {
if ((($v["object"] ?? null) instanceof EventDispatcher) && $v["function"] == "dispatchEvent") {
return $v["args"][0];
}
}
return null;
}
/**
* 获取当前注解事件的中间件列表
* @return array|mixed|null
*/
public static function getCurrentEventMiddlewares() {
$current_event = self::getCurrentEvent();
if (!isset($current_event->class, $current_event->method)) return null;
return EventManager::$middleware_map[$current_event->class][$current_event->method] ?? [];
}
public static function getEventTraceList() {
$result = [];
$list = debug_backtrace();
foreach ($list as $v) {
if ((($v["object"] ?? null) instanceof EventDispatcher) && $v["function"] == "dispatchEvent") {
$result[] = $v["args"][0];
}
}
return $result;
}
}

View File

@ -7,6 +7,7 @@ namespace ZM\Event\SwooleEvent;
use Swoole\Server;
use Swoole\Timer;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\ConnectionManager\ManagerGM;
use ZM\Console\Console;
use ZM\Event\SwooleEvent;
@ -19,6 +20,10 @@ class OnWorkerExit implements SwooleEvent
{
public function onCall(Server $server, $worker_id) {
Timer::clearAll();
foreach($server->connections as $v) {
$server->close($v);
Console::info("Closing connection #".$v);
}
Console::info("正在结束 Worker #".$worker_id.",进程内可能有事务在运行...");
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace ZM\Exception;
class InitException extends ZMException
{
}

View File

@ -55,7 +55,7 @@ class Framework
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nError path: " . DataProvider::getSourceRootDir() . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量
@ -107,6 +107,7 @@ class Framework
define("ZM_WORKER_NUM", $worker);
ZMAtomic::init();
$out["working_dir"] = DataProvider::getWorkingDir();
// 打印初始信息
$out["listen"] = ZMConfig::get("global", "host") . ":" . ZMConfig::get("global", "port");
@ -138,12 +139,12 @@ class Framework
$out["php_version"] = PHP_VERSION;
$out["swoole_version"] = SWOOLE_VERSION;
}
if ($add_port) {
$conf = ZMConfig::get("global", "remote_terminal");
$out["terminal"] = $conf["host"] . ":" . $conf["port"];
}
$out["working_dir"] = DataProvider::getWorkingDir();
self::printProps($out, $tty_width, $args["log-theme"] === null);
if ($args["preview"]) {
exit();
@ -215,7 +216,7 @@ class Framework
$r = ob_get_clean();
if (!empty($r)) $serv->send($fd, $r);
if (!in_array(trim($data), ['r', 'reload', 'stop'])) $serv->send($fd, ">>> ");
if (!in_array(trim($data), ['r', 'reload'])) $serv->send($fd, ">>> ");
});
$port->on('close', function ($serv, $fd) {
@ -310,7 +311,14 @@ class Framework
}
private function loadServerEvents() {
$r = exec(PHP_BINARY . " " . DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php", $output, $result_code);
if (\Phar::running() !== "") {
ob_start();
$r = include_once DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php";
$r = ob_get_clean();
$result_code = 0;
} else {
$r = exec(PHP_BINARY . " " . DataProvider::getFrameworkRootDir() . "/src/ZM/script_setup_loader.php", $output, $result_code);
}
if ($result_code !== 0) {
Console::error("Parsing code error!");
exit(1);
@ -440,7 +448,7 @@ class Framework
private static function writeNoDouble($k, $v, &$line_data, &$line_width, &$current_line, $colorful, $max_border) {
$tmp_line = $k . ": " . $v;
//Console::info("写入[".$tmp_line."]");
if (strlen($tmp_line) >= $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
if (strlen($tmp_line) > $line_width[$current_line]) { //输出的内容太多了,以至于一行都放不下一个,要折行
$title_strlen = strlen($k . ": ");
$content_len = $line_width[$current_line] - $title_strlen;

View File

@ -104,6 +104,7 @@ class ModulePacker
$this->addFiles(); //添加文件
$this->addLightCacheStore(); //保存light-cache-store指定的项
$this->addModuleConfig(); //生成module-config.json
$this->addZMDataFiles(); //添加需要保存的zm_data下的目录或文件
$this->addEntry(); //生成模块的入口文件module_entry.php
$this->phar->stopBuffering();
@ -159,7 +160,7 @@ class ModulePacker
foreach ($this->module['light-cache-store'] as $v) {
$r = LightCache::get($v);
if ($r === null) {
Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:`$v` 不存在或值为null无法为其保存。');
Console::warning(zm_internal_errcode("E00045") . 'LightCache 项:' . $v . ' 不存在或值为null无法为其保存。');
} else {
$store[$v] = $r;
Console::info('打包LightCache持久化项' . $v);
@ -179,9 +180,10 @@ class ModulePacker
'autoload-psr-4' => $this->generatePharAutoload(),
'unpack' => [
'composer-autoload-items' => $this->getComposerAutoloadItems(),
'global-config-override' => !empty($this->module['global-config-override'] ?? []) ? $this->module['global-config-override'] : false
'global-config-override' => $this->module['global-config-override'] ?? false
],
'allow-hotload' => empty($this->module['global-config-override'] ?? []) && !isset($this->module['depends'])
'allow-hotload' => $this->module["allow-hotload"] ?? false,
'pack-time' => time()
];
$this->phar->addFromString('zmplugin.json', json_encode($stub_values, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
$this->module_config = $stub_values;
@ -203,4 +205,28 @@ class ModulePacker
$this->phar->setStub($this->phar->createDefaultStub('module_entry.php'));
}
/**
* @throws ModulePackException
*/
private function addZMDataFiles() {
$base_dir = realpath(DataProvider::getDataFolder());
if (is_array($this->module["zm-data-store"] ?? null)) {
foreach ($this->module["zm-data-store"] as $v) {
if (is_dir($base_dir . '/' . $v)) {
$v = rtrim($v, '/');
Console::info("Adding external zm_data dir: " . $v);
$files = DataProvider::scanDirFiles($base_dir . '/' . $v, true, true);
foreach ($files as $single) {
$this->phar->addFile($base_dir . '/' . $v . '/' . $single, 'zm_data/' . $v . '/' . $single);
}
} elseif (is_file($base_dir . '/' . $v)) {
Console::info("Add external zm_data file: " . $v);
$this->phar->addFile($base_dir . '/' . $v, 'zm_data/' . $v);
} else {
throw new ModulePackException(zm_internal_errcode("E00066")."`zmdata-store` 指定的文件或目录不存在");
}
}
}
}
}

View File

@ -4,20 +4,214 @@
namespace ZM\Module;
use Jelix\Version\VersionComparator;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Exception\ModulePackException;
use ZM\Exception\ZMException;
use ZM\Store\LightCache;
use ZM\Utils\DataProvider;
use ZM\Utils\Manager\ModuleManager;
class ModuleUnpacker
{
private $module;
private $module_config = null;
private $light_cache = null;
private $unpack_data_files = [];
public function __construct(array $module) {
$this->module = $module;
}
/**
* 解包模块
* @param bool $override_light_cache
* @param bool $override_data_files
* @param bool $override_source
* @return array
* @throws ModulePackException
* @throws ZMException
*/
public function unpack(): array {
// TODO: 解包模块到src
public function unpack(bool $override_light_cache = false, bool $override_data_files = false, bool $override_source = false): array {
$this->checkConfig();
$this->checkDepends();
$this->checkLightCacheStore();
$this->checkZMDataStore();
$this->mergeComposer();
$this->copyZMDataStore($override_data_files);
$this->copyLightCacheStore($override_light_cache);
$this->mergeGlobalConfig();
$this->copySource($override_source);
return $this->module;
}
/**
* 检查模块配置文件是否正确地放在phar包的位置中
* @return void
*/
private function checkConfig() {
$config = "phar://" . $this->module["phar-path"] . "/" . $this->module["module-root-path"] . "/zm.json";
$this->module_config = json_decode(file_get_contents($config), true);
}
/**
* 检查模块依赖关系
* @throws ModulePackException
* @throws ZMException
*/
private function checkDepends() {
$configured = ModuleManager::getConfiguredModules();
$depends = $this->module_config["depends"] ?? [];
foreach ($depends as $k => $v) {
if (!isset($configured[$k])) {
throw new ModulePackException(zm_internal_errcode("E00064") . "模块 " . $this->module_config["name"] . " 依赖的模块 $k 不存在");
}
$current_ver = $configured[$k]["version"] ?? "1.0";
if (!VersionComparator::compareVersionRange($current_ver, $v)) {
throw new ModulePackException(zm_internal_errcode("E00063") . "模块 " . $this->module_config["name"] . " 依赖的模块 $k 版本依赖不符合条件(现有版本: " . $current_ver . ", 需求版本: " . $v . "");
}
}
}
/**
* 检查 light-cache-store 项是否合规
* @throws ModulePackException
*/
private function checkLightCacheStore() {
if (isset($this->module_config["light-cache-store"])) {
$file = json_decode(file_get_contents("phar://" . $this->module["phar-path"] . "/light_cache_store.json"), true);
if ($file === null) throw new ModulePackException(zm_internal_errcode("E00065") . "模块系统检测到打包的模块文件中未含有 `light_cache_store.json` 文件");
$this->light_cache = $file;
}
}
/**
* @throws ModulePackException
*/
private function checkZMDataStore() {
if (is_array($this->module_config["zm-data-store"] ?? null)) {
foreach ($this->module_config["zm-data-store"] as $v) {
if (!file_exists("phar://" . $this->module["phar-path"] . "/zm_data/" . $v)) {
throw new ModulePackException(zm_internal_errcode("E00067") . "压缩包损坏,内部找不到待解压的 zm_data 原始数据");
}
$file = "phar://" . $this->module["phar-path"] . "/zm_data/" . $v;
if (is_dir($file)) {
$all = DataProvider::scanDirFiles($file, true, true);
foreach ($all as $single) {
$this->unpack_data_files[$file . "/" . $single] = DataProvider::getDataFolder() . $v . "/" . $single;
}
} else {
$this->unpack_data_files[$file] = DataProvider::getDataFolder() . $v;
}
}
}
}
private function mergeComposer() {
$composer_file = DataProvider::getWorkingDir() . "/composer.json";
if (!file_exists($composer_file)) throw new ModulePackException(zm_internal_errcode("E00068"));
$composer = json_decode(file_get_contents($composer_file), true);
if (isset($this->module_config["composer-extend-autoload"])) {
$autoload = $this->module_config["composer-extend-autoload"];
if (isset($autoload["psr-4"])) {
Console::info("Adding extended autoload psr-4 for composer");
$composer["autoload"]["psr-4"] = isset($composer["autoload"]["psr-4"]) ? array_merge($composer["autoload"]["psr-4"], $autoload["psr-4"]) : $autoload["psr-4"];
}
if (isset($autoload["files"])) {
Console::info("Adding extended autoload file for composer");
$composer["autoload"]["files"] = isset($composer["autoload"]["files"]) ? array_merge($composer["autoload"]["files"], $autoload["files"]) : $autoload["files"];
}
}
if (isset($this->module_config["composer-extend-require"])) {
foreach ($this->module_config["composer-extend-require"] as $k => $v) {
Console::info("Adding extended required composer library: " . $k);
if (!isset($composer[$k])) $composer[$k] = $v;
}
}
file_put_contents($composer_file, json_encode($composer, 64 | 128 | 256));
}
/**
* @throws ModulePackException
*/
private function copyZMDataStore($override_data) {
foreach ($this->unpack_data_files as $k => $v) {
$pathinfo = pathinfo($v);
if (!is_dir($pathinfo["dirname"])) @mkdir($pathinfo["dirname"], 0755, true);
if (is_file($v) && $override_data !== true) {
Console::info("Skipping zm_data file (not overwriting): " . $v);
continue;
}
Console::info("Copying zm_data file: " . $v);
if (copy($k, $v) !== true) {
throw new ModulePackException(zm_internal_errcode("E00068") . "Cannot copy file: " . $v);
}
}
}
private function copyLightCacheStore($override) {
$r = ZMConfig::get('global', 'light_cache') ?? [
'size' => 512, //最多允许储存的条数需要2的倍数
'max_strlen' => 32768, //单行字符串最大长度需要2的倍数
'hash_conflict_proportion' => 0.6, //Hash冲突率越大越好但是需要的内存更多
'persistence_path' => DataProvider::getDataFolder() . '_cache.json',
'auto_save_interval' => 900
];
LightCache::init($r);
foreach (($this->light_cache ?? []) as $k => $v) {
if (LightCache::isset($k) && $override !== true) continue;
LightCache::addPersistence($k);
LightCache::set($k, $v);
}
}
private function mergeGlobalConfig() {
if ($this->module["unpack"]["global-config-override"] !== false) {
$prompt = !is_string($this->module["unpack"]["global-config-override"]) ? "请根据模块提供者提供的要求进行修改 global.php 中对应的配置项" : $this->module["unpack"]["global-config-override"];
Console::warning("模块作者要求用户手动修改 global.php 配置文件中的项目:");
Console::warning("*" . $prompt);
echo Console::setColor("请输入修改模式y(使用vim修改)/e(自行使用其他编辑器修改后确认)/N(默认暂不修改)[y/e/N] ", "gold");
$r = strtolower(trim(fgets(STDIN)));
switch ($r) {
case "y":
system("vim " . escapeshellarg(DataProvider::getWorkingDir() . "/config/global.php") . " > `tty`");
Console::info("已使用 vim 修改!");
break;
case "e":
echo Console::setColor("请修改后文件点击回车即可继续 [Enter] ", "gold");
fgets(STDIN);
break;
case "n":
Console::info("暂不修改 global.php");
break;
}
}
}
private function copySource(bool $override_source) {
$origin_base = "phar://" . $this->module["phar-path"] . "/" . $this->module["module-root-path"];
$dir = DataProvider::scanDirFiles($origin_base, true, true);
$base = DataProvider::getSourceRootDir() . "/" . $this->module["module-root-path"];
foreach ($dir as $v) {
$info = pathinfo($base . "/" . $v);
if (!is_dir($info["dirname"])) {
@mkdir($info["dirname"], 0755, true);
}
if (is_file($base . "/" . $v) && $override_source !== true) {
Console::info("Skipping source file (not overwriting): " . $v);
continue;
}
Console::info("Releasing source file: " . $this->module["module-root-path"] . "/" . $v);
if (copy($origin_base . "/" . $v, $base . "/" . $v) !== true) {
throw new ModulePackException(zm_internal_errcode("E00068") . "Cannot copy file: " . $v);
}
}
}
}

View File

@ -24,7 +24,7 @@ class ZMAtomic
* 初始化atomic计数器
*/
public static function init() {
foreach (ZMConfig::get("global", "init_atomics") as $k => $v) {
foreach ((ZMConfig::get("global", "init_atomics") ?? []) as $k => $v) {
self::$atomics[$k] = new Atomic($v);
}
self::$atomics["stop_signal"] = new Atomic(0);

View File

@ -99,15 +99,16 @@ class ModuleManager
}
/**
* 解包模块 TODO
* 解包模块
* @param $module
* @param array $options
* @return array|false
*/
public static function unpackModule($module) {
public static function unpackModule($module, array $options = []) {
try {
$packer = new ModuleUnpacker($module);
return $packer->unpack();
} catch (ModulePackException $e) {
return $packer->unpack((bool)$options["override-light-cache"], (bool)$options["override-zm-data"], (bool)$options["override-source"]);
} catch (ZMException $e) {
Console::error($e->getMessage());
return false;
}

View File

@ -4,14 +4,18 @@ use Doctrine\Common\Annotations\AnnotationReader;
use ZM\Annotation\Swoole\OnSetup;
use ZM\Annotation\Swoole\SwooleHandler;
use ZM\ConsoleApplication;
use ZM\Exception\InitException;
use ZM\Utils\DataProvider;
use ZM\Utils\ZMUtil;
require_once ((!is_dir(__DIR__ . '/../../vendor')) ? getcwd() : (__DIR__ . "/../..")) . "/vendor/autoload.php";
try {
(new ConsoleApplication('zhamao'))->initEnv();
try {
(new ConsoleApplication('zhamao'))->initEnv();
} catch (InitException $e) {
}
$base_path = DataProvider::getSourceRootDir();
$scan_paths = [];
$composer = json_decode(file_get_contents($base_path . "/composer.json"), true);

View File

@ -17,7 +17,7 @@ class ModuleManagerTest extends TestCase
ZMConfig::setDirectory(DataProvider::getSourceRootDir() . '/config');
ZMConfig::setEnv($args["env"] ?? "");
if (ZMConfig::get("global") === false) {
die ("Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\n");
die (zm_internal_errcode("E00007") . "Global config load failed: " . ZMConfig::$last_error . "\nPlease init first!\nSee: https://github.com/zhamao-robot/zhamao-framework/issues/37\n");
}
//定义常量

View File

@ -11,6 +11,7 @@ use ReflectionException;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnStart;
use ZM\Console\Console;
use ZM\Event\EventTracer;
class AnnotationParserRegisterTest extends TestCase
{
@ -21,7 +22,7 @@ class AnnotationParserRegisterTest extends TestCase
define("WORKING_DIR", realpath(__DIR__ . "/../../../"));
if (!defined("LOAD_MODE"))
define("LOAD_MODE", 0);
Console::init(2);
Console::init(4);
$this->parser = new AnnotationParser();
$this->parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
try {
@ -70,4 +71,8 @@ class AnnotationParserRegisterTest extends TestCase
$mapping = $this->parser->getReqMapping();
$this->assertEquals("index", $mapping["method"]);
}
public function testTracer() {
}
}

View File

@ -8,11 +8,14 @@ use Doctrine\Common\Annotations\AnnotationException;
use Module\Example\Hello;
use PHPUnit\Framework\TestCase;
use ReflectionException;
use Swoole\Atomic;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\CQ\CQCommand;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Event\EventManager;
use ZM\Store\LightCacheInside;
use ZM\Store\ZMAtomic;
class EventDispatcherTest extends TestCase
{
@ -23,7 +26,9 @@ class EventDispatcherTest extends TestCase
define("WORKING_DIR", realpath(__DIR__ . "/../../../"));
if (!defined("LOAD_MODE"))
define("LOAD_MODE", 0);
Console::init(2);
Console::init(4);
ZMAtomic::$atomics["_event_id"] = new Atomic(0);
LightCacheInside::init();
$parser = new AnnotationParser();
$parser->addRegisterPath(WORKING_DIR . "/src/Module/", "Module");
try {
@ -44,5 +49,13 @@ class EventDispatcherTest extends TestCase
$r = ob_get_clean();
echo $r;
$this->assertStringContainsString("你好啊", $r);
$dispatcher = new EventDispatcher(CQCommand::class);
$dispatcher->setReturnFunction(function ($result) {
//echo $result . PHP_EOL;
});
//$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; });
$dispatcher->setRuleFunction(function ($v) { return $v->match == "qwe"; });
//$dispatcher->setRuleFunction(fn ($v) => $v->match == "qwe");
$dispatcher->dispatchEvents();
}
}

31
zhamao
View File

@ -1,22 +1,35 @@
#!/bin/sh
# shellcheck disable=SC2068
# shellcheck disable=SC2181
# author: crazywhalecc
# since: 2.5.0
if [ -f "$(pwd)/runtime/php" ]; then
executable="$(pwd)/runtime/php"
echo "* Framework started with built-in php."
else
which php >/dev/null 2>&1
which php >/dev/null 2>&1
if [ $? -eq 0 ]; then
executable=$(which php)
executable=$(which php)
else
echo 'Cannot find any PHP runtime, please use command `./install-runtime.sh` or install PHP manually!'
exit 1
echo '[ErrCode:E00014] Cannot find any PHP runtime, please use command "./install-runtime.sh" or install PHP manually!'
exit 1
fi
fi
result=$(echo "$1" | grep -E "module|build")
if [ "$result" != "" ]; then
executable="$executable -d phar.readonly=off"
fi
if [ -f "$(pwd)/src/entry.php" ]; then
$executable "$(pwd)/src/entry.php" $@
$executable "$(pwd)/src/entry.php" $@
elif [ -f "$(pwd)/vendor/zhamao/framework/src/entry.php" ]; then
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
$executable "$(pwd)/vendor/zhamao/framework/src/entry.php" $@
else
echo "Cannot find zhamao-framework entry file!"
exit 1
fi
echo "[ErrCode:E00015] Cannot find zhamao-framework entry file!"
exit 1
fi