diff --git a/docs/advanced/custom-start.md b/docs/advanced/custom-start.md index 3c3fa460..11640da2 100644 --- a/docs/advanced/custom-start.md +++ b/docs/advanced/custom-start.md @@ -28,7 +28,7 @@ Composer 库引入模式的文件夹结构如下,我们假设你的项目目 ├── plugins/ // 开发者编写和安装的插件目录 │ ├── example/ // 插件示例,这里以名称为 example 的插件为例 │ │ ├── main.php // 这个插件使用了单文件格式,main.php 为插件的核心代码 -│ │ └── zmplugin.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 +│ │ └── composer.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 ├── vendor/ // 你自身项目的依赖库,由 Composer 生成 │ ├── zhamao/ │ │ └── framework/ // 框架的本体源码根目录 @@ -57,7 +57,7 @@ Composer 库引入模式的文件夹结构如下,我们假设你的项目目 ├── plugins/ // 开发者编写和安装的插件目录 │ ├── example/ // 插件示例,这里以名称为 example 的插件为例 │ │ ├── main.php // 这个插件使用了单文件格式,main.php 为插件的核心代码 -│ │ └── zmplugin.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 +│ │ └── composer.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 ├── src/ // 框架的本体源码根目录 │ ├── ZM/ // 框架的核心代码 │ └── ...... // 框架的根目录其他文件 @@ -88,7 +88,7 @@ Phar 模式也分两个小种类,Composer 库引入模式和源码模式。如 ├── plugins/ // 开发者编写和安装的插件目录 │ ├── example/ // 插件示例,这里以名称为 example 的插件为例 │ │ ├── main.php // 这个插件使用了单文件格式,main.php 为插件的核心代码 -│ │ └── zmplugin.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 +│ │ └── composer.json // 这个文件描述了炸毛框架插件的元信息,包括插件名称、版本、作者等 ├── config/ // 配置文件目录 ├── zhamao.phar // 炸毛框架本体的 Phar ├── zm_data/ // 存放框架依赖的持久化数据目录,如日志、缓存等 diff --git a/docs/components/common/global-defines.md b/docs/components/common/global-defines.md index f8c006d2..dbfa11e2 100644 --- a/docs/components/common/global-defines.md +++ b/docs/components/common/global-defines.md @@ -397,3 +397,11 @@ public function testRoute(HttpRequestEvent $event) $socket = ws_socket(); $socket->send('hello world', $event->getFd()); // 客户端的连接 fd 编号可以通过 WebSocketOpenEvent 等事件获取 ``` + +### zm_create_app() + +创建一个炸毛框架的单文件应用(仅单文件,项目外非编写插件模式时可用),效果等同于 `new \ZM\ZMApplication()`。 + +### zm_create_plugin() + +创建一个炸毛单文件模式的插件对象,效果等同于 `new \ZM\Plugin\ZMPlugin()`。 diff --git a/docs/components/store/file-system.md b/docs/components/store/file-system.md index f14e63bb..51dc3f21 100644 --- a/docs/components/store/file-system.md +++ b/docs/components/store/file-system.md @@ -27,7 +27,7 @@ └── test-app/ ├── main.php ├── empty/ - └── zmplugin.json + └── composer.json ``` ```php @@ -36,7 +36,7 @@ $result = \ZM\Store\FileSystem::scanDirFiles('/home/ab/test/', true, false); 结果: [ "/home/ab/test/test-app/main.php", - "/home/ab/test/test-app/zmplugin.json" + "/home/ab/test/test-app/composer.json" ] */ $result2 = \ZM\Store\FileSystem::scanDirFiles('/home/ab/test/', false, true, true); diff --git a/docs/guide/structure.md b/docs/guide/structure.md index ed641d85..eb940af6 100644 --- a/docs/guide/structure.md +++ b/docs/guide/structure.md @@ -27,7 +27,7 @@ config/ plugins/ └── test-app/ ├── main.php # 你的插件源代码文件 - └── zmplugin.json # 插件元信息(如名称、版本等) + └── composer.json # 插件元信息(如名称、版本等) ``` ### zm_data 目录 diff --git a/docs/plugins/develop.md b/docs/plugins/develop.md index 64285057..762b0eb8 100644 --- a/docs/plugins/develop.md +++ b/docs/plugins/develop.md @@ -1,3 +1,126 @@ # 插件开发 -TODO。 +在框架环境准备就绪后,可以使用框架提供的命令、工具集进行插件的开发。 + +如果你还没有阅读 [快速上手-聊天机器人](/guide/get-started.md) 章节的内容,请先阅读。 + +## 创建插件 + +框架默认支持两种形式的插件,一种是 `file` 单文件模式,另一种是 `psr4` 多文件模式。一般情况下,写一个较为简单的功能插件,推荐使用单文件模式,便于管理。 +编写功能较多、需要分多个文件写逻辑的插件,推荐使用 `psr4` 多文件模式。 + +框架的插件命名方式基于 Composer,Composer 要求组件、插件的名称要有开发者名称和项目名称两部分组成。 +例如,我的开发者代号仓库是 `jackson`,插件名称是 `group-manager`,最终你的插件名称就是 `jackson/group-manager`。 + +```bash +# 使用终端问答向导创建插件,可根据终端的提示选择你要创建插件的名称、类型和命名空间等内容。 +./zhamao plugin:make --type=file +# 使用 file 类型的示例骨架创建插件,插件名称为 foobar/test-plugin +./zhamao plugin:make --type=file foobar/test-plugin +# 使用 psr4 类型的示例骨架创建插件,插件名称为 foobar/psr4-plugin,命名空间为 foobar +./zhamao plugin:make --type=psr4 --namespace=foobar foobar/psr4-plugin +# 设置插件作者名称、描述信息 +./zhamao plugin:make --author=tom --description="示例插件" +``` + +### 单文件模式 + +单文件模式的优势在于以下几点: + +- 代码较少的情况下,逻辑表达更清晰,可直接在单文件结构下编写机器人、HTTP 服务器的逻辑。 +- 可以添加额外的框架事件 `onPluginLoad`(框架在读取插件元信息后触发的事件)。 +- 可以添加额外的框架事件 `onPack`(插件在被打包时执行的回调函数)。 + +单文件模式下的插件目录结构如下图所示: + +```text +plugins/ +└── test-app/ + ├── main.php # 你的插件源代码文件 + └── composer.json # 插件元信息(如名称、版本等) +``` + +使用 file 模式创建的插件会在 `./plugins/` 目录下新建一个文件夹,文件夹名称一般为插件名称 `xxx/yyy` 的 `yyy`。如果遇到重名文件夹时,框架会提示重名。 + +当然,单文件模式也有劣势: + +- 插件依赖了其他外部 Composer 组件时,依赖管理不方便。 +- 插件逻辑较为复杂时,写到一个文件内会导致代码过长,不利于维护。 +- 一些注解的绑定、中间件,均无法在单文件模式中使用。 + +### 多文件模式 + +多文件模式适用于几乎任何情况,有以下优势: + +- 逻辑复杂时,使用 Class 类名和注解绑定的形式,有助于编写大型项目。 +- 逻辑简单但功能点繁多时,可以使用类成员的多个绑定注解的方法区分功能逻辑,避免单文件的混乱。 +- 可以使用注解的全部特性,例如中间件绑定、依赖注入等。 + +多文件模式下的插件目录结构如下图所示: + +```text +plugins/ +└── psr4-plugin/ + ├── composer.json # 插件元信息(如名称、版本等) + ├── vendor/ # Composer 生成的插件依赖库目录和自动加载文件等 + └── src/ # 你的插件源代码文件目录,符合 PSR-4 格式的 + ├── Psr4Plugin.php # 使用创建插件命令生成的初始插件逻辑 + └── ... # 如果你的插件想要分成多个 PHP 文件,那么这里可以有多个 +``` + +### 混合模式 + +混合模式为单文件模式和多文件模式的结合,框架不提供创建混合模式插件脚手架的命令,但你可以自行混合。 + +混合模式其实就是多文件模式,只不过同时也可以引入一个单文件插件,弥补多文件模式没办法绑定 `onPack` 和 `onPluginLoad` 事件的问题。 + +混合模式在多文件模式的基础上多了一个 `main.php` 的单文件: + +```text +plugins/ +└── psr4-plugin/ + ├── composer.json # 插件元信息(如名称、版本等) + ├── vendor/ # Composer 生成的插件依赖库目录和自动加载文件等 + ├── main.php # 插件逻辑(单文件部分) + └── src/ # 你的插件源代码文件目录(多文件部分) + ├── Psr4Plugin.php # 使用创建插件命令生成的初始插件逻辑 + └── ... # 如果你的插件想要分成多个 PHP 文件,那么这里可以有多个 +``` + +在通过以上三种方式创建插件后,你可以通过 IDE、记事本等形式开始编写你的插件逻辑啦! + +## 编写插件(WIP) + +> 此处文档正在努力编写中! + +插件的编写内容当然取决于异想天开的你,这里用最基本和最典型的例子来说明如何从零编写一个自己的插件。 + +### 机器人命令问答 + +在使用框架的上方创建插件命令创建了一个插件目录和骨架文件后,打开文件可以看到默认生成了一个 BotCommand 机器人聊天命令事件,返回的内容为一句简单的话。 +你可以在创建插件后直接使用此命令,在对接机器人实现端(OneBot 12 实现)后,在与机器人对话的窗口或 App 内发送指令,即可测试机器人插件是否正常运行。 + +我们这里假设通过 psr4 模式创建了插件 `foobar/demo2`,初始化时预置的机器人聊天命令代码可能是下面这样: + +```php +#[BotCommand(match: '测试demo2')] +public function firstBotCommand(BotContext $ctx): void +{ + $ctx->reply('这是foobar/demo2插件的第一个命令!'); +} +``` + + + +在编写插件逻辑时,你可以自由使用框架内的各个组件和注解,这里不再过多描述。 + +## 插件打包 + +在写了在写了。 + +## 插件发布 + +在写了! diff --git a/docs/plugins/management.md b/docs/plugins/management.md index 1a714149..0b95d5ff 100644 --- a/docs/plugins/management.md +++ b/docs/plugins/management.md @@ -6,7 +6,7 @@ ## GitHub 插件下载 -你可以到 [插件市场](/plugin-market/) 下载并安装你心仪的插件,这些插件大部分为 OneBot 机器人适用的聊天机器人功能类插件。 +你可以到 [插件市场](/plugins/market.md) 下载并安装你心仪的插件,这些插件大部分为 OneBot 机器人适用的聊天机器人功能类插件。 在安装框架后,可以使用 `./zhamao plugin:install https://github.com/xxx/yyy.git` 样式的链接安装 GitHub 发布的插件。 diff --git a/src/Globals/global_functions.php b/src/Globals/global_functions.php index ad4bf64a..88cfdfde 100644 --- a/src/Globals/global_functions.php +++ b/src/Globals/global_functions.php @@ -18,12 +18,14 @@ use ZM\Framework; use ZM\Logger\ConsoleLogger; use ZM\Middleware\MiddlewareHandler; use ZM\Plugin\OneBot\BotMap; +use ZM\Plugin\ZMPlugin; use ZM\Schedule\Timer; use ZM\Store\Database\DBException; use ZM\Store\Database\DBQueryBuilder; use ZM\Store\Database\DBWrapper; use ZM\Store\KV\KVInterface; use ZM\Store\KV\Redis\RedisWrapper; +use ZM\ZMApplication; // 防止重复引用引发报错 if (function_exists('zm_internal_errcode')) { @@ -345,3 +347,19 @@ function ws_socket(int $flag = 1): WSServerSocketBase } return $a; } + +/** + * 创建炸毛框架应用 + */ +function zm_create_app(): ZMApplication +{ + return new ZMApplication(); +} + +/** + * 创建炸毛框架的插件对象 + */ +function zm_create_plugin(): ZMPlugin +{ + return new ZMPlugin(); +} diff --git a/src/ZM/Framework.php b/src/ZM/Framework.php index 8568f24f..79c91b88 100644 --- a/src/ZM/Framework.php +++ b/src/ZM/Framework.php @@ -50,7 +50,7 @@ class Framework public const VERSION_ID = 712; /** @var string 版本名称 */ - public const VERSION = '3.1.7'; + public const VERSION = '3.1.8'; /** * @var RuntimePreferences 运行时偏好(环境信息&参数) diff --git a/src/ZM/Plugin/PluginManager.php b/src/ZM/Plugin/PluginManager.php index a3689e06..42b2309d 100644 --- a/src/ZM/Plugin/PluginManager.php +++ b/src/ZM/Plugin/PluginManager.php @@ -53,7 +53,7 @@ class PluginManager continue; } - // 先看有没有 zmplugin.json,没有则不是正常的插件,发个 notice 然后跳过 + // 先看有没有 composer.json,没有则不是正常的插件,发个 notice 然后跳过 $meta_file = $item . '/composer.json'; if (!is_file($meta_file)) { logger()->notice('插件目录 {dir} 没有插件元信息(composer.json),跳过扫描。', ['dir' => $item]); @@ -382,7 +382,7 @@ class PluginManager ++$file_added; } // 找有没有 main,没有 main 就不添加 stub - $main = (json_decode(file_get_contents($dir . '/zmplugin.json'), true)['main'] ?? 'main.php'); + $main = (json_decode(file_get_contents($dir . '/composer.json'), true)['extra']['zm-plugin-main'] ?? 'main.php'); if (file_exists(zm_dir($dir . '/' . $main)) && $phar->offsetExists($main)) { $command_context?->info('设置插件默认入口文件 ' . $main); $phar->setStub($phar->setDefaultStub($main)); diff --git a/src/ZM/Plugin/PluginMeta.php b/src/ZM/Plugin/PluginMeta.php index ce88e95c..c0c11397 100644 --- a/src/ZM/Plugin/PluginMeta.php +++ b/src/ZM/Plugin/PluginMeta.php @@ -7,7 +7,7 @@ namespace ZM\Plugin; use ZM\Exception\PluginException; /** - * 插件的元信息对象,对应 zmplugin.json + * 插件的元信息对象 */ class PluginMeta implements \JsonSerializable {