一. 插件目录结构与文件规范
每个插件必须放在 plugins/
目录下,目录名即插件名,建议驼峰或下划线风格(如 Demo、UserCenter)。
标准目录结构:
plugins/
└── Demo/
├── plugin.json # 插件元信息(必需,主配置文件)
├── menu.php # 菜单配置(可选,需 features.register_menu=true)
├── routes.php # 路由配置(可选,需 features.register_route=true)
├── tasks.php # 定时任务配置(可选,需 features.register_task=true)
├── DemoPlugins.php # 插件主类(必需,类名=目录名+Plugins)
├── hooks/ # 插件钩子目录(可选,放置所有 hook 相关文件)
├── assets/ # 静态资源目录(可选,放置样式、JS、图片等)
│ ├── css/
│ ├── js/
│ └── images/
├── lib/ # 插件自定义类库(可选,放置本插件的辅助类、第三方库等)
├── views/ # 插件模板/视图目录(可选,放置本插件的模板文件)
└── lang/ # 插件多语言目录(可选,放置本插件的语言包)
系统只会自动读取和处理如下目录及文件:
plugin.json
(插件元信息,必需)menu.php
(菜单配置,需 features.register_menu=true)routes.php
(路由配置,需 features.register_route=true)tasks.php
(定时任务配置,需 features.register_task=true)DemoPlugins.php
(插件主类,必需)hooks/
(钩子目录,系统会自动扫描并注册钩子)assets/
(静态资源目录,前端可通过统一路径访问)lib/
(插件自定义类库,需在插件主类中手动引入)views/
(模板目录,系统模板渲染时会自动查找)lang/
(多语言目录,系统多语言加载时会自动查找)
其他目录或文件,系统不会自动读取。
二. plugin.json 配置说明
标准格式:
{
"name": "Demo",
"title": "演示插件",
"author": "作者名",
"version": "1.0.0",
"url": "https://your-site.com",
"description": "插件描述",
"namespace": "Plugins\Demo",
"core_class": "DemoPlugins",
"features": {
"register_route": true,
"register_menu": true,
"register_task": false
},
"hooks": [
"sendMail",
"onUserRegistered"
]
}
features
字段用于控制插件的各项注册功能。hooks
字段用于声明本插件支持的所有钩子方法名,主程序会根据此字段自动识别和调度插件的 hook。
字段说明表:
字段 | 类型 | 必须 | 说明 |
---|---|---|---|
name | string | 是 | 插件目录名,唯一标识 |
title | string | 是 | 插件名称 |
author | string | 否 | 作者名 |
version | string | 是 | 版本号 |
url | string | 否 | 插件主页/文档地址 |
description | string | 否 | 插件描述 |
namespace | string | 是 | 插件主类命名空间 |
core_class | string | 是 | 插件主类名 |
features | object | 否 | 注册功能控制,见下表 |
hooks | array | 否 | 本插件声明支持的所有 hook 方法名 |
features 字段说明:
字段 | 类型 | 必须 | 说明 |
---|---|---|---|
register_route | bool | 否 | 是否注册插件自定义路由 |
register_menu | bool | 否 | 是否注册插件自定义菜单 |
register_task | bool | 否 | 是否注册插件自定义定时任务 |
三. hooks 机制与声明
hooks
字段为数组,声明本插件支持的所有 hook 方法名。- 主程序会根据
plugin.json
的hooks
字段,自动识别哪些插件支持哪些 hook。 - 只有在
hooks
数组中声明的方法,主程序才会调度该插件。
hooks 字段示例:
"hooks": [
"sendMail",
"onUserRegistered",
"onUserLogin"
]
常见 hook 用法举例:
hook 名称 | 说明 | 典型参数示例 |
---|---|---|
sendMail | 发送邮件 | ($to, $subject, $body) |
onUserRegistered | 用户注册后 | ($userId, $userInfo) |
onUserLogin | 用户登录后 | ($userId, $loginTime) |
四. 菜单、路由、任务、钩子、资源等文件规范
4.1 menu.php(菜单配置)
- 返回菜单数组,支持父菜单+子菜单。
- 注册时,只有
features.register_menu
为true
才会生效。 - 系统会自动写入
db_id
字段到每个菜单项,便于精准删除。
示例 menu.php:
<?php
return [
[
"title" => "演示管理",
"icon" => "icon-demo",
"type" => 1,
"open_type" => 1,
"href" => "/admin/demo",
"sort" => 100,
"permission_ids" => "",
"status" => 1
],
[
"title" => "子菜单A",
"icon" => "icon-a",
"type" => 2,
"open_type" => 1,
"href" => "/admin/demo/a",
"sort" => 1,
"permission_ids" => "",
"status" => 1
]
];
4.2 routes.php(路由配置)
- 返回路由数组。
- 注册时,只有
features.register_route
为true
才会生效。
示例 routes.php:
<?php
return [
[
"method" => ["GET"],
"uri" => "/admin/demo",
"handler" => "DemoPlugins@apiDemo",
"namespace" => "Plugins\Demo",
"middleware" => ["AdminJwtCookie", "AdminJwtAuth"]
]
];
handler
格式为类名@方法名
。namespace
必须与主类命名空间一致。
4.3 tasks.php(定时任务配置)
- 返回任务数组。
- 注册时,只有
features.register_task
为true
才会生效。
示例 tasks.php:
<?php
return [
'demo_task' => [
'class' => '\Plugins\Demo\Task\DemoTask',
'method' => 'run',
'interval' => 60,
'priority' => 'normal',
'timeout' => 120,
'retry' => 2,
'retry_delay' => 30,
'beizhu' => 'Demo插件的定时任务'
]
];
任务配置字段说明:
字段 | 类型 | 必须 | 说明 |
---|---|---|---|
class | string | 是 | 任务执行类的完全限定名 |
method | string | 是 | 任务执行方法名 |
interval | int | 是 | 任务执行间隔(秒) |
priority | string | 否 | 任务优先级 high/normal/low |
timeout | int | 否 | 超时时间(秒),默认300 |
retry | int | 否 | 最大重试次数,默认3 |
retry_delay | int | 否 | 重试延迟(秒),默认60 |
beizhu | string | 否 | 备注/描述 |
4.4 hooks/(钩子目录)
- 所有钩子相关文件建议以
*.hook.php
命名,内容为标准 PHP 类或函数。 - 系统会自动扫描并注册钩子。
- 推荐:所有 hook 方法直接在插件主类实现,并在
plugin.json
的hooks
字段声明。
4.5 assets/(静态资源目录)
- 推荐分为
css/
、js/
、images/
,前端可通过统一路径访问。
4.6 lib/(插件自定义类库)
- 放置本插件的辅助类、第三方库等,需在主类中手动引入。
4.7 views/(模板/视图目录)
- 放置本插件的模板文件,推荐与主系统模板风格一致。
4.8 lang/(多语言目录)
- 多语言文件,推荐以
zh-cn.php
、en-us.php
等命名,返回数组。
五. 插件主类规范
- 主类文件名:DemoPlugins.php(目录名+Plugins)
- 命名空间:Plugins\Demo
- 类名:DemoPlugins
- 建议实现
onInit
(注册时自动调用)、onUninstall
(卸载时自动调用)、onUpdate
(更新时自动调用)、onDisable
(禁用时自动调用)等生命周期方法。
示例 DemoPlugins.php:
<?php
namespace Plugins\Demo;
class DemoPlugins
{
/**
* 生命周期:插件初始化(每次系统启动时调用)
*/
public function onInit() {}
/**
* 生命周期:插件禁用
*/
public function onDisable() {}
/**
* 生命周期:插件卸载
*/
public function onUninstall() {}
/**
* 生命周期:插件更新
*/
public function onUpdate(){}
// 路由 handler 示例
public function apiDemo() {}
// 示例 hook 方法(需在 plugin.json 的 hooks 字段声明)
public function sendMail($to, $subject, $body) {
// 发送邮件逻辑
}
}
六. 插件启用/停用/卸载流程
- 启用/注册插件
调用syncSinglePlugin('Demo')
或syncAllPlugins()
。插件信息写入数据库,菜单/路由/任务根据 features 注册。自动写入 db_id 到 plugin.json 和 menu.php。 - 停用插件
调用disableSinglePlugin('Demo')
或disableAllPlugins()
。仅将 plugin 表中对应插件的 status 字段设为 0,其他数据不变。 - 卸载插件
调用uninstallSinglePlugin('Demo')
或uninstallAllPlugins()
。精准删除插件表、菜单、路由、任务等所有相关内容(优先用 db_id)。删除后,插件相关菜单、路由、任务等全部失效。
注意:
- 所有操作遇到异常会直接抛出,便于排查。
- 插件注册、菜单、路由、任务等,只有 features 对应项为 true 时才会执行。
七. 插件接口调用与返回值
常用接口:
$pluginManager = new PluginManager();
// 一键刷新本地插件状态
$allStatus = $pluginManager->refreshAllPlugins();
// $allStatus 是所有插件的本地状态数组
// 启用单个插件
$pluginManager->enablePlugin('Demo');
// 停用单个插件
$pluginManager->disablePlugin('Demo');
// 卸载单个插件
$pluginManager->uninstallPlugin('Demo');
- 所有方法执行成功返回 true,如有异常会直接抛出(需用 try-catch 捕获)。
- 系统内置通常不需要在开发过程中去调度
八. Hook 调用与插件调度
- 主程序可通过
$pluginManager->callHook('hookName', $args)
自动调度所有已启用且声明了该 hook 的插件(按优先级只调第一个)。 - 可通过
$pluginManager->getPluginsByHook('hookName')
获取所有声明了该 hook 的插件列表。
示例:
$ret = $pluginManager->callHook('sendMail', [$to, $subject, $body]);
if ($ret && $ret['success']) {
// 成功
} else {
// 没有插件处理,或插件报错
}
九. 常见问题与注意事项
- features 没有某项时,默认视为 false,不会注册。
- 插件注册、菜单、路由、任务等,只有 features 对应项为 true 时才会执行。
- 插件注册后,plugin.json 会自动写入 db_id 字段,菜单注册后 menu.php 会写入每个菜单的 db_id 字段。
- 卸载时,优先用 db_id 精准删除,避免误删。
- 所有操作遇到异常会直接抛出,建议用 try-catch 捕获。
- 路由注册时如有冲突会自动跳过并记录。
- 插件主类建议实现 onInit(注册时自动调用)和 onUninstall(卸载时自动调用)方法。
- 插件升级建议先停用再升级,升级后重新启用。
十. 开发建议与最佳实践
- 强烈建议每个插件都写好 features 字段,明确声明支持哪些注册功能。
- 插件主类建议统一命名为 插件名Plugins,放在插件目录下。
- 路由、菜单、任务等配置文件建议返回标准数组,便于自动注册。
- 插件开发完成后,建议在测试环境多次启用/停用/卸载,确保无残留。
- 如需扩展更多 features,可在 features 字段中自定义,主程序可按需支持。
- 如需支持 hook,务必在 plugin.json 的 hooks 字段声明所有支持的 hook 方法名,并在主类实现。
十一. 完整插件示例
目录结构:
plugins/
└── Demo/
├── plugin.json
├── menu.php
├── routes.php
├── tasks.php
├── DemoPlugins.php
├── hooks/
│ └── onUserLogin.hook.php
├── assets/
│ ├── css/
│ │ └── demo.css
│ ├── js/
│ │ └── demo.js
│ └── images/
│ └── logo.png
├── lib/
│ └── DemoHelper.php
├── views/
│ └── index.html
└── lang/
├── zh-cn.php
└── en-us.php
plugin.json
{
"name": "Demo",
"title": "演示插件",
"author": "张三",
"version": "1.0.0",
"url": "https://demo.com",
"description": "这是一个演示插件。",
"namespace": "Plugins\Demo",
"core_class": "DemoPlugins",
"features": {
"register_route": true,
"register_menu": true,
"register_task": true
},
"hooks": [
"sendMail",
"onUserRegistered"
]
}
DemoPlugins.php
<?php
namespace Plugins\Demo;
class DemoPlugins
{
public function onInit()
{
// 插件初始化逻辑
}
public function onUninstall()
{
// 插件卸载清理逻辑
}
public function apiDemo()
{
// 路由 handler 示例
}
// 示例 hook 方法
public function sendMail($to, $subject, $body) {
// 发送邮件逻辑
}
}
如需更多示例或有特殊需求,欢迎在本帖回复交流!
请登录后查看回复内容