chore: 重写初始提交(清空历史,整理后全量提交)

This commit is contained in:
ywxapp
2026-08-16 16:54:14 +08:00
commit 6c1a106bc1
1808 changed files with 238144 additions and 0 deletions
+505
View File
@@ -0,0 +1,505 @@
# Ywxapp 插件应用开发指南
> 适用框架:Ywxapp(基于 ThinkPHP 8 的多应用框架)
> 本文以 `mqttbroker` 插件为实例,说明从脚手架到打包上线的完整流程。
---
## 1. 概述
Ywxapp 的「插件」本质上就是一个 **ThinkPHP 多应用(Multi-App**
- 插件源码目录:`addon/<name>/`
- 插件命名空间:`addon\<name>\...`
- 访问 URL 前缀:`/<name>/...`(例如 `/mqttbroker/backend/index`
核心支撑类:
| 类 / 文件 | 职责 |
| --- | --- |
| `ywxapp\addon` | 插件基类,定义 `install()` / `uninstall()` 抽象钩子 |
| `ywxapp\service\AddonService` | 安装 / 卸载 / 打包 / 开发者模式 |
| `ywxapp\service\AppService` | 框架 boot 时扫描 `addon/`,加载已启用插件的事件、中间件、服务 |
| `config/addon.php` | 插件机制全局配置(路径、优先级、白名单等) |
一个插件 = 一个自带路由、控制器、模型、视图、配置、命令、静态资源的独立应用。
---
## 2. 插件目录结构
`mqttbroker` 为例:
```
addon/mqttbroker/
├── Addon.php # 安装/卸载钩子(必须)
├── info.php # 插件元信息(必须)
├── menu.json # 后台/会员/前台菜单定义
├── install.sql # 建表/种子 SQL(安装时自动导入)
├── common.php # 应用公共文件(随应用自动加载,可选)
├── config.php # 设置表单字段定义(可选,仅当插wophp 文件,
# 统一在 info.php 的 events / middleware / services 数组里声明(详见 3.1)。
# - config.php 非必须:无可配置项的插件直接不建即可。
├── controller/ # 控制器:admin/、api/ ...
├── model/ # 模型
├── service/ # 业务服务类
├── protocol/ # 自定义协议(如 MQTT)
├── view/ # 视图模板(backend/admin/*.html
├── route/
│ └── app.php # 插件路由
└── static/ # 静态资源(打包时自动迁出到 public/static/addon/mqttbroker/
```
---
## 3. 核心文件详解
### 3.1 `info.php`(元信息,必须)
```php
return [
'name' => 'mqttbroker', // 小写字母,唯一标识,须与目录名一致
'title' => 'MQTT代理',
'intro' => '...',
'author' => '',
'website' => '',
'version' => '2.1.0', // 语义化版本 x.y.z
'state' => 1, // 0=禁用 1=启用(由框架读取控制加载)
'url' => '/mqttbroker/admin',
'license' => '', // 付费插件可声明 license=>true,配合 addon_license_check
'config' => [], // boot 时已合并进容器(见 3.5)
'events' => [], // 随应用加载:app()->loadEvent(...)
'middleware' => [], // 随应用加载:app()->middleware->import(...)
'services' => [], // 随应用加载:app()->bind(...)
];
```
- 框架 `AppService::loadAddonRelevant()` 在 boot 期扫描 `addon/`,仅对 `state=1` 的插件加载其 `events/middleware/services`
- 安装流程会写入 `install_time`,并管理 `state`
> **事件 / 中间件 / 服务在哪里声明?** 统一在 `info.php` 对应数组里填写(见下方示例)。虽然 ThinkPHP 的 MultiApp 在分发到该插件应用时也会自动加载 `event.php` / `middleware.php` / `provider.php`,但插件体系的**推荐入口是 `info.php`**:它在 boot 期由 `AppService::loadAddonRelevant()` 读取,对所有启用插件生效,且不依赖当前请求是否命中该插件(时机早、行为一致)。因此插件**无需创建** `event.php` / `middleware.php` 文件。
> - `events`:事件监听映射,如 `['MqttClientConnect' => \addon\mqttbroker\listener\Connect::class]`(等价于 `Event::listen`)。
> - `middleware`:要注册进应用中间件栈的类,如 `[\addon\mqttbroker\middleware\MqttAuth::class]`。
> - `services`:服务容器绑定(TP 服务类),如 `['mqttBroker' => \addon\mqttbroker\service\Broker::class]`。
>
> 没有要注册的就保留空数组 `[]`(你的 `mqttbroker` 当前即如此)。这些数组仅在 `state=1`(启用)时才会被加载。
### 3.2 `Addon.php`(钩子,必须实现 install/uninstall
```php
<?php
declare (strict_types = 1);
namespace addon\mqttbroker;
use think\facade\Db;
use ywxapp\AddonBase;
class Addon extends addon
{
// 安装钩子:表由 install.sql 自动创建,通常无需额外处理
public function install()
{
return true;
}
// 卸载钩子:清理本插件数据表与菜单残留(DROP/ALTER 必须放这里!)
public function uninstall()
{
Db::execute("DROP TABLE IF EXISTS `wxapp_mqttbroker_connection`");
// ... 其余表
Db::execute("DELETE FROM wxapp_admin_power WHERE addon='mqttbroker'");
Db::execute("DELETE FROM wxapp_user_rule WHERE name LIKE 'mqttbroker:%'");
return true;
}
}
```
> ⚠️ **重要事实**`enable/disable` 在 `ywxapp\addon` 中**不是**抽象方法;框架 `AddonService::enable()/disable()` 仅切换 `info.php` 的 `state` 值,**不会**回调插件的 `enable()/disable()` 钩子。因此不要把"启用/禁用时需要执行的逻辑"写在钩子里,菜单显隐完全由 `state` 决定。
### 3.3 `install.sql`(建表,自动导入)
安装时由 `AddonService::importsql()` 自动执行,规则如下:
- **白名单限制**:仅允许 `CREATE TABLE``INSERT` 语句;`SET NAMES` / `SET FOREIGN_KEY_CHECKS` 等会被跳过(仅记日志)。
- **表命名**`wxapp_<插件名>_*`,与核心表前缀保持一致。
- **前缀替换**SQL 中的 `__PREFIX__` 会被替换为数据库实际前缀(若直接写死 `wxapp_` 也可,但需与数据库配置一致)。
- **建议全部用 `CREATE TABLE IF NOT EXISTS`**,便于重跑。
- **破坏性语句(DROP / ALTER / DELETE)必须放进 `Addon::uninstall()`,绝不能写进 install.sql**(白名单会拒绝执行)。
示例:
```sql
CREATE TABLE IF NOT EXISTS `wxapp_mqttbroker_connection` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`client_id` varchar(191) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `idx_client_id` (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MQTT客户端连接表';
```
### 3.4 `menu.json`(菜单,自动注入)
```json
{
"backend": [
{
"name": "mqttbroker",
"title": "MQTT代理",
"icon": "fa fa-sitemap",
"type": 1,
"sort": 60,
"status": 1,
"child": [
{ "name": "mqttbroker/index", "title": "概览", "icon": "fa fa-dashboard", "type": 2, "sort": 1, "route": "/mqttbroker/backend/index" },
{ "name": "mqttbroker/stats", "title": "实时监控", "type": 2, "sort": 8, "route": "/mqttbroker/backend/stats" }
]
}
],
"member": [],
"frontend": []
}
```
字段说明:
- 顶层三类:`backend`(后台)、`member`(会员中心)、`frontend`(前台)。
- 每项:`name`(权限标识,首个菜单须与插件标识同名)、`title``icon``type`1=菜单 2=按钮)、`sort``route`(实际访问 URL)、`child`(子项)。
- 框架会把 `name` 处理为 `<addon>:<type>:<name>`,写入 `wxapp_admin_power`(后台)或 `wxapp_user_rule`(会员/前台)。
- 安装时 `createMenu()` 注入;卸载/重装时先清理旧菜单再重建(保证幂等)。
### 3.5 `config.php`(后台设置表单,**可选**)
> **重要:`config.php` 不是必须文件,框架也不会自动读取/渲染它。** 它只是一份「设置表单字段定义」,是否生效完全取决于插件**自己有没有实现一个后台设置页**去 `include` 它。
> - 若插件**没有可配置项**(如 `wxchat`),就**不要创建** `config.php`(早期脚手架生成的 `['status'=>true]` 属于无效占位,已清理)。
> - 若插件**需要后台可配置**(如 `mqttbroker`),则用「字段描述符数组」格式编写,并在插件内自建设置页读取它(见下方"如何真正用起来")。
```php
return [
[
'name' => 'port',
'title' => '监听端口',
'type' => 'number',
'value' => '1883',
'tip' => 'Broker 监听的 TCP 端口',
],
[
'name' => 'ssl_enabled',
'title' => '启用TLS',
'type' => 'select',
'options' => ['0' => '关闭', '1' => '开启'],
'value' => '0',
],
// ...
];
```
**字段格式**:每项为一个描述符 `['name'=>标识, 'title'=>显示名, 'type'=>控件类型, 'value'=>默认值, 'tip'=>提示, 'options'=>下拉项]``type` 常用 `string / number / select / textarea`
**如何真正用起来(以 `mqttbroker` 为例)**`config.php` 是「设置表单字段定义」,框架统一把配置以**独立项**写入数据库表 `wxapp_addon_config``addon`+`name` 唯一,每行一个配置项),并配合缓存(键 `addon_config_<name>`,默认 1 小时)。读取/保存统一走 `AddonService::config()`
1. **后台通用配置页 + 列表快捷入口**:后台「插件管理」列表页(`app/backend/view/backend/addon/index.html`)每行带「配置」按钮(仅对含 `config.php` 的插件显示),直达通用页(`app/backend/controller/addon.php::setting`)。通用页自动按 `config.php` 渲染表单、AJAX 保存写库并清缓存。**无需每个插件自建页面**,没有 `config.php` 的插件提示「无可配置项」。
2. 插件也可自建设置页(如 mqttbroker 的 `controller/backend/MqttBroker.php::setting/saveSetting`),只需调用:
```php
$def = include ADDON_PATH . 'mqttbroker' . DIRECTORY_SEPARATOR . 'config.php';
$saved = \ywxapp\service\AddonService::config('mqttbroker'); // 读数据库配置(带缓存)
// 保存:\ywxapp\service\AddonService::config('mqttbroker', input('post.'));
```
3. 运行期读取:业务代码用 `AddonService::config($name)`、基类 `getConfig()` 或助手 `get_addon_config($name)` 取值。建议业务默认值在代码里用 `array_merge($defaults, $saved)` 叠加,保证未配置时使用默认值(`config.php` 的 `value` 仅作表单初始显示)。
**三条容易混淆的配置入口,务必区分**:
| 入口 | 存储位置 | 加载时机 | 用途 |
| --- | --- | --- | --- |
| `config.php` + 数据库 | `wxapp_addon_config`(独立项,每行一个配置) | 后台通用配置页或插件自建设置页调用 `AddonService::config()` | 管理员可视化配置(端口、开关等),带缓存 |
| `info.php` 的 `config`/`events`/`middleware`/`services` | `info.php` | boot 期由 `AppService` 加载 | 事件/中间件/服务注册 |
| `config/` 目录下的 `*.php` | 各配置文件 | MultiApp 分发到该插件时加载 | 标准 TP 应用级配置 |
> 另:框架基类 `ywxapp\addon::getConfig()` 也能读 `config.php`(同样合并 `.addonrc`),但仅当你的类继承自它时可用;`mqttbroker` 的做法是直接 `include` + `AddonService::config()`,二者等效,按需选一即可。
### 3.6 `route/app.php`(路由)
```php
<?php
use think\facade\Route;
// 后台
Route::group('admin', function () {
Route::get('index', 'backend/MqttBroker/index');
Route::post('publish', 'backend/MqttBroker/doPublish');
Route::get('stats', 'backend/MqttBroker/stats');
});
// 对外 API
Route::group('api', function () {
Route::get('status', 'api/Mqtt/status');
Route::post('publish', 'api/Mqtt/publish');
});
```
- 框架以**多应用**方式加载插件:`addon/<name>` 即应用 `<name>`URL 前缀为 `/<name>/`。
- 控制器引用格式:`layer/Controller/action` → 对应文件 `controller/<layer>/<Controller>.php`(如 `backend/MqttBroker/index` → `controller/backend/MqttBroker.php::index()`)。
- 同目录下的 `common.php` 会随应用自动加载。
- ⚠️ 插件的事件监听、中间件、服务容器绑定**推荐通过 `info.php` 的 `events` / `middleware` / `services` 数组声明**boot 期由 `AppService::loadAddonRelevant()` 读取,见 3.1),而**不要**依赖 `event.php` / `middleware.php` 文件——后者仅由 MultiApp 在请求命中该插件应用时才分发加载,时机较晚且与插件统一加载机制不一致。所以插件**无需创建**这两个文件;要注册就写进 `info.php`,不需要则留空数组 `[]`。
### 3.7 控制器 / 模型 / 视图 / 服务
- 命名空间:
- 后台控制器:`addon\<name>\controller\admin`,继承 `ywxapp\controller\AddonBackend`
- API 控制器:`addon\<name>\controller\api`,继承 `ywxapp\controller\ApiController`
- 模型:`addon\<name>\model\Xxx`
- 服务:`addon\<name>\service\Xxx`
- 鉴权开关(开发期可放开):
```php
protected $noNeedLogin = ['*'];
protected $noNeedVerify = ['*'];
```
- 视图:后台模板放 `view/backend/admin/*.html`,用 `$this->fetch('admin/index')` 渲染(路径前缀由框架视图配置决定)。
### 3.8 `command/`(命令行 / 常驻进程)
```php
// config/console.php 注册
"mqttbroker:start" => \addon\mqttbroker\command\MqttBroker::class,
```
- 命令行放在 `command/`,在 `config/console.php` 注册。
- 长驻进程(如 Broker)基于 Workerman:命令内 `Worker::runAll()`。
- Windows 与 Linux 行为差异:单 PHP 进程无法多 `Worker` 实例,需用 `DIRECTORY_SEPARATOR === '\\'` 做平台降级(如 WS 监听仅 Linux 实网生效)。
---
## 4. 插件生命周期
```
框架启动 (AppService::boot)
└─ loadAddonRelevant(): 扫描 addon/,对 state=1 的插件加载 events/middleware/services
└─ 加载 ywxapp/helper.php(提供 addon_url / hook / get_addon_* 等全局函数)
安装 (addon:manage -a develop 或 后台离线/在线安装)
└─ executeInstall(): 写 info(state=0) → install 钩子 → 注入菜单 → importsql
(离线/在线安装默认 state=0,需手动启用;addon:manage -a develop 直接置 state=1
启用 / 禁用 (后台 toggle)
└─ enable()/disable() 钩子(可重写,返回 false 阻断)→ 再修改 info.php 的 state
默认实现会联动 Menu::enable/disable 同步菜单可见性(见 Addon.php 生成模板)
升级 (后台升级)
└─ upgrade($currentVersion) 钩子(可重写,用于数据/配置迁移)→ 清理配置缓存
卸载 (后台卸载)
└─ uninstall() 钩子(DROP 表 / 清菜单)→ 删除插件目录
```
> 生命周期钩子统一由 `AddonService::callAddonHook($method)` 调用插件主类
> `addon\<name>\Addon` 的同名方法(`install`/`uninstall`/`enable`/`disable`/`upgrade`)。
> 基类 `ywxapp\addon` 已为 `enable`/`disable`/`upgrade` 提供默认空实现,**只有 `install`/`uninstall` 仍是抽象方法,必须实现**。
---
## 4.1 钩子体系(可插拔扩展点)
直接复用 ThinkPHP 自带的事件机制,**无需框架额外封装**:
- 业务侧在关键节点用 ThinkPHP 内置全局助手 `event('事件名', $params)` 触发(即 `Event::trigger` 的全局封装,无需额外封装)。
- 插件侧在 `info.php['events']['listen']` 中声明监听器即可挂接,框架 boot 期 `loadEvent` 会自动注册,无需改框架源码。
```php
// addon/<name>/info.php
'events' => [
'listen' => [
'user_login_after' => [\addon\xxx\listener\UserLogin::class],
],
],
```
```php
// addon/<name>/listener/UserLogin.php
namespace addon\xxx\listener;
class UserLogin
{
public function handle($user)
{
// $user 为登录成功的会员模型实例
}
}
```
业务侧在关键节点预埋触发点(完整清单):
### 会员 / 认证(`ywxapp/library/User.php`
| 事件名 | 触发时机 | 参数 | 代码位置 |
| --- | --- | --- | --- |
| `user_register_after` | 会员注册成功 | 会员模型实例 `$user` | `User::register()` 事务提交前 |
| `user_login_after` | 会员/API 登录成功 | 会员模型实例 `$user` | `User::login()` 末尾 |
| `user_logout_after` | 会员登出成功 | 会员模型实例 `$user` | `User::logout()` 末尾 |
| `user_changepwd_after` | 会员修改密码成功 | 会员模型实例 `$user` | `User::changepwd()` 事务提交前 |
> 监听器 `handle($user)` 收到的 `$user` 均为 `ywxapp\model\User` 实例。
### 内容 / 文章(`app/api/controller/v1/Article.php`
| 事件名 | 触发时机 | 参数 | 代码位置 |
| --- | --- | --- | --- |
| `article_create_after` | 文章创建成功 | 文章模型实例 `$article` | `Article::create()` 成功后 |
| `article_update_after` | 文章更新成功 | 文章模型实例 `$article` | `Article::update()` 保存后 |
| `article_delete_after` | 文章删除成功 | 文章模型实例 `$article` | `Article::delete()` 删除后 |
### MQTT 消息(`addon/mqttbroker/service/Broker.php`
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
| --- | --- | --- | --- |
| `mqtt_message_published` | 客户端发布消息被 Broker 接受并分发 | `['topic'=>string, 'payload'=>string, 'qos'=>int, 'retain'=>bool, 'clientId'=>string]` | `Broker::handlePublish()`qos0/1)与 `Broker::handlePubRel()`qos2|
> 说明:`$SYS/` 开头的内部指标主题**不会**触发该事件(避免每秒指标造成噪声);
> qos2 消息在 PUBREL 确认后才真正分发,故事件在 `handlePubRel` 中触发,保证每一条消息仅触发一次。
### 全局异常(`ywxapp/handler/ExceptionHandle.php`
| 事件名 | 触发时机 | 参数 | 代码位置 |
| --- | --- | --- | --- |
| `app_exception` | 应用抛出异常 | `['exception'=>\Throwable, 'ignoreReport'=>bool]` | `ExceptionHandle::report()` |
| `app_exception_report` | 异常进入上报流程 | 同上 | `ExceptionHandle::report()` |
### 插件市场 / 订单(`app/api/controller/v1/Addon.php`
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
| --- | --- | --- | --- |
| `order_paid` | 插件购买订单支付完成(已签发授权 + 写入开发者分红账本) | `['order'=>订单模型, 'aid'=>int 插件id, 'uid'=>int 用户id]` | `Addon::completeOrder()` 末尾 |
> 插件可监听此钩子实现"发货 / 开通权益 / 发送通知"等购买后联动。
### 插件生命周期(`ywxapp/service/AddonService.php`
| 事件名 | 触发时机 | 参数(数组) | 代码位置 |
| --- | --- | --- | --- |
| `addon_install_after` | 插件安装成功(已写 info 状态、注入菜单、导入 SQL) | `['name'=>string 插件标识, 'info'=>array 插件信息]` | `AddonService::executeInstall()` 提交后 |
| `addon_uninstall_after` | 插件卸载完成(目录与数据表已清理) | `['name'=>string 插件标识]` | `AddonService::uninstall()` 末尾 |
| `addon_enable_after` | 插件被启用 | `['name'=>string 插件标识]` | `AddonService::enable()` 末尾 |
| `addon_disable_after` | 插件被禁用 | `['name'=>string 插件标识]` | `AddonService::disable()` 末尾 |
> 注意:与插件主类自身的 `install()/uninstall()/enable()/disable()` 生命周期方法(由 `callAddonHook` 调用)不同,
> 上述 `*_after` 是**全局事件**——任意插件均可监听以对"其它插件"的状态变化做出联动。
> 新增业务钩子点只需在对应位置调用 `event('your_event', $data)` 即可,
> 插件侧用同名事件名在 `info.php['events']['listen']` 中监听,无需任何注册代码。
---
## 4.2 辅助函数层(`ywxapp/helper.php`
由 `AppService::register()` 在框架启动时自动加载,全局可用:
| 函数 | 作用 |
| --- | --- |
| `addon_url($name, $url='', $vars=[])` | 生成插件访问地址 `/<name>/<url>`(修复了原先未定义的致命 bug) |
| `get_addon_instance($name)` | 获取插件主类实例 |
| `get_addon_info($name, $force=false)` | 读取插件元信息(优先缓存) |
| `get_addon_config($name, $force=false)` | 读取插件配置(优先缓存) |
| `get_appmarket_addon_list($onlyEnabled=false)` | 扫描插件目录,返回元信息列表 |
> 事件触发统一使用 ThinkPHP 自带的 `event('事件名', $params)` 助手,无需框架额外封装。
---
## 5. 开发者模式(本地边改边测)⭐
### 痛点
当你的插件源码**直接放在 `addon/<name>/`** 本地改代码时,框架的「压缩包安装」流程要求目标目录**不存在**才允许安装(`validateAddonInfo` 会抛 "Addon already exists")。结果是:目录在、代码能跑,但 `install.sql`(建表)和 `menu.json`(菜单)**从未执行过**,卡在中间态,无法测试。
### 解决方案:`addon:manage -a develop` 命令
```bash
php think addon:manage mqttbroker -a develop
```
它在**原地**执行标准安装步骤,**不需要先打包 zip**:
1. 置 `info.php` 的 `state = 1`(直接启用)
2. 调用 `install` 钩子
3. 清理旧菜单 → 重新注入 `menu.json`
4. 执行 `importsql()`(建表)
**幂等、可反复执行**:改了 `install.sql` 或 `menu.json` 后,再跑一次即可生效(`CREATE TABLE IF NOT EXISTS` + 菜单先清后建,不会报错)。
> 开发环境若需跳过远程授权校验:设置 `app_debug=1` 且 `ywxapp.unknownsources=1``AddonService::valid()` 会直接放行)。
---
## 6. 打包与发布上线
### 本地打包
通过**后台「插件管理 → 打包」按钮**(调用 `AddonService::package()`),生成:
```
addon/temp/mqttbroker-2.1.0.zip
```
打包内容 = 整个插件目录 + `public/static/addon/<name>/`(若存在)。注意:打包时 `static/` 会被迁出到 `public/static/addon/<name>/` 并删除插件目录内的 `static` 副本。
### 上传到服务器
1. 服务器后台「插件管理 → 离线安装」,上传该 zip。
2. 服务器侧 `local()` 流程:校验 → 解压到 `addon/<name>/` → `install` 钩子 → 注入菜单 → `importsql()`。
3. 安装后默认 `state=0`(禁用),在后台**启用**插件即可。
### 注意事项
- 离线安装要求服务器上 `addon/<name>` **不存在**;重装需先卸载。
- `install.sql` 白名单限制同上(仅 `CREATE TABLE` / `INSERT`)。
- 静态资源访问路径:`/static/addon/<name>/...`。
---
## 7. 完整开发流程(实例)
```bash
# 1. 脚手架(或手写目录结构)
php think addon:make mqttbroker -a build
# 2. 编写 info.php / Addon.php / install.sql / menu.json / config.php / route/app.php / 控制器 / 模型
# 3. 开发者模式:建表 + 注入菜单 + 启用(只需一次,之后改 SQL/菜单再跑)
php think addon:manage mqttbroker -a develop
# 4. 启动服务 / 调试
php think mqttbroker:start
# 5. 迭代:改业务代码即时生效;
# 改了 install.sql 或 menu.json → 再跑一次 addon:manage <name> -a develop
# 6. 打包
# 后台「插件管理 → 打包」→ 下载 mqttbroker-2.1.0.zip
# 7. 部署:服务器后台离线安装 → 启用
```
---
## 8. 常用命令速查
| 命令 | 作用 |
| --- | --- |
| `php think addon:manage <name> -a develop` | 开发者模式安装(建表+菜单+启用,免打包,可重跑) |
| `php think addon:make <name> -a build` | 生成插件脚手架(统一命令 `addon:make``-a` 指定动作) |
| `php think addon:make <name> -a clear` | 清理插件缓存/临时文件 |
| `php think addon:make <name> -a controller --name=<类>` | 生成控制器(另有 `--api`/`--plain` |
| `php think addon:make <name> -a model --name=<类>` | 生成模型 |
| `php think addon:make <name> -a <action> --name=<类>` | 其余生成器:`middleware`/`validate`/`event`/`listener`/`service`/`subscribe` |
| (后台按钮)打包 | 生成 `<name>-<version>.zip` |
| `php think mqttbroker:start` | 示例插件:启动 Broker(具体命令名随插件自定义) |
---
## 9. 常见坑
1. **install.sql 只允许 CREATE TABLE / INSERT**DROP / ALTER / DELETE 必须放 `Addon::uninstall()`。
2. **enable/disable 不触发插件钩子**,仅切换 `info.php` 的 `state`。
3. **表前缀**install.sql 用 `__PREFIX__` 或固定 `wxapp_`,需与数据库配置一致。
4. **路由控制器引用格式**`layer/Controller/action` 对应 `controller/<layer>/<Controller>.php`。
5. **卸载会 DROP 表且删除目录**,生产环境操作前务必备份。
6. **本地改了 install.sql 没生效**:忘了重跑 `addon:manage <name> -a develop`(它幂等,放心跑)。
7. **Windows 下多 Worker / WebSocket 仅 Linux 实网可验**:用 `DIRECTORY_SEPARATOR === '\\'` 做平台降级。
8. **事件/中间件/服务不要写进 `event.php` / `middleware.php` 文件**:插件体系通过 `info.php` 的 `events` / `middleware` / `services` 数组声明(boot 期由 `AppService` 加载),插件目录里**无需创建**这两个文件;命令行则放进 `command/` 并在 `config/console.php` 注册(不能省略)。
9. **`config.php` 是可选的,框架不会自动读它**:只有插件自建后台设置页去 `include` 它才生效(见 3.5)。没有可配置项就别建(`['status'=>true]` 这类占位无意义,会误导)。默认值别忘了与运行期代码里的默认值保持一致。