Skip to content

插件开发指南

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 范围说明
condoradmin1-999核心管理
condorshop1000-1999商城
condorsms2000-2999短信
condorsupport3000-3999客服
condorlawyer4000-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(前端)分开
  • 统一基类:继承 BackendTranslatableBackend
  • 类型声明:使用 declare(strict_types=1);

3. 数据库设计

  • 时间字段:使用 Unix 时间戳(bigint)
  • 软删除:添加 deleted_at 字段
  • 标准字段statusweighcreatetimeupdatetime
  • 外键规范{table}_id

4. 翻译管理

  • 键名前缀:所有翻译键加插件名前缀
  • 双语支持:提供中英文翻译
  • 路径配置:在 translation.php 中注册

5. 权限控制

  • 菜单权限:在 install.sql 中添加
  • 按钮权限is_keep=1,关联页面菜单
  • 路由保护:使用 AuthTokenAuthPermission 中间件

常见问题

Q1: 插件如何加载?

Webman 自动扫描 plugin/ 目录下的 config/ 文件,按文件名加载配置。

Q2: 路由冲突怎么办?

确保每个插件的路由前缀唯一:

  • 后台:/core/{plugin_name}/
  • 前端:/api/{plugin_name}/

Q3: 翻译不生效?

检查:

  1. translation.php 是否正确配置路径
  2. 翻译键是否加了插件前缀
  3. 翻译文件是否存在

Q4: 菜单不显示?

检查:

  1. SQL 是否正确执行
  2. 菜单 ID 是否冲突
  3. 用户角色是否有权限

相关文档