新版服务器插件快速开发指南(注册式钩子)
面向插件开发者,帮你快速上手新版注册式钩子插件开发。
参考脚手架:
public/plugins/host/example/(完整示例,可直接复制使用)
快速开始(5 分钟完成)
1. 复制脚手架
cd public/plugins/host/
cp -r example myplugin # 复制示例目录
mv myplugin/example.php myplugin/myplugin.php # 重命名入口文件2. 改 3 个地方
| 文件 | 修改内容 |
|---|---|
myplugin.php | 第 109 行 'example' → 'myplugin' |
myplugin.php | new ExamplePlugin() → new MyPlugin() |
src/ExamplePlugin.php | 类名改为 MyPlugin,修改 DisplayName |
3. 实现业务逻辑
在 src/MyPlugin.php 中填写你的 API 调用代码。
4. 后台绑定
系统后台「服务器」→「添加服务器」→ 选择你的插件即可使用。
目录结构
public/plugins/host/myplugin/
├── myplugin.php # 入口文件:只做钩子注册
├── src/
│ └── MyPlugin.php # 业务类:实现所有钩子方法
├── templates/ # 前台模板
│ ├── overview.html
│ └── manage.html
└── assets/ # 静态资源
├── css/style.css
└── js/main.js命名约束:插件标识(文件夹名 + 入口文件名)只允许小写字母、数字、下划线,以字母开头,最长 32 字符。
入口文件(只注册,不写业务)
myplugin.php 只做三件事:
<?php
// 1. 加载业务类
require_once __DIR__ . '/src/MyPlugin.php';
// 2. 注册能力钩子
use app\common\hook\Registry;
$plugin = new MyPlugin();
Registry::onMany('host', 'myplugin', [
'metaData' => [$plugin, 'onMetaData'],
'testLink' => [$plugin, 'onTestLink'],
'create' => [$plugin, 'onCreate'],
'terminate' => [$plugin, 'onTerminate'],
// ... 其他钩子
]);
// 3. 注册事件监听(可选)
use think\Hook;
use app\common\hook\EventRegistry;
Hook::add(EventRegistry::HOST_AFTER_CREATE, function($params) {
// 开通后做点啥
});⚠️ 入口文件被加载时立即执行,不要放业务逻辑(不要连 DB、调 API、echo 内容)。
必须实现的钩子
插件元数据
public function onMetaData(): array
{
return [
'DisplayName' => '我的插件',
'APIVersion' => '1.0.0',
'HelpDoc' => 'https://...',
];
}连接测试
public function onTestLink(array $params): array
{
$server = $params['server'];
return ['code' => 1, 'msg' => '连接成功'];
}核心生命周期
| 钩子 | 方法 | 说明 |
|---|---|---|
create | onCreate | 开通账号(支付后自动调用) |
terminate | onTerminate | 删除/终止 |
suspend | onSuspend | 暂停 |
unsuspend | onUnsuspend | 恢复 |
renew | onRenew | 续费 |
所有方法接收 array $params,返回 ['code' => 1/0, 'msg' => '...']。
$params 包含:
$params = [
'host' => [...], // 主机信息(id, domain, username, password, status...)
'product' => [...], // 商品信息(id, product_name, server_id...)
'server' => [...], // 服务器信息(host, ip, port, user, password...)
'server_meta' => [...], // 服务器自定义配置
'configoptions' => [...], // 商品选配字段
'host_meta' => [...], // 主机元数据
'addons' => [...], // 订单选配数据
];可选能力钩子
前台交互
| 钩子 | 方法 | 用途 |
|---|---|---|
clientArea | onClientArea | 定义前台详情页的标签页 |
clientAreaOutput | onClientAreaOutput | 输出标签页内容 |
clientButton | onClientButton | 定义前台操作按钮 |
clientButtonAction | onClientButtonAction | 执行按钮动作 |
handleAction | onHandleAction | 前台表单提交统一入口 |
标签页示例:
public function onClientArea(array $params): array
{
return [
'overview' => ['name' => '概览', 'icon' => 'fa-dashboard'],
'manage' => ['name' => '管理', 'icon' => 'fa-cog'],
];
}
public function onClientAreaOutput(array $params, string $key): array
{
if ($key === 'overview') {
return [
'template' => 'templates/overview.html',
'vars' => ['hostId' => $params['host']['id']],
];
}
}后台交互
| 钩子 | 方法 | 用途 |
|---|---|---|
adminButton | onAdminButton | 后台操作按钮 |
adminButtonAction | onAdminButtonAction | 执行后台按钮动作 |
productButton | onProductButton | 产品管理页按钮 |
productButtonAction | onProductButtonAction | 执行产品按钮动作 |
VPS 能力
| 钩子 | 方法 | 适用 |
|---|---|---|
powerOn | onPowerOn | VPS |
powerOff | onPowerOff | VPS |
reboot | onReboot | VPS |
vnc | onVnc | VPS |
reinstall | onReinstall | VPS |
resetPassword | onResetPassword | VPS |
chart | onChart | VPS |
其他
| 钩子 | 方法 | 用途 |
|---|---|---|
status | onStatus | 状态查询 |
sync | onSync | 同步数据 |
changePackage | onChangePackage | 升降级 |
cron | onCron | 主机定时任务 |
productCron | onProductCron | 商品定时任务 |
配置项
| 钩子 | 方法 | 显示位置 |
|---|---|---|
adminConfigOptions | onAdminConfigOptions | 后台服务器编辑页 |
configOptions | onConfigOptions | 后台商品编辑页 |
hostConfigOptions | onHostConfigOptions | 自动插入产品元数据 |
配置项字段格式:
[
'name' => 'field_name',
'title' => '显示名称',
'type' => 'input', // input/select/checkbox/textarea/number/password/switch
'value' => '默认值',
'prompt' => '提示说明',
'options' => ['a' => '选项A', 'b' => '选项B'], // select 用
]生命周期事件监听
能力钩子是"系统叫你做事",事件钩子是"系统通知你事情发生了"。
| 维度 | 能力钩子 | 事件钩子 |
|---|---|---|
| 关系 | 一对一 | 一对多广播 |
| 返回值 | 必须返回,影响主流程 | 不依赖返回值 |
| 用途 | 实现核心能力 | 做附属操作(发邮件、记日志、同步) |
可用事件:
use think\Hook;
use app\common\hook\EventRegistry;
// Host 生命周期
Hook::add(EventRegistry::HOST_BEFORE_CREATE, ...); // 开通前
Hook::add(EventRegistry::HOST_AFTER_CREATE, ...); // 开通后
Hook::add(EventRegistry::HOST_BEFORE_TERMINATE, ...); // 删除前
Hook::add(EventRegistry::HOST_AFTER_TERMINATE, ...); // 删除后
Hook::add(EventRegistry::HOST_BEFORE_SUSPEND, ...); // 暂停前
Hook::add(EventRegistry::HOST_AFTER_SUSPEND, ...); // 暂停后
Hook::add(EventRegistry::HOST_BEFORE_UNSUSPEND, ...); // 恢复前
Hook::add(EventRegistry::HOST_AFTER_UNSUSPEND, ...); // 恢复后
Hook::add(EventRegistry::HOST_BEFORE_RENEW, ...); // 续费前
Hook::add(EventRegistry::HOST_AFTER_RENEW, ...); // 续费后
Hook::add(EventRegistry::HOST_STATUS_CHANGED, ...); // 状态变更
// 订单
Hook::add(EventRegistry::ORDER_BEFORE_PAY, ...);
Hook::add(EventRegistry::ORDER_AFTER_PAY, ...);
Hook::add(EventRegistry::ORDER_BEFORE_CANCEL, ...);
Hook::add(EventRegistry::ORDER_AFTER_CANCEL, ...);
Hook::add(EventRegistry::ORDER_BEFORE_REFUND, ...);
Hook::add(EventRegistry::ORDER_AFTER_REFUND, ...);
// 用户
Hook::add(EventRegistry::USER_AFTER_REGISTER, ...);
Hook::add(EventRegistry::USER_AFTER_LOGIN, ...);
Hook::add(EventRegistry::USER_AFTER_LOGOUT, ...);
// 工单
Hook::add(EventRegistry::TICKET_AFTER_CREATE, ...);
Hook::add(EventRegistry::TICKET_AFTER_REPLY, ...);
Hook::add(EventRegistry::TICKET_AFTER_CLOSE, ...);监听示例:
Hook::add(EventRegistry::HOST_AFTER_CREATE, function($params) {
$host = $params['host'];
$result = $params['result'];
if ($result['code'] == 1) {
// 发送欢迎邮件
\app\common\service\BillNoticeService::sendGroup([
'user' => ['to' => $host['user_id'], 'tpl' => 'host_open_success_user'],
]);
}
});返回值规范
// 成功
['code' => 1, 'msg' => '操作成功']
// 失败
['code' => 0, 'msg' => '失败原因']
// 成功 + 数据
['code' => 1, 'msg' => '查询成功', 'data' => [...]]特殊钩子的返回值:
| 钩子 | 返回值格式 |
|---|---|
metaData | ['DisplayName'=>'名称', 'APIVersion'=>'1.0', 'HelpDoc'=>'url'] |
clientArea | ['tabKey' => ['name'=>'标签名', 'icon'=>'fa-xxx']] |
clientAreaOutput | ['template'=>'路径', 'vars'=>[]] 或 ['html'=>'...'] |
clientButton | ['buttonKey' => '按钮名称'] |
vnc | ['code'=>1, 'data'=>['url'=>'vnc链接']] |
status | ['code'=>1, 'data'=>['status'=>'Active', 'ip'=>'...']] |
chart | ['code'=>1, 'data'=>['labels'=>[], 'values'=>[]]] |
最佳实践
入口文件只注册,不写业务
❌ 错误:
// myplugin.php 中直接调 API
$client = new GuzzleHttp\Client();
$client->post(...);✅ 正确:
// myplugin.php 只注册
Registry::onMany('host', 'myplugin', ['create' => [$plugin, 'onCreate']]);
// 业务逻辑全在 src/MyPlugin.php 的方法里异常自己捕获
public function onCreate(array $params): array
{
try {
return ['code' => 1, 'msg' => '成功'];
} catch (\Throwable $e) {
return ['code' => 0, 'msg' => '失败:' . $e->getMessage()];
}
}不实现的钩子不要注册
只注册你实现了的钩子。Registry::onMany() 中不存在的方法不会被调用。
GuzzleHttp 做网络请求
$client = new \GuzzleHttp\Client(['timeout' => 15]);
$response = $client->post('https://api.example.com/create', ['json' => $data]);调试技巧
// 查看插件注册了哪些钩子
use app\common\hook\host\HostPluginHook;
$hooks = HostPluginHook::hooks('myplugin');
dump($hooks);
// 手动触发钩子测试
$params = (new \app\common\controller\Plugins)->build_host_plugin_param(57);
$result = HostPluginHook::create('myplugin', $params);
dump($result);系统日志位置:runtime/log/,搜索 Hook 关键词。
常见问题
Q:新插件和旧插件能共存吗?
可以。A 产品用新插件,B 产品用旧插件,互不影响。
Q:插件方法里能用 ThinkPHP 的功能吗?
可以。Db::name()、model()、cache() 等都能正常使用。
Q:插件超时怎么办?
设置合理的超时时间(5-30s),内部用 try/catch 捕获。
Q:只注册了一部分钩子,系统会报错吗?
不会。没注册的钩子系统会忽略,只是对应功能不可用。
参考
- 完整示例插件:
public/plugins/host/example/ - 旧版开发手册请参考本站「服务器插件开发手册」
chat_bubble_outline 评论