Files
YwxAppThink/docs/插件机制对比与差距分析.md

12 KiB
Raw Permalink Blame History

插件机制对比与差距分析(本项目 vs FastAdmin

配套文档:《FastAdmin 插件机制参考》(docs/FastAdmin插件机制参考.md)、《插件开发指南》(docs/插件开发指南.md)。 本文基于通读双方源码得出,目的是:看清架构差异 → 定位真实短板 → 给出可落地的完善建议


0. 一句话结论

本项目与 FastAdmin 是两种不同的插件架构

架构路线 插件本质
FastAdmin Route 分发 + copydirs 半集成 挂在主框架上的"模块",通过统一路由 addon/{addon}/{...} 访问,可把文件复制注入主框架
本项目(ywxapp MultiApp 多应用 每个插件是一个完整独立的 ThinkPHP8 应用(命名空间 addon\<name>),通过 /<addon>/<controller>/<action> 访问

所以"是否达到 FastAdmin 水平"不能简单对标——你的路由/隔离模型比 FastAdmin 更强更干净(无需 copydirs 往主框架塞文件),但在钩子体系、辅助函数层、生命周期完整度上确有缺口。


1. 逐项对比表

能力 FastAdmin 本项目 ywxapp 评价
元信息 info.iniINI info.phpPHP 数组,含 events/middleware/services/license 各有取舍,PHP 数组更灵活
加载入口 框架启动读 get_addon_autoload_config 生成的 hooks/route 配置 AppService::boot()→loadAddonRelevant()boot 期扫描 addon/*/info.php,仅 state=1 加载 时机早、统一
路由分发 \think\addon\Route::execute 解析 addon/{addon}/{controller}/{action} + _empty 兜底 MultiApp/<addon>/... 识别为独立应用,走标准 TP 路由(支持插件内 route/app.php 本项目更强:每插件可有完整 config/route/event.phpmiddleware.phpprovider.php
自动加载 动态:helper get_addon_class + 生成 hooks/route 配置 静态 PSR-4 addon\: addoncomposer.json 本项目更简单稳健
扩展注册(事件/中间件/服务) 钩子体系(见下)+ 行为事件 info.phpevents/middleware/services 三数组,boot 期 loadEvent/middleware->import/bind ⚠️ 能用,但无"钩子"抽象
钩子 / 行为事件 核心:方法即钩子(反射差集自动注册)+ hook()/Hook::listen + 前端 bootstrap.js 合并 缺失。只有原生 TP Event(需在 info.php 显式声明 listener 类);BaseAddon::registerHooks() 是空壳且从未被调用 最大短板
生命周期 install/uninstall(抽象)+ enable/disable/upgrade(可选)+ importsql + copydirs + refresh + 冲突检测 + 授权 AddonServiceinstall/local/uninstall/enable/disable/package/backupData/restoreBackup/importsql/createMenu;基类 addon 仅抽象 install/uninstall 🔶 大体齐全,enable/disable 钩子未打通到主类
后台菜单注入 install() 内写 admin_rule 等表 createMenu()menu.json → 写 admin_power(后台)/ 用户规则(member/frontend),先清后建幂等 对等甚至更规整(区分三端)
业务配置 config.php 字段描述符;set_addon_config 写回 config.php 文件 字段描述符;已改为数据库表 + 缓存service/Config.php),config.php 仅作默认值来源 本项目更合理(不改插件文件)
前台控制器基类 \think\addon\Controller(内置 Auth/多语言/__ADDON__/noNeedLogin AddonBackend → BaseControllerauth->verifyAuth),走 MultiApp 的标准应用控制器 🔶 有鉴权,但缺"资源前缀/多语言"等便利封装
辅助函数层 完整:get_addon_info/config/list/instance/classaddon_urlhookget_addon_tables 几乎缺失addon.php:105 调用了 addon_url(),但全项目无此函数定义(疑似潜在致命错误,见 §3.2 短板 + 隐藏 bug
冲突检测/全局文件 noconflict/getGlobalFiles(因 copydirs 需要) 无(MultiApp 隔离,本就不需要 N/A,非缺陷
在线插件市场/授权 远程下载/升级 + 域名授权 md5(md5(domain).license) package/local 打包与离线安装;info.php['license'] + hasValidLicense() 授权校验 🔶 有授权与离线安装,缺在线市场/一键升级
前端钩子 bootstrap.js 合并到 public/assets/js/addon.js 🔶 视需要
基类一致性 单一基类 \think\addon ⚠️ 两套并存:实际用 ywxapp\addon,另有 ywxapp\BaseAddon(含 registerHooks/registerRoutes/getThinkVersion完全未被使用(死代码) 需收敛

实施状态(截至 2026-07-21:本文列出的短板已全部补齐——

  • §3.2 addon_url() 未定义 → 已新增 ywxapp/helper.php 辅助层(addon_url / get_addon_*)。
  • §3.3 基类收敛 → 死代码 BaseAddon 已移除,统一使用 ywxapp\addon
  • §3.4 生命周期打通AddonService::enable/disable/upgrade 已回调插件主类对应方法。
  • §3.1 钩子体系 → 已在会员/文章/支付/MQTT/异常/插件市场/插件生命周期等约 30 个节点预埋钩子点,统一使用 ThinkPHP 内置 event() 触发;完整钩子点清单见 docs/插件开发指南.md §4.1。
  • §3.6 在线升级AddonService::onlineUpgrade() 已实现(下载 → 备份 → 覆盖 → upgrade 钩子 → 增量 SQL)。

当前架构在"可插拔能力、生命周期完整度、开发者体验"上已达到并在路由 / 隔离 / 配置存储方面超过 FastAdmin 水平。下文 §3 / §5 保留原始差距分析与路线图,供复盘参考。


2. 架构差异详解(为什么不能照搬 FastAdmin)

2.1 路由:MultiApp(你) vs Route::executeFA

  • FastAdmin:所有插件请求走同一条 addon/:addon/:controller/:action 路由,由 Route::execute 反射定位 \addon\<name>\controller\<X>,插件控制器必须继承 \think\addon\Controller。插件想要"独立配置/独立路由"很别扭。
  • 本项目MultiApp::setAddon() 直接把插件当成一个独立 TP 应用:设置 namespace=addon\<name>、独立 runtimeroute_path,并 loadApp() 加载该插件的 common.php / config/*.php / event.php / middleware.php / provider.php / 语言包。这意味着每个插件几乎拥有主应用的全部能力,隔离更彻底。
  • 👉 结论:你的路由模型优于 FastAdmin,无需引入 addon/{addon}/... 路由,也无需 copydirs。

2.2 文件注入:copydirsFA)你不需要

FastAdmin 靠 copydirs 把插件的 application/public/ 复制进主框架才能生效,由此衍生出"冲突检测、全局文件清单、启用即复制/禁用即删除"一整套复杂机制。MultiApp 天然隔离,完全不需要这套——这是你的优势,不是缺失。

2.3 扩展注册:Event 数组(你) vs 方法即钩子(FA)

  • 你现在:插件要监听全局事件,需在 info.php['events'] 里写 ['GlobalEvent' => [监听类::class]]boot 期 loadEvent 注册。能用,但不是"钩子"——没有"插件主类里写个同名方法就自动挂载"的便利,也没有统一的"钩子点"清单。
  • FastAdmin:定义好一批钩子名(如 user_login_after),任何插件主类写同名方法即自动参与,业务侧 hook('user_login_after',$data) 一行触发。扩展性和"可插拔感"明显更强

3. 真实短板与修复建议(按优先级)

3.1 P0:缺"钩子/行为事件"体系(最该补)

现状:只有原生 Event + info.php 声明,无统一钩子点、无方法即钩子、BaseAddon::registerHooks 是空壳。

建议方案(贴合 TP8,不照搬 FA 反射)

  1. 在框架侧定义钩子触发助手 hook($name, &$params),内部走 think\facade\Event::trigger("addon.$name", $params),并约定命名空间前缀 addon.
  2. 约定插件在 info.php['events'] 里声明 'addon.user_login_after' => [\addon\x\listener\Xxx::class]——复用你现有的 boot 期 loadEvent,零新增加载逻辑。
  3. 在关键业务点(登录、下单、消息投递等)预埋 hook('user_login_after', $user)
  4. 文档里维护一份全局钩子点清单(钩子名 + 触发时机 + 参数),这是"可插拔"的关键。

如果想要"方法即钩子"的便利,可在 AppService::loadAddonRelevant() 里对启用插件主类做一次 get_class_methods 差集,把匹配钩子名的方法用 Event::listen 注册——但非必需,声明式已够用且更可控。

3.2 P0addon_url() 未定义(潜在致命 bug

ywxapp/addon.php:105 执行 $info['url'] = addon_url($name);,但全项目搜不到 function addon_url 的定义。当 getInfo() 走"未命中缓存"分支时会触发 Call to undefined function建议:新建插件助手文件(如 ywxapp/helper.php,并在 composer autoload.files 引入),实现:

function addon_url(string $name, string $url = '', array $vars = []): string {
    // MultiApp 模型下:/<addon>/<controller>/<action>
    $path = '/' . $name . ($url ? '/' . ltrim($url, '/') : '');
    return $vars ? $path . '?' . http_build_query($vars) : $path;
}

同时补齐 get_addon_info/get_addon_config/get_appmarket_addon_list/get_addon_instance 等常用包装(内部转调 AddonService / Config),形成统一辅助层。

3.3 P1:基类收敛(消除死代码 BaseAddon

ywxapp\BaseAddonywxapp\addon 两套并存,实际插件全部 extends addonBaseAddon 从未被继承/调用。 建议:二选一。要么把 BaseAddon 的有用能力(enable/disable/upgrade 默认实现、getThinkVersion 版本约束、getDependencies 依赖声明)合并进 addon,删掉 BaseAddon;要么明确废弃并从仓库移除,避免误导。

3.4 P1:打通 enable/disable/upgrade 到插件主类

现在 AddonService::enable()/disable() 主要做 state 切换与菜单/表处理,但未回调插件主类的 enable()/disable()/upgrade()(基类 addon 也没这几个方法)。 建议:在 addon 基类补 enable()/disable()/upgrade() 空实现(可重写),并在 AddonService 对应流程中"若方法存在则调用",与 install/uninstall 对称——对齐 FastAdmin 生命周期。

3.5 P2:前台控制器基类便利封装

可选:为插件前台控制器提供一个基类,内置 config 注入视图、__ADDON__ 静态资源前缀、按 noNeedLogin 的会员鉴权、多语言目录约定,减少每个插件的样板代码。

3.6 P2:升级与在线市场(按业务需要)

已有 package/local 离线打包安装与 license 授权;若要对齐 FastAdmin,可补"版本比对 + 覆盖升级(保留数据)+ 远程下载"。非核心可后置。


4. 无需对齐的项(避免过度设计)

以下 FastAdmin 特性由本项目架构决定不需要引入:

  • copydirs 全局文件映射 —— MultiApp 已隔离。
  • noconflict/getGlobalFiles 冲突检测 —— 无文件注入即无冲突。
  • addon/{addon}/{controller}/{action} 统一路由 —— 多应用路由更强。
  • info.ini —— info.php 更适合本项目。
  • set_addon_config 写回 config.php 文件 —— 已用"数据库+缓存"替代,更优。

5. 完善路线图(建议顺序)

  1. addon_url 未定义(§3.2)——止血,最快。
  2. 建统一辅助层 ywxapp/helper.php(§3.2)——get_addon_* 系列。
  3. 引入钩子体系(§3.1)——hook() 助手 + 钩子点清单文档 + 复用 info.php events。
  4. 基类收敛 + 生命周期打通(§3.3、§3.4)——删/并 BaseAddon,补 enable/disable/upgrade 回调。
  5. (可选)前台控制器基类、在线升级(§3.5、§3.6)。

完成 1~4 后,本项目插件机制在"可插拔能力、生命周期完整度、开发者体验"上即可达到并在路由/隔离/配置存储方面超过 FastAdmin 的水平。