本知识库目录
异次元发卡 3.5.6 插件开发与部署指南
适用版本:异次元发卡 3.5.6(提交 60b26c56f23d02da94715b1fc92899f02c86f35f)。
本文只描述仓库中实际存在的插件机制。Demo 只能说明目录和注解写法,不能作为“自制插件可以直接启用”的完整示例。
1. 先看结论
1.1 插件如何被检测
程序通过 Kernel\Util\Plugin::getPlugins() 扫描:
只要插件目录下存在可加载的 Config/Info.php,后台接口 /admin/api/plugin/getPlugins 就能读到插件元数据。检测插件不要求它出现在应用商店缓存中。
1.2 为什么原版不能正常使用自制插件
原版 kernel/Plugin/Hook.php 只在应用商店授权组件成功加载时读取:
这个文件由商店插件授权链生成并加密。自制插件虽然能被 getPlugins() 扫描到,但它的 #[Hook] 不会因此自动注册;后台启停也会走商店插件的 _plugin_start() / _plugin_stop()。
1.3 当前仓库的本地插件改动
本仓库增加了两个有限、独立的本地插件路径:
| 文件 | 作用 |
|---|---|
kernel/Plugin/Hook.php | 继续加载商店 Hook;另外扫描不在 runtime/plugin/store.cache 中、且 STATUS=1 的本地插件,并注册其 #[Hook]。 |
app/Controller/Admin/Api/Plugin.php | 对不在商店缓存中的插件直接写入 Config/Config.php,执行启停状态 Hook,并记录管理日志。 |
商店插件仍使用原来的加密缓存和授权流程。仓库根目录的 kernel/Plugin.php 是应用商店授权组件,不应修改、解密或伪造;本文不依赖修改它。
2. 插件目录
最小可用插件:
2.1 命名规则
目录名就是插件 ID,例如
OrderLog、TelegramNotification。目录名只能使用字母、数字和下划线,且必须以字母开头更稳妥。
命名空间必须与目录名大小写一致:
App\Plugin\OrderLog\...。Hook 文件名必须与类名一致,例如
Hook/Main.php对应App\Plugin\OrderLog\Hook\Main。Linux 区分大小写。不要照抄旧
CardAPI示例中的大小写不一致写法。插件 ID 不要与
runtime/plugin/store.cache中的商店插件重名;命中该缓存后会按商店插件处理,不会走本地路径。
3. 必需配置文件
3.1 Config/Info.php
Plugin::NAME 是后台显示名称,目录名仍是插件 ID。Info.php 返回值不是对象,必须是数组。
3.2 Config/Config.php
配置会被后台 setConfig 重新写成 PHP 数组。不要手工把 Token、数据库密码等秘密提交到 Git。
3.3 Config/Submit.php
这是后台“配置”弹窗的字段定义。每个字段至少有 title、name、type;name 必须与 Config.php 的键一致。
常用类型:
type | 用途 | 常用键 |
|---|---|---|
input | 单行文本 | placeholder、required、default |
password | 密钥输入 | 同 input |
number | 数字输入 | required、default |
textarea | 多行文本 | height、default |
select | 下拉选择 | dict: [[id, name], ...] |
switch | 开关,提交通常是 0/1 | placeholder、default |
checkbox | 多选 | dict、default |
json | JSON 编辑框 | default |
explain | 只显示说明,不保存业务值 | placeholder |
dict 可以直接写数组,也可以写数据字典字符串,例如 API 通知插件使用的 commodity,id,name。具体字典由当前版本的后台组件解析,插件不应假设未知字典一定存在。
示例:
Kernel\Util\Plugin::getPlugin() 会把 Config.php 中已有的值填回 PLUGIN_SUBMIT[*].default;不存在的值才使用 Submit.php 给出的默认值。
4. Hook 机制
4.1 基本写法
要求:
Hook 方法必须是
public。#[Hook]必须使用命名参数point:,值来自App\Consts\Hook。方法参数顺序必须和挂载点实际传参一致;类型声明错误会让订单流程报错。
Hook 方法应尽快返回。同步网络请求会增加付款请求耗时,失败应捕获异常,不能阻止支付和发货。
本地插件只有
STATUS=1才注册;停用后下一次请求不会再调用它。
4.2 Hook 返回值
底层 Kernel\Util\Plugin::hook() 的行为:
string:追加到当前结果,适合输出 HTML/JS。array:追加到结果数组,适合表格列、导航等结构化扩展。Kernel\Plugin\Entity\Stock:立即返回库存对象。null/无返回值:不改变结果。
页面挂载点常见做法是直接 echo,数据型挂载点则应按注释要求修改引用参数或返回数组。
4.3 3.5.6 挂载点速查
完整常量以 app/Consts/Hook.php 为准。常用挂载点如下:
| 常量 | 触发位置 / 参数 |
|---|---|
ADMIN_VIEW_MENU | 后台左侧菜单,通常直接输出 HTML |
ADMIN_VIEW_HEADER / ADMIN_VIEW_FOOTER | 后台 CSS / JS |
ADMIN_VIEW_NAV | 后台应用商店旁导航 |
ADMIN_VIEW_USER_TABLE ADMIN_VIEW_USER_TOOLBAR ADMIN_VIEW_USER_FOOTER | 后台会员页 |
ADMIN_VIEW_COMMODITY_TABLE ADMIN_VIEW_COMMODITY_TOOLBAR ADMIN_VIEW_COMMODITY_FOOTER | 后台商品页 |
ADMIN_VIEW_COMMODITY_POST | 商品添加/编辑表单扩展 |
ADMIN_VIEW_CATEGORY_TOOLBAR ADMIN_VIEW_CATEGORY_TABLE ADMIN_VIEW_CATEGORY_POST | 后台分类页 |
ADMIN_VIEW_ORDER_TABLE ADMIN_VIEW_ORDER_TOOLBAR ADMIN_VIEW_ORDER_FOOTER | 后台订单页 |
ADMIN_VIEW_CONFIG_TOOLBAR | 网站设置页工具栏,返回二维数组 |
ADMIN_API_PLUGIN_SAVE_CONFIG | 保存插件配置,参数为 string $pluginName, array $postMap |
USER_API_ORDER_TRADE_BEGIN | 下单前,参数为 array $_POST |
USER_API_ORDER_TRADE_AFTER | 下单成功,Commodity $commodity, Order $order, Pay $pay |
USER_API_ORDER_TRADE_PAY_BEGIN | 下单后、发起支付前,订单相关对象 |
USER_API_ORDER_PAY_AFTER | 付款成功,Commodity $commodity, Order $order, Pay $pay |
USER_API_RECHARGE_AFTER | 充值付款成功,UserRecharge $recharge, Pay $pay |
USER_API_AUTH_REGISTER_BEGIN/AFTER | 注册前 / 注册后 |
USER_API_AUTH_LOGIN_BEGIN/AFTER | 登录前 / 登录后 |
KERNEL_INIT | 核心初始化完成 |
CONTROLLER_CALL_BEFORE/AFTER | 控制器调用前 / 后 |
RENDER_VIEW | 视图渲染,传入视图原文引用 |
USER_VIEW_AUTH_LOGIN_BUTTON / USER_VIEW_AUTH_REGISTER_BUTTON | 前台登录 / 注册按钮 |
USER_VIEW_SECURITY_NAV / USER_VIEW_PERSONAL_FORM | 用户安全中心 / 个人资料 |
USER_VIEW_COMMODITY_POST | 前台商品表单 |
HTTP_ROUTE_RESPONSE | HTTP 路由响应返回用户前 |
USER_VIEW_INDEX_HEADER/BODY/FOOTER | 前台首页三个区域 |
USER_API_INDEX_CATEGORY_LIST | 分类列表,数组引用 |
USER_API_INDEX_COMMODITY_LIST | 商品列表,数组引用 |
USER_API_INDEX_COMMODITY_DETAIL_INFO | 商品详情数组引用 |
USER_API_INDEX_TRADE_CALC_AMOUNT | 下单金额计算结果数组引用 |
USER_API_INDEX_PAY_LIST | 支付列表数组引用 |
USER_API_INDEX_QUERY_LIST | 查询订单列表数组引用 |
USER_API_INDEX_QUERY_SECRET | 查询卡密,Order $order |
USER_API_PURCHASE_RECORD_LIST | 购买记录列表数组引用 |
USER_VIEW_MENU / USER_VIEW_HEADER_NAV | 前台菜单 / 顶部导航 |
USER_VIEW_QUERY_TRADE_NO | 订单号后扩展内容 |
USER_VIEW_HEADER/BODY/FOOTER | 前台公共布局区域 |
USER_GLOBAL_VIEW_HEADER/BODY/FOOTER | 前台全局区域 |
WAF_INTERCEPT | WAF 拦截,参数为 $message |
SERVICE_SMTP_SEND_BEFORE/SUCCESS/ERROR | 邮件发送前 成功 失败 |
HACK_ROUTE_TABLE_COLUMNS | 订单表格列扩展 |
HACK_ROUTE_TABLE_SEARCH | 订单表格搜索扩展 |
HACK_SUBMIT_FORM / HACK_SUBMIT_TAB | 表单 / 标签页扩展 |
SERVICE_SHOP_GET_ITEM_STOCK | 商品库存扩展,返回 Stock |
不要凭常量名称猜参数,开发前应打开 app/Consts/Hook.php 和触发点调用处搜索 hook(,以当前源码为准。
5. 生命周期
生命周期注解使用 Kernel\Annotation\Plugin,建议始终写命名参数:
可用状态:
| 状态 | 常量 | 说明 |
|---|---|---|
| 启动 | Plugin::START | 后台启用插件时执行 |
| 停止 | Plugin::STOP | 后台停用插件时执行 |
| 卸载 | Plugin::UNINSTALL | 卸载流程使用 |
| 安装 | Plugin::INSTALL | 安装流程使用 |
| 升级 | Plugin::UPGRADE | 升级流程使用 |
| 保存配置 | Plugin::SAVE_CONFIG | 保存配置时执行,即使插件未启用 |
当前本地插件分支在启用/停用时直接调用 runHookState()。手工复制目录不会自动执行“安装”或“升级”迁移;如果插件需要建表,必须在正式安装流程中调用,或提供一次性、可重复执行的管理操作。
6. 控制器、视图和 URL
插件控制器通过 /plugin/{PluginID}/{Controller}/{method} 访问。例如:
对应:
后台控制器继承 App\Controller\Base\API\ManagePlugin 或 App\Controller\Base\View\ManagePlugin;前台控制器使用对应的 UserPlugin。后台接口应加 ManageSession,涉及输入时加 Waf,例如:
视图由基类的 render() 从插件自己的 View/ 目录读取。Hook 中常见写法:
路由访问插件控制器前必须启用插件;Hook::load() 会对未启用插件重定向到首页。
7. 日志、缓存和数据库
7.1 运行日志
日志文件是:
后台插件列表中的“日志”按钮读取该文件。不要写入 Bot Token、密码、卡密或完整用户敏感信息。
7.2 插件缓存
App\Util\Plugin 已提供 setCache()、getCache()、getCaches()、delCache() 和 clearCache(),数据保存到:
这不是高并发数据库。需要查询、事务或持久业务数据时使用现有 Eloquent 模型和数据库迁移逻辑。
8. 安装、启用和打包
8.1 手工安装
把插件目录放到:
然后:
检查
Config/Info.php、Config/Config.php和命名空间。确认目录名不是
store.cache中已有插件 ID。后台进入“通用插件”。
清空状态筛选,找到插件并打开配置。
保存配置后点击“启用”。不要只手工把
STATUS改成1,因为启用动作还负责注册 Hook。
8.2 ZIP 结构
压缩包根目录必须直接包含:
错误结构:
如果安装器把这个 ZIP 解压到 app/Plugin/,最终会多一层目录,插件就不会被检测到。
8.3 1Panel / Linux 权限
PHP-FPM 用户必须能遍历插件目录并读写配置、日志。推荐:
如果后台能看到插件但保存配置失败、日志为空,优先检查权限。Windows 生成的 ZIP 可能没有 Unix 目录模式,上传后目录会变成 644,PHP-FPM 无法进入 Config/、Hook/ 或 Support/。
8.4 修改代码后的缓存
本地 Hook 当前按请求扫描,通常刷新页面即可看到新代码;但 PHP OPcache 可能继续使用旧字节码。修改 Hook、控制器或模板后,按顺序处理:
后台停用再启用插件。
清理 PHP OPcache,或重启对应 PHP-FPM 容器。
浏览器强制刷新后台页面。
9. 最小自制插件示例
目录:
Config/Info.php:
Config/Config.php:
Hook/Main.php:
自检:
启用后创建测试订单,再在插件“日志”中确认是否出现订单号。
10. Telegram 订单通知插件
仓库中的 app/Plugin/TelegramNotification 是完整的付款成功通知实现,适合直接使用或作为网络通知插件参考。
位于 https://blog.mhdy.net/archives/yi-ci-yuan-fa-qia-de-zi-zhi-cha-jian
配置步骤
在
@BotFather使用/newbot创建 Bot,保存 Token。让 Bot 在目标私聊、群组或频道中收到一条消息。
调用
https://api.telegram.org/bot<TOKEN>/getUpdates获取message.chat.id。论坛群需要额外填写
message_thread_id;普通群留空。后台填写 Token、Chat ID,保存后点击启用。
重要配置
默认只发送纯文本,不发送卡密。
commodity_mode=1时只通知选中的商品。notify_free=0可忽略零元/赠送订单。联系方式默认脱敏;完整显示前确认隐私和群权限。
timeout建议 3 秒以内;网络失败只记录日志,不阻塞支付和发货。自定义 API 只允许填写自己控制的 HTTPS 代理;Token 和订单内容会发送到该地址。
完整变量和故障排查见 app/Plugin/TelegramNotification/Wiki/README.md。
11. 自检与故障排查
11.1 插件不显示
依次检查:
查看后台接口 /admin/api/plugin/getPlugins 的响应,确认列表中有对应 id。
11.2 能显示但 Hook 不执行
Config/Config.php的STATUS是否为1。是否点击了后台“启用”,而不是只改文件。
Hook 类名、文件名、命名空间是否一致。
方法是否
public,是否使用#[Hook(point: ...)]。插件 ID 是否误出现在
runtime/plugin/store.cache。PHP-FPM 用户是否能读取
Hook/目录。是否需要清理 OPcache。
可在测试环境运行已有检查脚本:
该脚本会临时启用 Telegram 插件,确认 USER_API_ORDER_PAY_AFTER 已注册,然后恢复原配置。
11.3 配置或日志写入失败
检查:
目录建议 755,文件建议 644;运行用户需要对配置文件和日志文件具有写权限。不要为了省事把整个站点改成 777。
11.4 订单流程被插件拖慢或报错
网络请求设置短超时,并捕获
Throwable。不要在付款 Hook 中修改订单状态,除非插件明确负责发货且已处理幂等。
不要把卡密放进外部通知。
日志只记录订单号、结果和脱敏错误。
先停用插件验证问题是否消失,再查看
runtime.log。
12. 发布前检查清单
插件 ID 唯一,目录和命名空间大小写一致。
Config/Info.php返回完整元数据。Config/Config.php至少有STATUS。所有外部输入经过验证,后台接口有会话和 WAF 保护。
Hook 参数与触发点实际调用一致。
网络调用有超时、异常处理和日志脱敏。
不发送卡密、Token、密码和不必要的个人信息。
ZIP 根目录没有多余的插件名外层目录。
Linux 目录
755、文件644,PHP-FPM 可读写必要文件。在测试订单上验证启用、停用、重复触发和网络失败场景。