---
url: /docs/5.0/plugin/practice.md
---
# 插件开发实战

> 在上一篇 [快速入门](./quickstart.md) 中，我们已经成功搭建了一个基础插件并完成安装。本章将带你**从零开始**，通过一个完整的实战案例，手把手教你开发一个功能完善的 CatchAdmin 后台模块插件。

## 开始之前

在开始开发之前，请确保你已经：

* 完成了 [快速入门](./quickstart.md) 的学习
* 熟悉 Laravel 的基本概念（路由、控制器、模型）
* 了解 Vue.js 的基础知识（可选，用于前端页面开发）

## 目录结构约定

开发一个完整的后台模块插件时，我们推荐遵循以下**约定的目录结构**：

```
├─test                         # 插件根目录
│  ├─resource                  # 资源目录（可选）
│  │  └─views                  # Vue 视图文件目录
│  ├─config                    # 配置文件目录（可选）
│  ├─migrations                # 数据库迁移目录（可选）
│  ├─routes                    # 路由定义目录（可选）
│  ├─src                       # 源代码目录（核心）
│  │  ├─Http                   # HTTP 层
│  │  │  ├─Controllers         # 控制器
│  │  │  └─Requests            # 表单验证（可选）
│  │  └─Models                 # 数据模型（可选）
│  ├─hook.php                  # 生命周期钩子
│  ├─composer.json             # Composer 配置
│  └─README.md                 # 说明文档
```

### 目录说明

| 目录/文件 | 必需 | 说明 |
|-----------|------|------|
| `src/` | | 存放插件的核心源代码 |
| `composer.json` | | 定义插件的依赖和元信息 |
| `hook.php` | | 插件生命周期钩子，详见 [钩子文档](./hook.md) |
| `routes/` | 可选 | 定义插件的 API 路由 |
| `migrations/` | 可选 | 数据库迁移文件 |
| `resource/views/` | 可选 | Vue 前端页面文件 |
| `config/` | 可选 | 插件配置文件 |

:::tip
什么时候需要这些目录？

* **开发完整后台模块**：上述所有目录基本都需要用到
* **开发简单功能插件**：根据实际需求选择性添加
* **开发 API 接口**：必须按照约定的目录结构进行开发

CatchAdmin 的插件本质上就是一个 **Composer 包**，因此你可以根据实际需求灵活组织代码结构。
:::

接下来，我们将使用上一篇创建的 `test/test` 插件，一步步开发一个完整的后台模块。

## 第一步：创建数据迁移

大多数插件都需要存储数据，因此我们首先创建数据库迁移文件。

### 生成迁移文件

执行以下命令，在插件目录中创建迁移文件：

```shell
php artisan make:migration CreateUser --path=packages\test\test\migrations
```

命令执行后，会在 `packages/test/test/migrations` 目录下生成一个迁移文件：

![创建数据迁移](https://image.catchadmin.com/202512161431000.png)

:::tip 📚 不熟悉数据迁移？
如果你对 Laravel 的数据迁移不太了解，可以先阅读官方文档：[Laravel 数据迁移](https://laravel-docs.catchadmin.com/docs/12/database/migrations)
:::

### 编写迁移文件

打开生成的迁移文件，添加表结构定义：

```php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('test_user', function (Blueprint $table) {
            $table->id();
            $table->string('name')->comment('名称');
            $table->string('mobile')->comment('手机号');
            $table->string('avatar')->nullable()->comment('头像');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('test_user');
    }
};
```

### 执行迁移

运行以下命令创建数据表：

```shell
php artisan migrate --path=packages\test\test\migrations\
```

执行成功后，使用数据库客户端（如 Navicat）查看，可以看到 `test_user` 表已创建：

![数据表](https://image.catchadmin.com/202512161504294.png)

## 第二步：创建模型

数据表创建完成后，我们需要创建对应的 Eloquent 模型来操作数据。

### 生成模型文件

执行 Laravel 的模型生成命令：

```shell
php artisan make:model TestUser
```

该命令会在 `app/Models` 目录下生成模型文件：

![创建模型](https://image.catchadmin.com/202512161509504.png)

### 移动到插件目录

将生成的模型文件移动到插件的 `src/Models` 目录下（如果目录不存在，请先手动创建）：

![创建模型](https://image.catchadmin.com/202512161514146.png)

:::warning ⚠️ 注意命名空间
移动文件后，**必须修改命名空间**以匹配新的目录位置！详见 [常见问题 - 命名空间相关](./faq.md#命名空间相关)
:::

修改后的模型内容如下：

```php
<?php

namespace Test\Test\Models;

use Illuminate\Database\Eloquent\Model;

class TestUser extends Model
{
    protected $table = 'test_user';
    
    protected $fillable = [
        'name', 'mobile', 'avatar'
    ];
}
```

## 第三步：创建控制器

控制器负责处理 HTTP 请求，是连接前端和数据的桥梁。

### 创建目录结构

按照约定，在 `src` 目录下创建以下目录结构：

```
src/
└─Http/
  └─Controllers/
```

然后在 `Controllers` 目录中创建 `TestUserController.php`：

![创建控制器](https://image.catchadmin.com/202512161525610.png)

### 编写控制器代码

```php
<?php

namespace Test\Test\Http\Controllers;

use Catch\Base\CatchController;

class TestUserController extends CatchController
{
    public function index()
    {
        return ['test_user' => 'has no users'];
    }
}
```

## 第四步：配置路由

控制器创建完成后，还需要配置路由才能访问。

### 创建路由文件

在插件根目录创建 `routes` 目录，并在其中创建 `api.php` 文件：

![创建路由](https://image.catchadmin.com/202512161529395.png)

在 `api.php` 中添加路由定义：

```php
\Illuminate\Support\Facades\Route::get('/test/user', [\Test\Test\Http\Controllers\TestUserController::class, 'index']);
```

### 重新安装插件

:::warning ⚠️ 重要步骤
每次修改插件的路由、配置等文件后，都需要**重新安装插件**才能生效！详见 [常见问题 - 安装相关](./faq.md#安装相关)
:::

```shell
# 先卸载旧版本
composer remove test/test --ignore-platform-reqs

# 再安装新版本
composer require test/test:* --ignore-platform-reqs
```

### 启动开发服务器

```shell
php artisan serve
```

### 验证接口

打开浏览器访问 `http://127.0.0.1:8000/test/user`：

![访问接口链接](https://image.catchadmin.com/202512161545982.png)

🎉 **恭喜！** 如果看到返回的 JSON 数据，说明接口已经成功运行了！

## 第五步：集成 CatchAdmin 功能

到目前为止，我们已经有了一个可以访问的基础接口。接下来，我们将让插件与 CatchAdmin 深度集成，实现：

* ✅ 身份认证
* ✅ 权限控制
* ✅ Vue 前端页面
* ✅ 自动生成菜单

### 5.1 添加身份认证

为了让插件接口需要登录才能访问，我们需要添加身份认证中间件。

修改 `routes/api.php` 文件：

```php
\Illuminate\Support\Facades\Route::prefix(config('catch.route.prefix'))
    ->middleware([
        \Catch\Middleware\AuthMiddleware::class // 身份认证中间件
    ])
    ->group(function(){
        \Illuminate\Support\Facades\Route::get('test/user',
            [\Test\Test\Http\Controllers\TestUserController::class, 'index']);
    });
```

重新安装插件后，访问 `http://127.0.0.1:8000/api/test/user`：

![出现身份认证失效:Unauthenticated.](https://image.catchadmin.com/202512161559134.png)

看到 `Unauthenticated` 提示，说明身份认证中间件已生效！🔐

### 5.2 添加权限控制

在身份认证的基础上，我们还可以添加权限控制，确保只有拥有相应权限的用户才能访问接口。

继续修改 `routes/api.php`：

```php
\Illuminate\Support\Facades\Route::prefix(config('catch.route.prefix'))
    ->middleware([
        \Catch\Middleware\AuthMiddleware::class,           // 身份认证
        \Modules\Permissions\Middlewares\PermissionGate::class // 权限控制
    ])
    ->group(function(){
        \Illuminate\Support\Facades\Route::get('test/user',
            [\Test\Test\Http\Controllers\TestUserController::class, 'index']);
    });
```

### 5.3 添加 Vue 前端页面

CatchAdmin 使用 Vue 作为前端框架。好消息是，插件的 Vue 页面**无需编译**，可以实现即插即用！

#### 创建视图目录

在插件根目录创建以下目录结构：

```
resource/
└─views/
  └─user/
    └─index.vue
```

![添加 Vue 页面](https://image.catchadmin.com/202512161607495.png)

#### 编写 Vue 页面

在 `index.vue` 中添加内容：

```vue
<script>
export default {
    name: "index.vue"
}
</script>

<template>
    <div>这是测试用户页面</div>
</template>

<style scoped>

</style>
```

#### 访问 Vue 页面

安装插件后，可以通过以下 URL 访问 Vue 文件内容：

```
http://127.0.0.1:8000/api/plugins/test/test/user/index
```

:::tip 📝 URL 规则说明
| 部分 | 说明 |
|------|------|
| `api/plugins` | 固定前缀 |
| `test/test` | 插件包名 |
| `user/index` | 对应 `resource/views/user/index.vue` |

**注意**：必须先安装插件，URL 才能生效！
:::

![访问 Vue 页面](https://image.catchadmin.com/202512161617416.png)

### 5.4 使用钩子自动创建菜单

最后一步，我们使用插件钩子在安装时自动创建后台菜单。更多钩子用法请参考 [生命周期钩子](./hook.md)。

#### 编辑钩子文件

找到插件根目录的 `hook.php` 文件：

![钩子自动创建菜单](https://image.catchadmin.com/202512181501555.png)

在 `afterInstall` 方法中添加菜单创建代码：

```php
public function afterInstall(array $context): void
{
    Plugin::createMenus([
        Plugin::createMenu('测试插件', '/test', 'Test\Test', children: [
            Plugin::createMenu('测试用户', 'user', 'Test\Test',
                controller: 'TestUser', controllerMethod: 'index', type: 2,
                component: Plugin::view('test/test', 'user.index')
            )
        ])
    ]);
}
```

:::warning ⚠️ module 参数说明
`module` 参数必须填写插件的**根命名空间**（如 `Test\Test`），这用于标识菜单归属于哪个插件。

![module](https://image.catchadmin.com/202512161633975.png)
:::

#### 重新安装并查看效果

```shell
composer remove test/test --ignore-platform-reqs
composer require test/test:* --ignore-platform-reqs
```

刷新后台页面，你将看到新创建的菜单：

![CatchAdmin 插件测试页面](https://image.catchadmin.com/202512181524897.png)

🎉 **大功告成！** 你已经成功开发了一个完整的 CatchAdmin 后台模块插件！

## 卸载插件

卸载插件非常简单，只需执行以下命令：

```shell
composer remove test/test --ignore-platform-reqs
```

### 使用钩子清理数据

为了在卸载时自动清理插件创建的菜单等数据，可以在 `hook.php` 的 `afterUninstall` 方法中添加清理逻辑：

```php
public function afterUninstall(array $context): void
{
    // 删除该插件创建的所有菜单
    Permissions::where('module', $context['namespace'])->delete();
}
```

:::tip 💡 提示

* `$context['namespace']` 会自动获取插件的根命名空间
* 上述代码会删除该插件的**所有菜单**，你可以根据实际需求调整删除逻辑
  :::

## 总结

通过本教程，你已经学会了：

1. **目录结构约定** - 插件的标准目录组织方式
2. **数据库迁移** - 在插件中创建和管理数据表
3. **模型与控制器** - 编写业务逻辑代码
4. **路由配置** - 定义 API 接口
5. **中间件集成** - 添加身份认证和权限控制
6. **Vue 页面开发** - 创建无需编译的前端页面
7. **[生命周期钩子](./hook.md)** - 在安装/卸载时执行自定义逻辑

遇到问题？请查看 [常见问题](./faq.md)。

现在，你可以根据实际需求，开发更复杂的插件功能了！🚀
