Skip to content

插件系统

插件系统提供灵活的扩展机制,允许开发者在不修改核心代码的情况下扩展框架功能。支持 CMS 和 API 双模式运行。

快速开始

创建第一个插件

app/Plugin/ 目录下创建插件:

app/Plugin/
└── HelloWorld/
    ├── Index.php       # 主入口文件
    └── package.json    # 元数据(可选)

Index.php:

php
<?php
/**
 * Name: HelloWorld
 * Description: Hello World 插件
 * Mode: auto
 * Version: 1.0.0
 * Author: Your Name
 * URI: https://example.com
 */
if (!defined('ANON_ALLOWED_ACCESS')) exit;

class Anon_Plugin_HelloWorld
{
    public static function init()
    {
        // 注册路由
        Anon::route('/hello', function() {
            Anon::success(['message' => 'Hello World!']);
        }, [
            'method' => 'GET',
            'token' => false,
        ]);
    }
}

启用插件

app/useApp.php 中配置:

php
'plugins' => [
    'enabled' => true,
    'active' => [
        'HelloWorld',  // 激活的插件列表
    ],
],

插件结构

目录结构

app/Plugin/
└── PluginName/
    ├── Index.php           # 主入口(必需)
    ├── package.json        # 元数据(可选)
    ├── app/
    │   ├── mode/
    │   │   ├── api.php     # API 模式业务逻辑
    │   │   └── cms.php     # CMS 模式业务逻辑
    │   ├── pages.php       # 管理页面配置
    │   └── setup.php       # 设置项配置
    └── assets/             # 静态资源
        ├── css/
        └── js/

主入口文件

基本结构

php
<?php
/**
 * Name: 插件名称
 * Description: 插件描述
 * Mode: auto
 * Version: 1.0.0
 * Author: 作者名
 * URI: 主页链接
 */
if (!defined('ANON_ALLOWED_ACCESS')) exit;

class Anon_Plugin_PluginName
{
    /**
     * 初始化方法(必需)
     */
    public static function init()
    {
        // 初始化逻辑
    }
    
    /**
     * 激活时调用(可选)
     */
    public static function activate()
    {
        // 激活逻辑
    }
    
    /**
     * 停用时调用(可选)
     */
    public static function deactivate()
    {
        // 停用逻辑
    }
}

元数据格式

package.json 方式(推荐)

json
{
  "name": "PluginName",
  "description": "插件描述",
  "version": "1.0.0",
  "author": "作者名",
  "url": "https://example.com",
  "anon": {
    "mode": "auto"
  }
}

文件注释方式

php
/**
 * Name: PluginName
 * Description: 插件描述
 * Mode: auto
 * Version: 1.0.0
 * Author: 作者名
 * URI: https://example.com
 */

运行模式

Mode 配置说明

模式说明加载时机
api仅在 API 模式下加载ANON_APP_MODE === 'api'
cms仅在 CMS 模式下加载ANON_APP_MODE === 'cms'
auto在所有模式下都加载总是加载

判断当前模式

php
class Anon_Plugin_MyPlugin
{
    public static function init()
    {
        if (Anon_System_Plugin::isApiMode()) {
            // API 模式下的初始化
            self::initApi();
        } elseif (Anon_System_Plugin::isCmsMode()) {
            // CMS 模式下的初始化
            self::initCms();
        }
    }
    
    private static function initApi()
    {
        // API 路由注册
        Anon::route('/api/plugin/data', [...]);
    }
    
    private static function initCms()
    {
        // CMS 管理页面、钩子等
        Anon::filter('admin_navbar_sidebar', [...]);
    }
}

自动加载模式文件

使用高级结构自动加载:

php
<?php
class Anon_Plugin_MyPlugin extends Anon_Plugin_Base
{
    // 基类会自动加载 app/mode/ 下的对应文件
}

app/mode/api.php:

php
<?php
$plugin = $this;

Anon::route('/api/data', function() use ($plugin) {
    // API 业务逻辑
    return ['data' => 'API response'];
});

app/mode/cms.php:

php
<?php
$plugin = $this;

// CMS 管理页面
Anon::filter('admin_navbar_sidebar', function($items) use ($plugin) {
    $items[] = [
        'key' => 'my-plugin',
        'label' => '我的插件',
        'icon' => 'StarOutlined',
        'children' => [
            [
                'key' => '/pages?plugin=my-plugin:settings',
                'label' => '设置',
            ]
        ]
    ];
    return $items;
});

中间件扩展

插件可以通过 registerMiddleware() 方法注册中间件:

基本用法

php
class Anon_Plugin_MyPlugin
{
    public static function registerMiddleware()
    {
        Anon_System_Extension::register(
            'myMiddleware',
            [self::class, 'handle'],
            10,
            ['hooks' => ['request.start']]
        );
    }
    
    public static function handle($data)
    {
        // 中间件逻辑
        header('X-Custom-Header: MyPlugin');
        return $data;
    }
}

注册多个中间件

php
public static function registerMiddleware()
{
    // CORS 中间件(高优先级)
    Anon_System_Extension::register(
        'cors',
        [CorsMiddleware::class, 'handle'],
        1,
        ['hooks' => ['request.start']]
    );
    
    // 日志中间件(低优先级)
    Anon_System_Extension::register(
        'logger',
        [LoggerMiddleware::class, 'handle'],
        100,
        ['hooks' => ['request.end']]
    );
}

详见:中间件扩展系统

Widget 组件

插件可以注册可复用的 UI 组件:

注册 Widget

php
class Anon_Plugin_SidebarWidget
{
    public static function init()
    {
        $widget = Anon_System_Widget::getInstance();
        
        $widget->register('my_widget', '我的组件', function($args) {
            return '<div class="widget">内容</div>';
        });
    }
}

详见:Widget 组件系统

钩子系统

添加动作钩子

php
Anon_System_Hook::add_action('user_login', function($userId) {
    // 用户登录后的操作
    Anon_Debug::info("用户 {$userId} 登录");
});

添加过滤器钩子

php
Anon_System_Hook::add_filter('post_content', function($content) {
    // 修改文章内容
    return $content . '<p>附加内容</p>';
});

执行钩子

php
// 执行动作
Anon_System_Hook::do_action('custom_action', $param1, $param2);

// 应用过滤器
$value = Anon_System_Hook::apply_filters('custom_filter', $defaultValue);

路由注册

基本路由

php
Anon::route('/api/plugin/endpoint', function() {
    Anon::success(['data' => 'response']);
}, [
    'method' => 'POST',
    'requireLogin' => true,
    'token' => true,
]);

带参数的路由

php
Anon::route('/api/post/{id}', function($id) {
    $db = Anon_Database::getInstance();
    $post = $db->db('posts')->where('id', '=', $id)->first();
    
    if ($post) {
        Anon::success($post);
    } else {
        Anon::error('文章不存在', 404);
    }
}, [
    'method' => 'GET',
]);

路由元数据

php
Anon::route('/secure/data', $handler, [
    'header' => true,              // 设置响应头
    'requireLogin' => true,        // 需要登录
    'method' => ['POST', 'PUT'],   // 允许的 HTTP 方法
    'token' => true,               // 需要 Token 验证
    'cache' => [                   // 缓存配置
        'enabled' => true,
        'time' => 3600,
    ],
]);

插件设置

定义设置 Schema

php
class Anon_Plugin_MyPlugin
{
    public static function getSettingsSchema(): array
    {
        return [
            'api_key' => [
                'type' => 'text',
                'label' => 'API 密钥',
                'default' => '',
                'required' => true,
            ],
            'enable_feature' => [
                'type' => 'checkbox',
                'label' => '启用功能',
                'default' => true,
            ],
            'max_items' => [
                'type' => 'number',
                'label' => '最大数量',
                'default' => 10,
                'min' => 1,
                'max' => 100,
            ],
        ];
    }
}

读取设置

php
// 从 options 表读取
$options = Anon_System_Plugin::getPluginOptions('my-plugin');
$apiKey = $options['api_key'] ?? '';

// 或使用代理(支持优先级)
class Anon_Plugin_MyPlugin extends Anon_Plugin_Base
{
    public function init()
    {
        $plugin = $this;
        $apiKey = $plugin->options()->get('api_key', '');
    }
}

保存设置

php
// 直接保存
Anon_Cms_Options::set('plugin:my-plugin', [
    'api_key' => 'new-key',
    'enable_feature' => true,
]);

// 或使用代理
$plugin->options()->set('api_key', 'new-key');

自定义管理页面

页面配置

app/pages.php 中定义:

php
<?php
return [
    'settings' => [
        'title' => '插件设置',
        'content' => <<<HTML
<div id="my-plugin-settings">
    <el-form :model="form" label-width="120px">
        <el-form-item label="API 密钥">
            <el-input v-model="form.api_key"></el-input>
        </el-form-item>
        <el-form-item>
            <el-button type="primary" @click="save">保存</el-button>
        </el-form-item>
    </el-form>
</div>
<script>
window.AnonPluginPage = {
    data() {
        return {
            form: {
                api_key: ''
            }
        };
    },
    methods: {
        save() {
            // 保存逻辑
        }
    }
};
</script>
HTML,
        'handler' => function($action, $data) {
            if ($action === 'save') {
                // 后端处理逻辑
                return ['success' => true];
            }
            return null;
        }
    ]
];

访问页面

/#/pages?plugin=plugin-slug:page-slug

例如:/#/pages?plugin=my-plugin:settings

管理后台菜单

添加顶级菜单

php
Anon::filter('admin_navbar_sidebar', function($items) {
    $items[] = [
        'key' => 'my-plugin',
        'icon' => 'StarOutlined',
        'label' => '我的插件',
        'children' => [
            [
                'key' => '/pages?plugin=my-plugin:settings',
                'label' => '设置',
            ],
            [
                'key' => '/pages?plugin=my-plugin:data',
                'label' => '数据管理',
            ],
        ],
    ];
    return $items;
});

挂载到现有菜单

php
Anon::filter('admin_navbar_sidebar', function($items) {
    Anon_Cms_Admin_UI_Navbar::mount($items, 'manage', [
        'key' => '/pages?plugin=my-plugin:config',
        'label' => '插件配置',
        'icon' => 'SettingOutlined',
    ]);
    return $items;
});

数据库操作

创建表

php
class Anon_Plugin_MyPlugin
{
    public static function activate()
    {
        $db = Anon_Database::getInstance();
        
        if (!$db->tableExists('plugin_my_table')) {
            $db->createTable('plugin_my_table', [
                'id' => [
                    'type' => 'INT',
                    'null' => false,
                    'primary' => true,
                    'key' => 'PRI',
                    'auto_increment' => true,
                ],
                'name' => [
                    'type' => 'VARCHAR(255)',
                    'null' => false,
                ],
                'created_at' => [
                    'type' => 'DATETIME',
                    'null' => false,
                ],
            ]);
        }
    }
}

CRUD 操作

php
$db = Anon_Database::getInstance();

// 插入
$id = $db->db('plugin_my_table')->insert([
    'name' => '测试数据',
    'created_at' => date('Y-m-d H:i:s'),
]);

// 查询
$data = $db->db('plugin_my_table')
    ->where('id', '=', $id)
    ->first();

// 更新
$db->db('plugin_my_table')
    ->where('id', '=', $id)
    ->update(['name' => '新名称']);

// 删除
$db->db('plugin_my_table')
    ->where('id', '=', $id)
    ->delete();

CLI 命令

插件可以注册命令行工具:

php
class Anon_Plugin_MyPlugin
{
    public static function init()
    {
        Anon_System_Console::command('my-plugin:task', function($args) {
            Anon_System_Console::info('执行任务...');
            
            // 任务逻辑
            
            Anon_System_Console::success('任务完成');
            return 0;
        }, '我的插件任务');
    }
}

详见:CLI 命令系统

插件生命周期

1. 扫描阶段

系统扫描 app/Plugin/ 目录,查找所有插件。

2. 加载阶段

  • 读取插件元数据
  • 检查激活状态
  • 加载主入口文件

3. 初始化阶段

  • 调用 init() 方法
  • 注册路由、钩子、中间件
  • 执行初始化逻辑

4. 激活/停用

  • 激活:调用 activate() 方法
  • 停用:调用 deactivate() 方法

5. 重新加载

php
// 重新加载单个插件
Anon_System_Plugin::reloadPlugin('my-plugin');

// 清除扫描缓存
Anon_System_Plugin::clearScanCache();

插件系统钩子

框架为插件系统提供的钩子:

php
// 插件系统初始化前
Anon_System_Hook::add_action('plugin_system_before_init', function() {
    // ...
});

// 插件系统初始化后
Anon_System_Hook::add_action('plugin_system_after_init', function($loadedPlugins) {
    // ...
});

// 插件扫描前
Anon_System_Hook::add_action('plugin_before_scan', function() {
    // ...
});

// 插件扫描后
Anon_System_Hook::add_action('plugin_after_scan', function() {
    // ...
});

// 插件加载前
Anon_System_Hook::add_action('plugin_before_load', function($slug, $meta) {
    // ...
});

// 插件加载后
Anon_System_Hook::add_action('plugin_after_load', function($slug, $meta) {
    // ...
});

// 插件加载错误
Anon_System_Hook::add_action('plugin_load_error', function($slug, $e) {
    // ...
});

最佳实践

1. 命名规范

php
// ✅ 正确:清晰的命名
class Anon_Plugin_UserAnalytics
{
    public static function init() { }
}

// ❌ 错误:模糊的命名
class Anon_Plugin_UA
{
    public static function init() { }
}

2. 错误处理

php
public static function init()
{
    try {
        // 初始化逻辑
    } catch (Throwable $e) {
        Anon_Debug::error('Plugin init failed', [
            'plugin' => self::class,
            'error' => $e->getMessage(),
        ]);
    }
}

3. 性能优化

php
// ✅ 正确:使用缓存
public static function getData()
{
    $cached = Anon_Cache::get('plugin_data');
    if ($cached !== null) {
        return $cached;
    }
    
    $data = self::fetchData(); // 耗时操作
    Anon_Cache::set('plugin_data', $data, 3600);
    
    return $data;
}

4. 安全检查

php
// 验证用户输入
$data = Anon_Http_Request::validate([
    'username' => '用户名不能为空',
    'email' => '邮箱格式不正确',
]);

// 权限检查
$user = Anon_Http_Request::requireAuth();
if (!$user['is_admin']) {
    Anon::error('无权访问', 403);
}

相关文档

Released under the MIT License.