Files
Xboard/docs/en/development/plugin-development-guide.md
T
2026-02-22 04:13:42 +08:00

16 KiB
Raw Blame History

XBoard 插件开发指南

插件结构

每个插件都是一个独立目录,推荐结构如下:

plugins/
└── YourPlugin/               # 插件目录(PascalCase 命名)
    ├── Plugin.php            # 插件主类(必需)
    ├── config.json           # 插件配置(必需)
    ├── routes/
    │   └── api.php           # API 路由
    ├── Controllers/          # 控制器目录
    │   └── YourController.php
    ├── Commands/             # Artisan 命令目录
    │   └── YourCommand.php
    └── README.md             # 文档说明

快速开始

1. 创建配置文件 config.json

{
    "name": "My Plugin",
    "code": "my_plugin", // Corresponds to plugin directory (lowercase + underscore)
    "version": "1.0.0",
    "description": "Plugin functionality description",
    "author": "Author Name",
    "require": {
        "xboard": ">=1.0.0" // Version not fully implemented yet
    },
    "config": {
        "api_key": {
            "type": "string",
            "default": "",
            "label": "API Key",
            "description": "API Key"
        },
        "timeout": {
            "type": "number",
            "default": 300,
            "label": "Timeout (seconds)",
            "description": "Timeout in seconds"
        }
    }
}

2. 创建插件主类 Plugin.php

<?php

namespace Plugin\YourPlugin;

use App\Services\Plugin\AbstractPlugin;

class Plugin extends AbstractPlugin
{
    /**
     * Called when plugin starts
     */
    public function boot(): void
    {
        // Register frontend configuration hook
        $this->filter('guest_comm_config', function ($config) {
            $config['my_plugin_enable'] = true;
            $config['my_plugin_setting'] = $this->getConfig('api_key', '');
            return $config;
        });
    }
}

3. 创建控制器

推荐做法:继承 PluginController

<?php

namespace Plugin\YourPlugin\Controllers;

use App\Http\Controllers\PluginController;
use Illuminate\Http\Request;

class YourController extends PluginController
{
    public function handle(Request $request)
    {
        // Get plugin configuration
        $apiKey = $this->getConfig('api_key');
        $timeout = $this->getConfig('timeout', 300);

        // Your business logic...

        return $this->success(['message' => 'Success']);
    }
}

4. 创建路由 routes/api.php

<?php

use Illuminate\Support\Facades\Route;
use Plugin\YourPlugin\Controllers\YourController;

Route::group([
    'prefix' => 'api/v1/your-plugin'
], function () {
    Route::post('/handle', [YourController::class, 'handle']);
});

配置访问

在控制器中可直接访问插件配置:

// Get single configuration
$value = $this->getConfig('key', 'default_value');

// Get all configurations
$allConfig = $this->getConfig();

// Check if plugin is enabled
$enabled = $this->isPluginEnabled();

Hook 系统

常用 Hook(建议优先关注)

XBoard 在多个关键业务节点提供了内置 hook。插件可通过 filterlisten 扩展行为。

Hook 名称 类型 常见参数 说明
user.register.before action Request 用户注册前
user.register.after action User 用户注册后
user.login.after action User 用户登录后
user.password.reset.after action User 密码重置后
order.cancel.before action Order 订单取消前
order.cancel.after action Order 订单取消后
payment.notify.before action method, uuid, request 支付回调校验前
payment.notify.verified action array 支付回调校验成功
payment.notify.failed action method, uuid, request 支付回调校验失败
traffic.reset.after action User 流量重置后
ticket.create.after action Ticket 工单创建后
ticket.reply.user.after action Ticket 用户回复工单后
ticket.close.after action Ticket 工单关闭后

hook 能力会持续扩展。可结合本文与 php artisan hook:list 获取最新支持列表。

Filter Hook

用于修改数据:

// In Plugin.php boot() method
$this->filter('guest_comm_config', function ($config) {
    // Add configuration for frontend
    $config['my_setting'] = $this->getConfig('setting');
    return $config;
});

Action Hook

用于执行动作:

$this->listen('user.created', function ($user) {
    // Operations after user creation
    $this->doSomething($user);
});

实战示例:Telegram 登录插件

下面使用 TelegramLogin 插件展示完整实现思路。

插件主类

<?php

namespace Plugin\TelegramLogin;

use App\Services\Plugin\AbstractPlugin;

class Plugin extends AbstractPlugin
{
    public function boot(): void
    {
        $this->filter('guest_comm_config', function ($config) {
            $config['telegram_login_enable'] = true;
            $config['telegram_login_domain'] = $this->getConfig('domain', '');
            $config['telegram_bot_username'] = $this->getConfig('bot_username', '');
            return $config;
        });
    }
}

控制器(继承 PluginController

class TelegramLoginController extends PluginController
{
    public function telegramLogin(Request $request)
    {
        // Check plugin status
        if ($error = $this->beforePluginAction()) {
            return $error[1];
        }

        // Get configuration
        $botToken = $this->getConfig('bot_token');
        $timeout = $this->getConfig('auth_timeout', 300);

        // Business logic...

        return $this->success($result);
    }
}

插件定时任务(Scheduler

插件可在主类中实现 schedule(Schedule $schedule) 方法来注册定时任务。

use Illuminate\Console\Scheduling\Schedule;

class Plugin extends AbstractPlugin
{
    public function schedule(Schedule $schedule): void
    {
        // Execute every hour
        $schedule->call(function () {
            // Your scheduled task logic
            \Log::info('Plugin scheduled task executed');
        })->hourly();
    }
}
  • 只需在 Plugin.php 中实现 schedule()
  • 主程序会自动调度所有插件任务。
  • 支持 Laravel Scheduler 的全部能力。

插件 Artisan 命令

插件启用后,会自动加载 Commands/ 目录中的命令类。

命令目录结构

plugins/YourPlugin/
├── Commands/
│   ├── TestCommand.php      # 测试命令
│   ├── BackupCommand.php    # 备份命令
│   └── CleanupCommand.php   # 清理命令

创建命令类

示例:TestCommand.php

<?php

namespace Plugin\YourPlugin\Commands;

use Illuminate\Console\Command;

class TestCommand extends Command
{
    protected $signature = 'your-plugin:test {action=ping} {--message=Hello}';
    protected $description = 'Test plugin functionality';

    public function handle(): int
    {
        $action = $this->argument('action');
        $message = $this->option('message');

        try {
            return match ($action) {
                'ping' => $this->ping($message),
                'info' => $this->showInfo(),
                default => $this->showHelp()
            };
        } catch (\Exception $e) {
            $this->error('Operation failed: ' . $e->getMessage());
            return 1;
        }
    }

    protected function ping(string $message): int
    {
        $this->info("✅ {$message}");
        return 0;
    }

    protected function showInfo(): int
    {
        $this->info('Plugin Information:');
        $this->table(
            ['Property', 'Value'],
            [
                ['Plugin Name', 'YourPlugin'],
                ['Version', '1.0.0'],
                ['Status', 'Enabled'],
            ]
        );
        return 0;
    }

    protected function showHelp(): int
    {
        $this->info('Usage:');
        $this->line('  php artisan your-plugin:test ping --message="Hello"  # Test');
        $this->line('  php artisan your-plugin:test info                    # Show info');
        return 0;
    }
}

自动注册规则

  • 插件启用时,自动注册 Commands/ 目录下全部命令类。
  • 命令命名空间自动按 Plugin\YourPlugin\Commands 解析。
  • 支持 Laravel 命令全部能力(参数、选项、交互等)。

使用示例

# Test command
php artisan your-plugin:test ping --message="Hello World"

# Show information
php artisan your-plugin:test info

# View help
php artisan your-plugin:test --help

命令开发建议

  1. 命令命名:使用 plugin-name:action,例如 telegram:test
  2. 异常处理:主流程建议统一 try-catch
  3. 返回码规范:成功返回 0,失败返回 1
  4. 可用性:提供清晰的帮助信息和错误提示。
  5. 类型声明:建议使用 PHP 8.2 类型声明。

开发工具与基础能力

控制器基类选择

方案一:继承 PluginController(推荐)

  • 自动配置读取:$this->getConfig()
  • 自动状态检查:$this->beforePluginAction()
  • 统一返回与错误处理

方案二:使用 HasPluginConfig Trait

use App\Http\Controllers\Controller;
use App\Traits\HasPluginConfig;

class YourController extends Controller
{
    use HasPluginConfig;

    public function handle()
    {
        $config = $this->getConfig('key');
        // ...
    }
}

配置类型

支持以下配置类型:

  • string:字符串
  • number:数字
  • boolean:布尔值
  • json:数组
  • yaml

通用最佳实践

1. 保持主类简洁

  • Plugin.php 只保留启动相关逻辑。
  • 主要负责注册 hooks、路由、调度等入口。
  • 复杂业务放到 Controller 或 Service。

2. 统一配置管理

  • 所有配置都在 config.json 明确定义。
  • 通过 $this->getConfig() 读取。
  • 所有项建议提供默认值。

3. 路由设计清晰

  • 使用语义化前缀。
  • API 路由放在 routes/api.php
  • Web 路由放在 routes/web.php

4. 完善错误处理

public function handle(Request $request)
{
    // Check plugin status
    if ($error = $this->beforePluginAction()) {
        return $error[1];
    }

    try {
        // Business logic
        return $this->success($result);
    } catch (\Exception $e) {
        return $this->fail([500, $e->getMessage()]);
    }
}

调试技巧

1. 日志记录

\Log::info('Plugin operation', ['data' => $data]);
\Log::error('Plugin error', ['error' => $e->getMessage()]);

2. 配置校验

// Check required configuration
if (!$this->getConfig('required_key')) {
    return $this->fail([400, 'Missing configuration']);
}

3. 开发模式

if (config('app.debug')) {
    // Detailed debug information for development environment
}

插件生命周期

  1. 安装:校验配置并注册到数据库。
  2. 启用:加载插件并注册 hooks、路由、命令、调度。
  3. 运行:处理请求并执行业务逻辑。

阶段总结

结合 TelegramLogin 插件的实践经验:

  • 简洁:主类短小、职责单一。
  • 实用:基于 PluginController,开发效率高。
  • 可维护:目录结构清晰,模式统一。
  • 可扩展:通过 hooks 可快速扩展业务。

插件 Artisan 命令完整指南

功能亮点

  • 自动注册:插件启用时自动注册 Commands/ 下命令。
  • 命名空间隔离:各插件命令互不冲突。
  • 类型安全:支持 PHP 8.2 类型声明。
  • 异常处理:可统一错误输出与退出码。
  • 配置集成:命令中可读取插件配置。
  • 交互支持:支持输入、确认、选择等交互流程。

真实案例

1. Telegram 插件命令

# Test Bot connection
php artisan telegram:test ping

# Send message
php artisan telegram:test send --message="Hello World"

# Get Bot information
php artisan telegram:test info

2. TelegramExtra 插件命令

# Show all statistics
php artisan telegram-extra:stats all

# User statistics
php artisan telegram-extra:stats users

# JSON format output
php artisan telegram-extra:stats users --format=json

3. Example 插件命令

# Basic usage
php artisan example:hello

# With arguments and options
php artisan example:hello Bear --message="Welcome!"

命令开发规范

1. 命名约定

// ✅ Recommended: Use plugin name as prefix
protected $signature = 'telegram:test {action}';
protected $signature = 'telegram-extra:stats {type}';
protected $signature = 'example:hello {name}';

// ❌ Avoid: Use generic names
protected $signature = 'test {action}';
protected $signature = 'stats {type}';

2. 异常处理模式

public function handle(): int
{
    try {
        // Main logic
        return $this->executeAction();
    } catch (\Exception $e) {
        $this->error('Operation failed: ' . $e->getMessage());
        return 1;
    }
}

3. 交互能力

// Get user input
$chatId = $this->ask('Please enter chat ID');

// Confirm operation
if (!$this->confirm('Are you sure you want to execute this operation?')) {
    $this->info('Operation cancelled');
    return 0;
}

// Choose operation
$action = $this->choice('Choose operation', ['ping', 'send', 'info']);

4. 配置访问

// Access plugin configuration in commands
protected function getConfig(string $key, $default = null): mixed
{
    // Get plugin instance through PluginManager
    $plugin = app(\App\Services\Plugin\PluginManager::class)
        ->getEnabledPlugins()['example_plugin'] ?? null;

    return $plugin ? $plugin->getConfig($key, $default) : $default;
}

进阶用法

1. 多命令插件

// One plugin can have multiple commands
plugins/YourPlugin/Commands/
├── BackupCommand.php      # Backup command
├── CleanupCommand.php     # Cleanup command
├── StatsCommand.php       # Statistics command
└── TestCommand.php        # Test command

2. 命令间通信

// Share data between commands through cache or database
Cache::put('plugin:backup:progress', $progress, 3600);
$progress = Cache::get('plugin:backup:progress');

3. 与定时任务联动

// Call commands in plugin's schedule method
public function schedule(Schedule $schedule): void
{
    $schedule->command('your-plugin:backup')->daily();
    $schedule->command('your-plugin:cleanup')->weekly();
}

命令调试建议

1. 命令测试

# View command help
php artisan your-plugin:command --help

# Verbose output
php artisan your-plugin:command --verbose

# Debug mode
php artisan your-plugin:command --debug

2. 命令日志

// Log in commands
Log::info('Plugin command executed', [
    'command' => $this->signature,
    'arguments' => $this->arguments(),
    'options' => $this->options()
]);

3. 性能监控

// Record command execution time
$startTime = microtime(true);
// ... execution logic
$endTime = microtime(true);
$this->info("Execution time: " . round(($endTime - $startTime) * 1000, 2) . "ms");

常见问题

Q: 命令没有出现在列表里?

A: 检查插件是否启用,并确认 Commands/ 目录存在且命令类有效。

Q: 命令执行失败?

A: 检查命名空间是否正确,且类继承了 Illuminate\Console\Command

Q: 如何在命令中读取插件配置?

A: 通过 PluginManager 获取插件实例,再调用 getConfig()

Q: 命令能否调用其他命令?

A: 可以,使用 Artisan::call()

Artisan::call('other-plugin:command', ['arg' => 'value']);

总结

插件命令系统为 XBoard 提供了强扩展能力:

  • 开发效率:快速构建运维与管理命令
  • 运维便利:支持自动化日常操作
  • 监控能力:便于查看系统运行状态
  • 调试支持:可快速定位与排查问题

合理使用插件命令,可以显著提升系统可维护性和使用体验。