# 插件开发指南 ## 目录结构 ``` plugins/{vendor}.{name}/ ├── plugin.json # 插件清单(必填) ├── src/ │ ├── ServiceProvider.php # 插件入口(继承基类) │ ├── ... # 业务代码 │ └── Filament/ # 可选:后台页面/资源 ├── routes/web.php # 可选:前台路由(boot 时自动加载) ├── database/migrations/ # 可选:迁移(migrate 时自动加载) └── views/ # 可选:视图(命名空间 plugin.{vendor}.{name}) ``` ## plugin.json ```json { "title": "插件标题", "version": "1.0.0", "description": "插件描述", "author": "作者", "type": "core", "provider": "Plugins\\Vendor\\Name\\ServiceProvider", "requires": ["neatstudio.payment"], "filament_pages": ["Plugins\\Vendor\\Name\\Filament\\Pages\\SettingsPage"], "filament_resources": ["Plugins\\Vendor\\Name\\Filament\\Resources\\OrderResource"] } ``` | 字段 | 说明 | |------|------| | `provider` | 入口类 FQCN;缺省时自动推断 `src/ServiceProvider.php` | | `requires` | 依赖的其他插件(如会员依赖支付),按 `vendor.name` 引用 | | `filament_pages` / `filament_resources` | 注册到后台的页面/资源(由 `PluginPages` 汇总) | ## ServiceProvider ```php loadRoutes(__DIR__.'/../routes/web.php'); // 注册视图命名空间(可选) $this->loadViews(__DIR__.'/../views', 'plugin.vendor.name'); // 注册动作/过滤器 $manager->addAction('comment.created', function ($comment) { ... }, 10); $manager->addFilter('post.rendered', fn (string $html, $post) => $html, 10); } } ``` > 注意:插件入口类**不要**命名为 `PluginServiceProvider`(与基类短名冲突会导致 PHP 声明错误),统一用 `ServiceProvider`。 ## 钩子系统 ### addAction(hook, callback, priority) — 无返回值 | Hook | 参数 | 用途 | |------|------|------| | `comment.created` | `Comment` | 新评论创建(AI 审核在此监听) | | `payment.paid` | `Payment` | 支付成功(订阅激活/解锁在此监听) | ### addFilter(hook, callback, priority) — 返回值传给下一个过滤器 | Hook | 签名 | 用途 | |------|------|------| | `post.rendered` | `(string $html, Post $post): string` | 文章渲染后处理(付费内容过滤) | | `seo.structured_data` | `(array $data): array` | 扩展 JSON-LD 结构化数据 | | `payment.gateway` | `(array $gateways): array` | 注册支付渠道 | ## 迁移 插件迁移放在 `database/migrations/`,`app/Providers/AppServiceProvider` 启动时自动注册,`php artisan migrate` 会一并执行(无需手动 --path)。 ## 打包与安装 - 将插件目录打成 ZIP(根目录含 plugin.json) - 后台「插件管理」→ 上传 ZIP 安装,或配置远程市场 `MARKET_URL` - 内置插件(config/plugins.php enabled 列表)不可卸载,只能停用 ## 异步任务(配合 Workerman) 实现 `App\Blog\Jobs\AiJob` 接口并在 Workerman 常驻进程执行: ```php use App\Blog\Jobs\AiJob; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; class MyAiJob implements AiJob, ShouldQueue { use Queueable; public function __construct(public int $id) {} public function handle(\App\Blog\Services\LlmClient $llm): void { // LlmClient 由容器自动注入 } } ``` 入队:`dispatch(new MyAiJob($id))->onQueue('ai')`(Workerman 默认消费 `default,ai` 队列)。