Files
laralog/docs/plugins.md
T
ak 4d63c0c683 docs: 插件/主题开发文档补全新能力
- plugins.md:requires 依赖强制校验行为、filament.post_form/post_table 后台注入(追加写法 + 示例)、Payment payable 商品化约定(payableLabel/payableUrl)、paywall 视图覆盖机制
- themes.md:新增付费墙覆盖章节(membership.paywall / paid-teaser,主题同名文件可覆盖)
- README:插件段落补充依赖校验、后台注入钩子、订单商品化说明
2026-08-12 01:23:37 +08:00

7.4 KiB
Raw Blame History

插件开发指南

目录结构

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)直接映射到 Postmeta 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 队列)。