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

130 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 插件机制对比与差距分析(本项目 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.ini`INI | `info.php`PHP 数组,含 `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.php``middleware.php``provider.php` |
| **自动加载** | 动态:helper `get_addon_class` + 生成 hooks/route 配置 | 静态 PSR-4 `addon\: addon`composer.json | ✅ 本项目更简单稳健 |
| **扩展注册(事件/中间件/服务)** | 钩子体系(见下)+ 行为事件 | `info.php``events/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 + 冲突检测 + 授权 | `AddonService`install/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 → BaseController``auth->verifyAuth`),走 MultiApp 的标准应用控制器 | 🔶 有鉴权,但缺"资源前缀/多语言"等便利封装 |
| **辅助函数层** | 完整:`get_addon_info/config/list/instance/class``addon_url``hook``get_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>`、独立 `runtime``route_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 ⭐ P0`addon_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` 引入),实现:
```php
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\BaseAddon``ywxapp\addon` 两套并存,实际插件全部 `extends addon``BaseAddon` 从未被继承/调用。
**建议**:二选一。要么把 `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 的水平。