🔌 插件注册
ThinkAdmin 的插件注册基于 Composer 与 ThinkPHP 服务机制。插件在 composer.json 中声明服务类,执行发布流程后服务类会写入 vendor/services.php,再由 ThinkPHP 在运行时加载并执行 register() / boot()。
📚 基础概念
🤔 基本介绍
插件注册主要解决三件事:让插件类可以自动加载,让插件服务参与 ThinkPHP 启动流程,让插件中心能够读取插件编码、路径、包名和菜单定义。
// 1. 插件在 composer.json 中声明 extra.think.services
// 2. php think xadmin:publish 汇总服务到 vendor/services.php
// 3. ThinkPHP 加载服务类并执行 register()/boot()
// 4. 继承 think\admin\Plugin 的服务类会记录插件元信息,并要求提供静态 menu()🎯 核心功能
功能1:声明服务类
extra.think.services 是 ThinkPHP 服务声明。它可以指向普通 think\Service 子类,也可以指向继承 think\admin\Plugin 的插件服务类。
{
"extra": {
"think": {
"services": [
"plugin\\account\\Service"
]
}
}
}功能2:注册启动逻辑
服务类可以在 register() 中注册命令、路由、事件监听或中间件,也可以在 boot() 中注册启动后的逻辑。具体调用时机遵循 ThinkPHP 服务机制。
public function register(): void
{
$this->commands([Fans::class, Auto::class, Clear::class]);
$this->app->event->listen('WechatFansSubscribe', static function ($openid) {
AutoService::register($openid);
});
$this->app->route->any('/plugin-wxpay-notify/:vars', static function (Request $request) {
// 处理支付通知
});
}功能3:暴露插件菜单
继承 think\admin\Plugin 的服务类必须实现静态 menu(): array。插件中心通过该方法展示插件入口;如果要把菜单写入后台菜单表,需要在数据库迁移中显式调用 PhinxExtend::write2menu()。
public static function menu(): array
{
$code = app(static::class)->appCode;
return [
[
'name' => '用户管理',
'subs' => [
['name' => '用户账号管理', 'node' => "{$code}/master/index"],
['name' => '终端账号管理', 'node' => "{$code}/device/index"],
],
],
];
}🚀 主要功能
- 服务层集成: 基于 ThinkPHP 服务层加载插件服务
- 插件元信息:
think\admin\Plugin记录插件编码、路径、命名空间、包名和服务类 - 菜单声明:
menu()为插件中心提供插件入口菜单 - 扩展注册: 可注册命令、路由、事件监听和中间件
- 包信息展示:
xadmin:publish会生成vendor/versions.php,供插件中心读取包信息
📋 插件类型
ThinkAdmin 常见插件来源分为三类:
1. 本地插件
通过 Composer repositories.type=path 引入本地目录,适合插件开发和本地调试。插件根目录需要包含 composer.json,通常需要配置 autoload。本地插件不建议启用 extra.plugin.clear,避免源目录被清理。
当前 ThinkAdminDeveloper 根项目就是这种开发聚合模式:根 composer.json 声明多个 path 仓库指向 plugin/*,用于把各独立包放在一个工作区内联调。用户安装项目通常不需要复制这组 path 仓库配置,除非正在本地开发插件。
2. 开放插件
发布到 Packagist 或其他 Composer 仓库,通过 composer require vendor/package 安装。依赖解析、版本锁定和更新都由 Composer 处理。
3. 私有插件
可结合 ThinkAdmin 插件中心及授权平台分发,安装方式和授权能力以对应平台及插件说明为准。
⚙️ 配置要求
应用插件一般包含以下配置:
- type: ThinkAdmin 应用插件使用
think-admin-plugin,普通开发组件可使用其他 Composer 类型。 - name: Composer 包名,如
zoujingli/think-plugs-admin。 - autoload: PSR-4 或 files 自动加载规则。
- extra.think.services: ThinkPHP 服务类列表,由
xadmin:publish汇总到vendor/services.php。 - extra.config: 插件中心展示信息,由
xadmin:publish汇总到vendor/versions.php。 - extra.plugin: 安装器规则,支持
init、copy、clear、event等配置。
extra.think.services 与 extra.config 由 xadmin:publish 汇总到 vendor/services.php 和 vendor/versions.php;extra.plugin 则由安装器在 Composer 安装/更新阶段处理。两者职责不同:前者影响运行时服务加载和插件中心展示,后者影响文件复制、初始化和清理。
本地插件示例
{
"type": "project",
"require": {
"zoujingli/think-plugs-admin": "dev-master"
},
"repositories": {
"ThinkAdminPlugs": {
"type": "path",
"url": "../think-plugs-admin"
}
},
"autoload": {
"psr-0": {
"": "extend"
},
"psr-4": {
"app\\": "app"
}
}
}插件 composer 示例
下面示例来自后台管理模块的真实结构,重点展示服务声明、插件中心展示信息和安装器复制规则。
{
"type": "think-admin-plugin",
"name": "zoujingli/think-plugs-admin",
"license": "MIT",
"homepage": "https://thinkadmin.top",
"description": "Admin backend module with system, menu, auth, file and queue management for ThinkAdmin",
"require": {
"php": ">7.1",
"ext-json": "*",
"topthink/framework": "^6.0|^8.0",
"topthink/think-view": "^1.0|^2.0",
"zoujingli/ip2region": "^1.0|^2.0|^3.0|@dev",
"zoujingli/think-install": "^1.0|@dev",
"zoujingli/think-library": "^6.1|@dev",
"zoujingli/think-plugs-static": "^1.0|@dev"
},
"autoload": {
"psr-4": {
"app\\admin\\": "src"
}
},
"extra": {
"think": {
"services": [
"app\\admin\\Service"
]
},
"config": {
"type": "module",
"name": "系统后台管理",
"document": "https://thinkadmin.top/plugin/think-plugs-admin.html",
"description": "后台基础管理模块,提供系统配置、任务、日志、字典、文件、菜单、权限和用户管理。"
},
"plugin": {
"copy": {
"src": "!app/admin",
"stc/database": "database/migrations"
},
"clear": true
}
}
}配置说明:
- type:
think-admin-plugin会由think-install安装器处理extra.plugin规则。 - autoload: 定义插件类的自动加载规则;模块插件可以映射到
app\admin\、app\wechat\等应用命名空间。 - extra.think.services: ThinkPHP 服务声明,不限定必须继承
think\admin\Plugin;例如 Helper 插件的服务类继承普通think\Service。 - extra.config: 插件中心展示信息,
type常见值有module、plugin、service、library。 - extra.plugin.copy: 安装时复制文件,目标以
!开头时表示先删除目标再复制。 - extra.plugin.clear: 仅在安装路径位于
vendor下时清理原安装目录。
🧩 插件服务类
继承 think\admin\Plugin 的服务类会在构造时记录插件元信息,并必须实现 menu(): array。普通工具组件可以只继承 think\Service,不需要 menu()。
基础属性
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
$package | string | 否 | 插件包名,如 zoujingli/think-plugs-admin;未设置时会从服务类上级目录内的 composer.json 自动读取 |
$appCode | string | 否 | 插件编码,未设置时按命名空间自动计算;如果命名空间以项目默认应用命名空间开头,会去掉该前缀后用 - 连接 |
$appName | string | 是 | 插件名称,用于插件注册信息 |
$appPath | string | 否 | 插件路径,未设置时按服务类文件自动计算 |
$appAlias | string | 否 | 插件别名;非空且不同于 $appCode 时会写入 app.app_map |
$appSpace | string | 否 | 插件命名空间,未设置时按服务类命名空间计算 |
$appService | string | 否 | 服务注册类,默认为当前类 |
插件服务实例化后会写入 Plugin::get() 的内部注册表。调用 Plugin::get($code, true) 时,会再关联 vendor/versions.php 中对应包的安装展示信息;如果 $appName 为空,则会尝试使用安装信息里的名称补齐。
基础示例
<?php
declare(strict_types=1);
namespace plugin\account;
use think\admin\Plugin;
class Service extends Plugin
{
protected $appName = '账号管理';
protected $package = 'zoujingli/think-plugs-account';
public static function menu(): array
{
$code = app(static::class)->appCode;
return [
[
'name' => '用户管理',
'subs' => [
['name' => '用户账号管理', 'icon' => 'layui-icon layui-icon-user', 'node' => "{$code}/master/index"],
['name' => '终端账号管理', 'icon' => 'layui-icon layui-icon-cellphone', 'node' => "{$code}/device/index"],
['name' => '手机短信管理', 'icon' => 'layui-icon layui-icon-email', 'node' => "{$code}/message/index"],
],
],
];
}
}带服务注册示例
<?php
declare(strict_types=1);
namespace app\wechat;
use app\wechat\command\Auto;
use app\wechat\command\Clear;
use app\wechat\command\Fans;
use app\wechat\service\AutoService;
use app\wechat\service\PaymentService;
use think\admin\extend\CodeExtend;
use think\admin\Plugin;
use think\Request;
class Service extends Plugin
{
protected $appName = '微信管理';
protected $package = 'zoujingli/think-plugs-wechat';
public function register(): void
{
$this->commands([Fans::class, Auto::class, Clear::class]);
$this->app->event->listen('WechatFansSubscribe', static function ($openid) {
AutoService::register($openid);
});
$this->app->route->any('/plugin-wxpay-notify/:vars', static function (Request $request) {
try {
$data = json_decode(CodeExtend::deSafe64($request->param('vars')), true);
return PaymentService::notify($data);
} catch (\Error|\Exception $exception) {
return "Error: {$exception->getMessage()}";
}
});
}
public static function menu(): array
{
$code = app(static::class)->appCode;
return [
[
'name' => '微信管理',
'subs' => [
['name' => '微信接口配置', 'icon' => 'layui-icon layui-icon-set', 'node' => "{$code}/config/options"],
['name' => '微信支付配置', 'icon' => 'layui-icon layui-icon-rmb', 'node' => "{$code}/config/payment"],
],
],
[
'name' => '微信定制',
'subs' => [
['name' => '微信粉丝管理', 'icon' => 'layui-icon layui-icon-username', 'node' => "{$code}/fans/index"],
['name' => '微信图文管理', 'icon' => 'layui-icon layui-icon-template-1', 'node' => "{$code}/news/index"],
['name' => '微信菜单配置', 'icon' => 'layui-icon layui-icon-cellphone', 'node' => "{$code}/menu/index"],
['name' => '回复规则管理', 'icon' => 'layui-icon layui-icon-engine', 'node' => "{$code}/keys/index"],
['name' => '关注自动回复', 'icon' => 'layui-icon layui-icon-release', 'node' => "{$code}/auto/index"],
],
],
[
'name' => '微信支付',
'subs' => [
['name' => '微信支付行为', 'icon' => 'layui-icon layui-icon-rmb', 'node' => "{$code}/payment.record/index"],
['name' => '微信退款管理', 'icon' => 'layui-icon layui-icon-engine', 'node' => "{$code}/payment.refund/index"],
],
],
];
}
}普通 ThinkPHP 服务示例
Helper 插件是开发辅助组件,composer.json 中同样声明了 extra.think.services,但服务类继承普通 think\Service,只在 boot() 中注册命令,不提供插件中心菜单。
<?php
namespace plugin\helper;
class Service extends \think\Service
{
public function boot()
{
$this->commands([
DbModelStruct::class,
DbIndexStruct::class,
]);
}
}📌 register() 常见用途
注册命令
$this->commands([Fans::class, Auto::class, Clear::class]);
$this->commands(['xadmin:worker' => Worker::class]);注册事件监听
$this->app->event->listen('WechatFansSubscribe', static function ($openid) {
AutoService::register($openid);
});注册路由
$this->app->route->any('/plugin-payment-notify/:vars', function (Request $request) {
try {
$data = json_decode(CodeExtend::deSafe64($request->param('vars')), true);
return Payment::mk($data['channel'])->notify($data);
} catch (\Error|\Exception $exception) {
return 'Error: ' . $exception->getMessage();
}
});注册中间件
$this->app->middleware->add(function (Request $request, \Closure $next) {
// 处理请求数据
return $next($request);
}, 'route');📌 菜单定义说明
使用 appCode 动态获取插件编码
public static function menu(): array
{
$code = app(static::class)->appCode;
return [
[
'name' => '用户管理',
'subs' => [
['name' => '用户账号管理', 'node' => "{$code}/master/index"],
['name' => '终端账号管理', 'node' => "{$code}/device/index"],
],
],
];
}合并其他插件菜单
支付插件会合并账号插件菜单,使账号相关入口和支付入口能一起展示。
<?php
namespace plugin\payment;
use plugin\account\Service as AccountService;
use think\admin\Plugin;
class Service extends Plugin
{
protected $appName = '支付管理';
protected $package = 'zoujingli/think-plugs-payment';
public static function menu(): array
{
$code = app(static::class)->appCode;
return array_merge(AccountService::menu(), [
[
'name' => '支付管理',
'subs' => [
['name' => '支付配置管理', 'node' => "{$code}/config/index"],
['name' => '支付行为管理', 'node' => "{$code}/record/index"],
['name' => '支付退款管理', 'node' => "{$code}/refund/index"],
],
],
]);
}
}插件中心展示与系统菜单写入
menu() 返回值由插件中心读取,用于插件入口展示和权限过滤。系统菜单表不会因为定义 menu() 自动写入;如需安装时写入后台全局菜单,应在迁移脚本中调用 PhinxExtend::write2menu()。
PhinxExtend::write2menu([
[
'name' => '应用管理',
'icon' => 'layui-icon layui-icon-app',
'node' => '',
'subs' => [
['name' => '插件应用管理', 'node' => 'plugin-center/index/index'],
],
],
]);⚠️ 注意事项
extra.think.services需要配合php think xadmin:publish生成vendor/services.php。think\admin\Plugin服务类必须实现menu(): array,没有菜单的服务也应返回空数组。- 当前
think\admin\Plugin没有$this->menus()方法,菜单通过静态menu()方法声明。 extra.config写入vendor/versions.php,用于插件中心展示名称、类型、授权、文档和描述等信息。- 菜单节点建议使用
app(static::class)->appCode或self::getAppCode()拼接,避免插件编码调整后节点失配。
