- plugins.md:requires 依赖强制校验行为、filament.post_form/post_table 后台注入(追加写法 + 示例)、Payment payable 商品化约定(payableLabel/payableUrl)、paywall 视图覆盖机制 - themes.md:新增付费墙覆盖章节(membership.paywall / paid-teaser,主题同名文件可覆盖) - README:插件段落补充依赖校验、后台注入钩子、订单商品化说明
7.4 KiB
插件开发指南
目录结构
plugins/{vendor}.{name}/
├── plugin.json # 插件清单(必填)
├── src/
│ ├── ServiceProvider.php # 插件入口(继承基类)
│ ├── ... # 业务代码
│ └── Filament/ # 可选:后台页面/资源
├── routes/web.php # 可选:前台路由(boot 时自动加载)
├── database/migrations/ # 可选:迁移(migrate 时自动加载)
└── views/ # 可选:视图(命名空间 plugin.{vendor}.{name})
plugin.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 汇总,统一归入「插件」导航分组) |
依赖管理(requires 强制校验)
enable():依赖未安装或未启用 → 拒绝启用并提示(后台弹错误通知)disable()/uninstall():有已启用的插件依赖它 → 拒绝操作,需先停用上层插件- 应用启动时依赖未满足的插件跳过 boot,避免运行时缺底层能力
- 后台插件卡片展示依赖链状态(徽章:✓ 已启用 / ✗ 未安装 / ✗ 未启用),依赖不满足的插件无法启用
插件分层示例:payment(支付)→ membership(会员,requires payment)→ 你的业务插件(requires membership 或 payment)。上层插件可放心假设底层能力存在。
ServiceProvider
<?php
declare(strict_types=1);
namespace Plugins\Vendor\Name;
use App\Blog\Support\PluginManager;
use App\Blog\Support\PluginServiceProvider;
class ServiceProvider extends PluginServiceProvider
{
protected function boot(PluginManager $manager): void
{
// 加载前台路由(可选)
$this->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 |
支付成功(按 $payment->payable 分发:订阅激活/解锁) |
filament.post_form |
Schema |
文章编辑表单追加字段(会员付费设置在此注入) |
filament.post_table |
Table |
文章列表追加列/操作/过滤 |
addFilter(hook, callback, priority) — 返回值传给下一个过滤器
| Hook | 签名 | 用途 |
|---|---|---|
post.rendered |
(string $html, Post $post): string |
文章渲染后处理(付费内容过滤、paywall) |
seo.structured_data |
(array $data): array |
扩展 JSON-LD 结构化数据 |
payment.gateway |
(array $gateways): array |
注册支付渠道 |
后台注入(Filament 表单 / 表格)
核心资源的表单/表格会触发 filament.post_form / filament.post_table 两个钩子,插件可追加自己的字段与列,停用插件后字段自动消失,核心零侵入。
注意:
Schema::components()与Table::columns()都是整体替换语义,追加要这样写:
- 表单:
$schema->components([...$schema->getComponents(), Section::make(...)])- 表格:
$table->pushColumns([...])
示例(给文章加「付费设置」):
$manager->addAction('filament.post_form', function (Schema $schema) {
$schema->components([
...$schema->getComponents(),
Section::make('付费设置')->columns(3)->collapsible()->schema([
TextInput::make('meta.price')->label('单篇价格(分)')->numeric()->default(0),
Toggle::make('meta.members_only')->label('会员专享'),
]),
]);
});
$manager->addAction('filament.post_table', function (Table $table) {
$table->pushColumns([
TextColumn::make('meta.price')->label('单篇价格'),
IconColumn::make('meta.members_only')->label('会员')->boolean(),
]);
});
字段用点号路径(meta.price)直接映射到 Post 的 meta JSON。核心保存时会把表单里的 meta 键与已有 meta 合并,不会覆盖其他插件写入的键(如解锁用户)。
订单商品化(可购买实体)
Payment 用多态关联指向被购买的实体(payable_type / payable_id)。支付成功后的 payment.paid 钩子按 $payment->payable 分发(instanceof 判断),不要再用字符串解析业务类型。
订单后台的「商品」列与跳转链接由实体上的两个约定方法生成:
public function payableLabel(): string { return '文章:'.$this->title; }
public function payableUrl(): ?string { return route('filament.admin.resources.posts.edit', $this); }
已实现:App\Models\Post(单篇解锁)、MembershipPlan(会员套餐)。新实体(皮肤授权、插件授权、打赏)实现同样两个方法即自动接入订单展示,支付插件零改动。
付费内容前台展示(paywall)
会员插件在 post.rendered 过滤器里处理两种付费形态:
- 整篇锁定(
meta.members_only)→ 渲染membership.paywall(封面 + 标题 + 价格 + 单篇解锁/开通会员按钮) [paid]付费块 → 替换为membership.paid-teaser(内嵌解锁按钮的提示块)
缺省视图在 resources/views/membership/,主题可覆盖:themes/{name}/views/membership/paywall.blade.php。视图内可用 $post,按钮沿用 .btn 类,.paywall / .paid-teaser 样式两套内置主题已带。
迁移
插件迁移放在 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 常驻进程执行:
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 队列)。