插件开发指南
CondorAdmin 采用插件化架构,所有业务模块以 Webman 插件形式独立开发和部署。本文档指导如何从零创建一个新插件。
插件架构
插件目录结构
plugin/{plugin_name}/
├── AGENTS.md # 开发规范文档
├── install.sql # 数据库结构
├── uninstall.sql # 卸载脚本
├── composer.json # 依赖配置
├── config/
│ ├── route.php # 路由配置
│ ├── menu.php # 菜单配置
│ ├── translation.php # 翻译配置
│ ├── middleware.php # 中间件
│ └── process.php # 自定义进程
├── app/
│ ├── admin/controller/ # 后台管理控制器
│ ├── api/controller/ # 前端 API 控制器
│ ├── library/ # 基类库
│ ├── model/ # 数据模型
│ ├── middleware/ # 中间件
│ └── process/ # 进程
└── resource/
└── translations/
├── zh-cn/messages.php # 中文翻译
└── en-us/messages.php # 英文翻译插件类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 核心插件 | 系统基础功能 | condoradmin(权限管理) |
| 业务插件 | 业务功能模块 | condorshop(商城)、condorsms(短信) |
| 集成插件 | 第三方集成 | condorauth(第三方登录) |
快速开始
第一步:创建插件目录
bash
cd plugin/
mkdir condorexample
cd condorexample第二步:创建 composer.json
json
{
"name": "condor/condorexample",
"type": "webman-plugin",
"license": "MIT",
"description": "示例插件",
"require": {},
"autoload": {
"psr-4": {
"plugin\\condorexample\\": "src",
"plugin\\condorexample\\app\\": "app"
}
}
}第三步:创建配置文件
路由配置
位置:config/route.php
php
<?php
use Webman\Route;
use plugin\condorexample\app\admin\controller\ExampleController;
// 后台管理路由(需要权限)
Route::group('/core/condorexample', function () {
// CRUD 完整路由
createRoutes('/example', ExampleController::class);
})->middleware([
\plugin\condoradmin\app\middleware\AuthToken::class,
\plugin\condoradmin\app\middleware\AuthPermission::class,
\plugin\condoradmin\app\middleware\Lang::class,
]);
// 前端 API 路由(可选)
Route::group('/api/condorexample', function () {
Route::post('/example/list', [\plugin\condorexample\app\api\controller\ExampleController::class, 'list']);
})->middleware([
\plugin\condoradmin\app\middleware\CrossDomain::class,
]);路由规范:
- 后台管理:
/core/{plugin_name}/{module} - 前端 API:
/api/{plugin_name}/{module}
菜单配置
位置:config/menu.php
php
<?php
return [
[
'id' => 5000,
'pid' => 0,
'type' => 'menu_dir',
'title' => '示例管理',
'icon' => 'mdi:folder',
'name' => 'condorexample',
'path' => '/condorexample',
'component' => 'layout.base',
'redirect' => '',
'order' => 90,
'keepalive' => 0,
'isHide' => 0,
'isLink' => '',
'isIframe' => 0,
],
[
'id' => 5001,
'pid' => 5000,
'type' => 'menu',
'title' => '示例列表',
'icon' => 'mdi:view-list',
'name' => 'condorexample_example',
'path' => '/condorexample/example',
'component' => 'view.condorexample_example',
'order' => 1,
'keepalive' => 1,
'isHide' => 0,
],
];翻译配置
位置:config/translation.php
php
<?php
return [
'locale' => 'zh-cn',
'fallback_locale' => 'zh-cn',
'paths' => [
base_path() . '/plugin/condorexample/resource/translations'
],
];第四步:创建数据库表
位置:install.sql
sql
-- 数据表(命名规范:con_{plugin_name}_xxx)
CREATE TABLE IF NOT EXISTS `con_condorexample_example` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`title` varchar(100) NOT NULL COMMENT '标题',
`content` text COMMENT '内容',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0=禁用 1=启用)',
`weigh` int NOT NULL DEFAULT '0' COMMENT '排序',
`createtime` bigint DEFAULT NULL COMMENT '创建时间',
`updatetime` bigint DEFAULT NULL COMMENT '更新时间',
`deleted_at` bigint DEFAULT NULL COMMENT '软删除',
PRIMARY KEY (`id`),
KEY `status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='示例表';
-- 菜单权限(ID 范围:5000-5999)
INSERT INTO `con_system_menu_rule`
(`id`, `is_keep`, `pid`, `name`, `title`, `icon`, `path`, `component`, `i18nkey`, `menu_type`, `weigh`, `status`, `createtime`, `updatetime`)
VALUES
-- 一级菜单(目录)
(5000, NULL, 0, 'condorexample', '示例管理', 'mdi:folder', '/condorexample', 'layout.base', 'route.condorexample', 1, 90, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-- 二级菜单(页面)
(5001, NULL, 5000, 'condorexample_example', '示例列表', 'mdi:view-list', '/condorexample/example', 'view.condorexample_example', 'route.condorexample_example', 1, 1, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
-- 按钮权限
(5002, 1, 5001, 'example_index', '查看', '', '/core/condorexample/example/index', NULL, 'condor.route.view', 0, 0, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(5003, 1, 5001, 'example_add', '添加', '', '/core/condorexample/example/add', NULL, 'condor.route.add', 0, 0, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(5004, 1, 5001, 'example_edit', '编辑', '', '/core/condorexample/example/edit', NULL, 'condor.route.edit', 0, 0, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(5005, 1, 5001, 'example_del', '删除', '', '/core/condorexample/example/del', NULL, 'condor.route.delete', 0, 0, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP()),
(5006, 1, 5001, 'example_multi', '批量操作', '', '/core/condorexample/example/multi', NULL, 'condor.route.bulk_actions', 0, 0, 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP());ID 分配规范:
| 插件 | ID 范围 | 说明 |
|---|---|---|
| condoradmin | 1-999 | 核心管理 |
| condorshop | 1000-1999 | 商城 |
| condorsms | 2000-2999 | 短信 |
| condorsupport | 3000-3999 | 客服 |
| condorlawyer | 4000-4999 | 律师 |
| 自定义插件 | 5000+ | 按顺序递增 |
第五步:创建 Model
位置:app/model/CondorexampleExample.php
php
<?php
declare(strict_types=1);
namespace plugin\condorexample\app\model;
use support\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CondorexampleExample extends Model
{
use SoftDeletes;
protected $table = 'condorexample_example';
public $timestamps = true;
protected $dateFormat = 'U';
const CREATED_AT = 'createtime';
const UPDATED_AT = 'updatetime';
const DELETED_AT = 'deleted_at';
protected $guarded = [];
protected $casts = [
'status' => 'integer',
'weigh' => 'integer',
];
}命名规范:
- 表名:
con_{plugin_name}_{table}(例如:con_condorexample_example) - 模型名:
{PluginName}{Table}(驼峰,例如:CondorexampleExample)
第六步:创建 Controller
位置:app/admin/controller/ExampleController.php
php
<?php
declare(strict_types=1);
namespace plugin\condorexample\app\admin\controller;
use plugin\condoradmin\app\library\Backend;
use plugin\condorexample\app\model\CondorexampleExample;
class ExampleController extends Backend
{
protected $model;
protected array $searchable = [
'title' => ['type' => 'string'],
'status' => ['type' => 'int'],
'createtime' => ['type' => 'timestamp'],
];
protected array $sortable = ['id', 'weigh', 'createtime'];
protected $noNeedRight = ['selectpage'];
public function __construct()
{
$this->model = new CondorexampleExample();
parent::__construct();
}
}第七步:创建翻译文件
中文翻译
位置:resource/translations/zh-cn/messages.php
php
<?php
return [
'condorexample.title' => '标题',
'condorexample.content' => '内容',
'condorexample.status' => '状态',
'condorexample.weigh' => '排序',
'condorexample.createtime' => '创建时间',
];英文翻译
位置:resource/translations/en-us/messages.php
php
<?php
return [
'condorexample.title' => 'Title',
'condorexample.content' => 'Content',
'condorexample.status' => 'Status',
'condorexample.weigh' => 'Weight',
'condorexample.createtime' => 'Create Time',
];第八步:前端页面
位置:condor-admin/src/views/condorexample/example/index.vue
vue
<script setup lang="ts">
import { ref } from 'vue';
import { $t } from '@/locales';
defineOptions({ name: 'CondorexampleExample' });
const config = ref<Condor.Table.Config>({
urls: {
index: '/core/condorexample/example/index',
add: '/core/condorexample/example/add',
edit: '/core/condorexample/example/edit',
del: '/core/condorexample/example/del',
multi: '/core/condorexample/example/multi'
},
rowKey(row) {
return row.id;
},
columns: [
{
type: 'selection',
key: 'id',
title: 'ID',
width: 80
},
{
key: 'title',
title() { return $t('condorexample.example.title'); },
operator: 'like',
rules: [{ required: true, trigger: 'blur' }]
},
{
key: 'content',
title() { return $t('condorexample.example.content'); },
table: false,
component: { name: 'n-input', props: { type: 'textarea' } }
},
{
key: 'status',
title() { return $t('condor.common.status'); },
value: 1,
component: {
name: 'n-switch',
props: { checkedValue: 1, uncheckedValue: 0 }
}
},
{
key: 'createtime',
title() { return $t('condor.common.createtime'); },
form: false,
render: 'datetime'
},
{
type: 'operate',
title() { return $t('common.operate'); },
buttons: ['edit', 'del']
}
]
});
</script>
<template>
<div class="h-full">
<CondorTable :config="config" />
</div>
</template>高级功能
多语言表(Translatable)
主表 Model
php
<?php
namespace plugin\condorexample\app\model;
use support\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class CondorexampleArticle extends Model
{
use SoftDeletes;
protected $table = 'condorexample_article';
// 关联翻译表
public function translations()
{
return $this->hasMany(CondorexampleArticleTranslations::class, 'main_id');
}
}翻译表 Model
php
<?php
namespace plugin\condorexample\app\model;
use support\Model;
class CondorexampleArticleTranslations extends Model
{
protected $table = 'condorexample_article_translations';
public $timestamps = false;
protected $fillable = ['main_id', 'locale', 'title', 'content'];
}Controller
php
<?php
namespace plugin\condorexample\app\admin\controller;
use plugin\condoradmin\app\library\TranslatableBackend;
use plugin\condorexample\app\model\CondorexampleArticle;
use plugin\condorexample\app\model\CondorexampleArticleTranslations;
class ArticleController extends TranslatableBackend
{
protected $model;
protected $translationModel = null;
// 多语言字段
protected array $multilingualFields = ['title', 'content'];
public function __construct()
{
$this->model = new CondorexampleArticle();
$this->translationModel = new CondorexampleArticleTranslations();
parent::__construct();
}
}自定义中间件
位置:app/middleware/CustomMiddleware.php
php
<?php
namespace plugin\condorexample\app\middleware;
use Webman\MiddlewareInterface;
use Webman\Http\Response;
use Webman\Http\Request;
class CustomMiddleware implements MiddlewareInterface
{
public function process(Request $request, callable $handler): Response
{
// 前置处理
$response = $handler($request);
// 后置处理
return $response;
}
}注册中间件:config/middleware.php
php
<?php
return [
'' => [
plugin\condorexample\app\middleware\CustomMiddleware::class,
]
];自定义进程
位置:app/process/TaskProcess.php
php
<?php
namespace plugin\condorexample\app\process;
use Workerman\Timer;
class TaskProcess
{
public function onWorkerStart()
{
// 每 10 秒执行一次
Timer::add(10, function () {
// 任务逻辑
});
}
}注册进程:config/process.php
php
<?php
return [
'task' => [
'handler' => plugin\condorexample\app\process\TaskProcess::class,
'count' => 1
]
];安装与卸载
安装脚本
bash
# 执行 SQL
mysql -u root -p database_name < plugin/condorexample/install.sql
# 更新 Composer
composer dump-autoload卸载脚本
位置:uninstall.sql
sql
-- 删除数据表
DROP TABLE IF EXISTS `con_condorexample_example`;
-- 删除菜单权限
DELETE FROM `con_system_menu_rule` WHERE `id` >= 5000 AND `id` <= 5999;最佳实践
1. 命名规范
- 表名:
con_{plugin_name}_{table} - 模型类:
{PluginName}{Table} - 控制器类:
{Table}Controller - 路由前缀:
/core/{plugin_name}或/api/{plugin_name} - 翻译键:
{plugin_name}.{key} - 菜单 ID:连续区间,每个插件 1000 个
2. 目录结构
- 分离关注点:admin(后台)和 api(前端)分开
- 统一基类:继承
Backend或TranslatableBackend - 类型声明:使用
declare(strict_types=1);
3. 数据库设计
- 时间字段:使用 Unix 时间戳(bigint)
- 软删除:添加
deleted_at字段 - 标准字段:
status、weigh、createtime、updatetime - 外键规范:
{table}_id
4. 翻译管理
- 键名前缀:所有翻译键加插件名前缀
- 双语支持:提供中英文翻译
- 路径配置:在
translation.php中注册
5. 权限控制
- 菜单权限:在
install.sql中添加 - 按钮权限:
is_keep=1,关联页面菜单 - 路由保护:使用
AuthToken和AuthPermission中间件
常见问题
Q1: 插件如何加载?
Webman 自动扫描 plugin/ 目录下的 config/ 文件,按文件名加载配置。
Q2: 路由冲突怎么办?
确保每个插件的路由前缀唯一:
- 后台:
/core/{plugin_name}/ - 前端:
/api/{plugin_name}/
Q3: 翻译不生效?
检查:
translation.php是否正确配置路径- 翻译键是否加了插件前缀
- 翻译文件是否存在
Q4: 菜单不显示?
检查:
- SQL 是否正确执行
- 菜单 ID 是否冲突
- 用户角色是否有权限
相关文档
- 📖 新增页面开发
- 📦 Backend 控制器
- 🔐 权限系统
- 🌍 中间件系统