Files
2026-08-31 11:33:59 +08:00

537 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: cross-plugin-fullstack-development
description: Full-stack development workflow for Madong plugins. Covers plugin structure, backend CRUD and Swagger, frontend template source, menu import, install copy behavior, i18n, verification, and anti-regression rules for uninstall/reinstall scenarios.
globs:
- "backend/plugin/**/*.php"
- "backend/plugin/**/config/**/*.php"
- "backend/plugin/**/resource/**/*.php"
- "backend/plugin/**/resource/template/admin/**/*"
- "frontend/admin/src/apps/**/*.vue"
- "frontend/admin/src/apps/**/*.tsx"
- "frontend/admin/src/apps/**/*.ts"
- ".windsurf/skills/**/*.md"
---
# Madong 插件全流程开发技能
用于开发类似 `article` 这样的完整插件,覆盖从目录结构、后端 CRUD、Swagger、菜单导入、前端模板源、安装复制机制、国际化,到卸载重装后的防回退验证。
## 适用场景
- 新建一个完整业务插件。
- 为已有插件补齐后台 CRUD、前台模板、菜单和语言文件。
- 修复插件卸载再安装后的页面回退、菜单异常、Swagger 冲突。
- 将一次真实插件开发过程沉淀为可复用工作流。
## 总原则
1. 先核对项目真实实现,再使用 skill 或模板。
2. 插件前端的权威源目录是 `backend/plugin/{plugin}/resource/template/admin`,不是运行目录。
3. 安装后复制出来的 `frontend/admin/src/apps/{plugin}` 属于运行产物,不作为正式开发目录;若只改这里,卸载重装会回退。
4. 后端 Swagger 路由必须唯一,旧演示接口不可与新 CRUD 路由重复。
5. 插件菜单以安装导入的系统菜单为准,不依赖前端自动扫描插件路由生成侧边栏。
6. 前后端路径、权限码、菜单 component、i18n key 必须一致。
7. 插件 skill 若与真实项目不一致,先更新 skill 再继续生成或修改。
8. 插件开发顺序默认采用 template-first:后端完成后直接开发 `resource/template/admin`,不再单独维护运行目录。
## 先理解插件安装与复制机制
### 1. 模板源与运行目录
**模板源目录:**
```text
backend/plugin/{plugin}/resource/template/admin/
```
**安装后目标目录:**
```text
frontend/admin/src/apps/{plugin}/
```
当前项目会在插件安装时通过 `core\plugin\traits\TemplateTrait::copyTemplateDir('admin')`
把模板源整体复制到前端运行目录。
### 2. 关键结论
- 改 `frontend/admin/src/apps/{plugin}` 只会影响当前运行结果。
- 改 `backend/plugin/{plugin}/resource/template/admin` 才能影响未来安装结果。
- 只要模板源还是旧版,卸载再安装就会把旧版重新覆盖回来。
### 3. 正确维护顺序
修改插件前端时遵循:
1. 先完成后端开发与接口联调。
2. 后端完成后,直接在 `backend/plugin/{plugin}/resource/template/admin` 下开发前端模板源。
3. 不要把 `frontend/admin/src/apps/{plugin}` 作为正式开发目录。
4. 完成模板源后,通过插件安装或复制流程生成运行目录并验证结果。
5. 仅当用户明确要求排查运行态问题时,才临时查看运行目录,但不要把它作为主维护对象。
## 开发前检查
### 1. 确认插件真实目录结构
以 `article` 为例:
```text
backend/plugin/article/
├── app/
├── config/
├── resource/
│ ├── menu/
│ ├── template/admin/
│ └── ...
```
至少核对:
- `app/adminapi/controller`
- `app/service/admin`
- `app/dao`
- `app/model`
- `app/validate` 或 `app/adminapi/validate`
- `resource/template/admin/api`
- `resource/template/admin/views`
- `resource/template/admin/lang`
- `resource/menu/admin.php`
- `config/translation.php`
- `config/route.php`
### 2. 确认参考实现
优先寻找以下真实参考:
- 同类已完成插件
- 主项目现有 `BasicCrud` 页面
- 主项目已接好 Swagger 的 `Crud` 控制器
- 现有插件菜单定义
- 现有插件语言文件加载方式
### 3. Windows 开发环境检查
在 Windows 下开发插件时,注意新插件目录创建本身未必会立即触发外层监控重启。
如果新插件接口返回 404,需要核对监控是否覆盖 `backend/plugin` 根目录变更。
## 插件目录与产物约定
## 后端
```text
backend/plugin/{plugin}/app/
├── adminapi/controller/
├── adminapi/validate/
├── api/controller/
├── dao/
├── model/
├── schema/request/
├── schema/response/
└── service/admin/
```
## 模板源前端
```text
backend/plugin/{plugin}/resource/template/admin/
├── api/index.ts
├── lang/
│ ├── zh-cn/zh.json
│ └── en/en.json
└── views/{plugin_key}/{module}/
├── index.vue
└── schemas/index.tsx
```
## 安装后前端运行目录
```text
frontend/admin/src/apps/{plugin}/
├── api/index.ts
├── lang/
│ ├── zh-cn/zh.json
│ └── en/en.json
└── views/{plugin_key}/{module}/
├── index.vue
└── schemas/index.tsx
```
## 后端开发流程
### 1. 数据库迁移
路径:
```text
backend/plugin/{plugin}/resource/database/migrations/
```
要求:
- 主键优先使用雪花 ID:`bigInteger('id')->primary()`。
- 按真实项目风格补齐索引、时间字段、软删除字段。
- 状态字段优先使用 `enabled` 或业务已有枚举字段。
- 不要直接照搬 Laravel 默认 `id()`。
- 不要从旧项目或旧插件盲目继承 `tenant_id`、多租户索引、租户作用域;必须先核对当前项目是否仍为单租户结构。
### 2. Model / DAO / Service / Validate
要求:
- Model 继承插件或项目当前实际使用的 `BaseModel`。
- DAO 继承 `core\base\BaseDao`。
- Service 继承 `core\base\BaseService`。
- Validate 继承 `core\base\BaseValidate`。
- 自定义业务逻辑放在 Service,不要塞进 Controller。
本项目额外要求:
- DAO / Service 文件中必须显式 `use core\base\BaseDao;`、`use core\base\BaseService;`,不能只写 `extends BaseDao/BaseService` 而省略导入。
- Service 构造函数使用容器注入 DAO,例如 `__construct(FooDao $dao)`,不要在 Service 内部手动 `new FooDao()`。
- 如果 DAO 重写 `selectList()`,方法签名必须与当前项目 `BaseDao` 保持一致,包括:
- `string|array $field = '*'`
- `int $page = 0`
- `int $limit = 0`
- `bool $search = false`
- `?array $withoutScopes = null`
- `getKeywordFields()` 优先使用 `protected`,与当前项目已落地 DAO 风格保持一致。
- 若页面会通过 `Crud` 的 `format=select` 复用下拉,模型应提供 `getNameAttribute()`,把业务显示字段映射到 `name`,以兼容 `title / name / real_name / id` 标签解析顺序。
- JSON 字段应同时在 Model 中配置 `casts`,并在 Validate 中支持“数组或 JSON 字符串”两种输入形态。
### 3. Admin 控制器与 CRUD
控制器通常放在:
```text
backend/plugin/{plugin}/app/adminapi/controller/
```
要求:
- 继承 `app\adminapi\controller\Crud`。
- 构造函数注入 Service 和 Validate。
- 标准方法走 `parent::index()`、`parent::show()`、`parent::store()`、`parent::update()`、`parent::destroy()`。
- 路由路径与前端 `BaseService` 的 `baseUrl` 一致。
### 4. Swagger / OpenAPI 规范
插件最容易踩的坑之一是 Swagger 路由冲突。
必须遵守:
- 每个 `OA\Get`、`OA\Post`、`OA\Put`、`OA\Delete` 的 `path` 必须唯一。
- 新 CRUD 控制器不要和旧 demo Controller 使用相同路径。
- 若存在旧演示接口,迁移到 `legacy` 命名空间或改用不冲突路径。
- 详情、删除、批量删除等接口按项目现有 Swagger 属性写法统一。
- `IdRequest`、`BatchDeleteRequest`、`SchemaConstants::X_SCHEMA_REQUEST` 等参数约定要与项目现有控制器保持一致。
### 5. 路由与权限码
建议保持如下映射:
```text
GET /{plugin}/{resource}
GET /{plugin}/{resource}/{id}
POST /{plugin}/{resource}
PUT /{plugin}/{resource}/{id}
DELETE /{plugin}/{resource}/{id}
```
要求:
- `addAuth`、`editAuth`、`removeAuth`、`viewAuth` 与后端权限码一致。
- 菜单按钮权限码与控制器 `Permission` 属性一致。
- 不要出现前端权限码、后端权限码、菜单权限码三套不一致。
## 前端模板源开发流程
### 1. 后端完成后直接开发模板源
插件前端正式维护目录:
```text
backend/plugin/{plugin}/resource/template/admin/
```
后端完成后,直接在该目录开发插件前端。
默认不要单独开发 `frontend/admin/src/apps/{plugin}`。
运行目录只作为安装产物和验证结果使用。
### 2. index.vue 统一使用官方 BasicCrud
标准页面入口应为:
- `BasicCrud`
- `useCrud`
- `crudSchema`
不要继续保留插件 demo 里的:
- `el-card`
- 手写 `el-table`
- `fetchData()` 拉取列表
### 3. schemas/index.tsx
要求:
- 使用 `CrudSchema`。
- 列表接口优先接 `BaseService.list`。
- 标准 CRUD 页面使用 `crudApi.add/edit/remove/view`。
- `rowKey` 默认使用 `id`。
- 表头索引统一使用 `article.common.columns.index` 这类语言键。
- 查询条件使用项目真实后端支持的查询参数格式,例如 `LIKE_name`、`LIKE_title`。
### 4. API 层
插件模板 API 通常放在:
```text
backend/plugin/{plugin}/resource/template/admin/api/index.ts
```
要求:
- 使用 `BaseService` 生成标准 CRUD Service。
- 仍可保留兼容旧页面的 `pluginApi` 包装方法。
- `baseUrl` 必须与后端控制器路径一致。
- 自定义只读接口可继续使用 `request.get()` 封装。
- 如运行目录为调试临时新增了 API 类型、Service 或语言键,最终必须同步回模板源,否则插件重装后会回退。
### 5. i18n 语言文件
路径:
```text
backend/plugin/{plugin}/resource/template/admin/lang/zh-cn/zh.json
backend/plugin/{plugin}/resource/template/admin/lang/en/en.json
```
要求:
- 语言文件根对象使用插件名,例如 `article`。
- 如果 `schemas` 中使用了:
- `common.columns.index`
- `modules.*.search.*`
- `modules.*.form.*`
- `modules.*.columns.*`
则中英文都必须补齐。
- 不要只改中文不改英文,也不要只改运行目录不改模板源。
## 插件菜单与路由加载规则
### 1. 插件菜单来源
插件侧边栏应来自**安装时导入的系统菜单**,不是前端自动扫描插件 `routes/index.ts`。
因此:
- 不要依赖 `frontend/admin/src/apps/{plugin}/routes/index.ts` 自动显示菜单。
- 插件安装后应由 `sys_menu` 数据决定导航是否出现。
### 2. 菜单 component 路径
菜单中的 `component` 应填写相对 `views` 的组件路径,并包含插件 key 目录段。
例如页面文件:
```text
frontend/admin/src/apps/article/views/article/content/index.vue
```
则菜单 component 应使用类似:
```text
/article/content/index
```
### 3. meta.module
安装导入菜单时,`app` 字段应默认使用插件名 `{plugin}`,以便运行时生成:
```text
meta.module = {plugin}
```
这样组件加载器才会到:
```text
frontend/admin/src/apps/{plugin}/views...
```
去查找页面。
## 防回退规则
这是插件开发最重要的回归规则。
### 1. 何时会回退
满足以下条件时,卸载再安装会回退:
- 你只改了 `frontend/admin/src/apps/{plugin}`。
- 模板源 `backend/plugin/{plugin}/resource/template/admin` 仍是旧版。
- 重装时安装器重新复制旧模板源。
### 2. 如何避免回退
每次插件前端修改完成后,必须检查:
- `index.vue` 是否同步到模板源。
- `schemas/index.tsx` 是否同步到模板源。
- `api/index.ts` 是否同步到模板源。
- `lang/zh-cn/zh.json` 与 `lang/en/en.json` 是否同步到模板源。
### 3. 最终交付规则
若用户要求“以后重装不要丢”,交付标准不是“当前页面能看”,而是:
- 运行目录正常。
- 模板源正常。
- 卸载后重装仍正常。
## 推荐开发顺序
1. 明确插件名、模块名、路由、权限码。
2. 建迁移、模型、DAO、Service、Validate、Controller。
3. 接好 Swagger 注解,先排除路由冲突。
4. 配置菜单与权限。
5. 在模板源下实现前端 `api + index.vue + schemas + lang`。
6. 安装插件,复制模板到运行目录。
7. 在运行目录联调。
8. 如联调时临时改了运行目录,最终再同步回模板源。
9. 卸载再安装,确认不回退。
## 验证清单
### 后端验证
- 启动后无 Swagger `Unable to merge @OA\Get()` 等冲突。
- CRUD 路由与权限码可正常访问。
- 列表接口返回 `items/total`,与 `Crud::formatNormal()` 对齐。
- 删除接口支持单个 `id` 或批量 `ids`,与项目基类能力一致。
### 前端验证
- 页面使用官方 `BasicCrud` 外观。
- 列表数据结构与 `BaseService` 返回兼容。
- 中英文切换无缺失 key。
- 菜单能正常打开到对应插件页面。
### 重装验证
- 卸载插件。
- 重新安装插件。
- 确认 `frontend/admin/src/apps/{plugin}` 被重新复制后,页面仍保持最新结构。
- 确认没有回退到旧 `el-table` demo 版本。
## 常见坑位
### 1. Swagger 冲突
原因:
- 新旧控制器共用了同一路径。
- 旧 `ArticleController` demo 接口未迁移。
处理:
- 迁移旧接口到 `legacy` 路径。
- 新 CRUD 控制器使用正式资源路径。
### 2. 重装后页面回退
原因:
- 只改运行目录,没改模板源。
处理:
- 以模板源为主,运行目录为辅。
### 3. 插件菜单显示异常
原因:
- 把插件前端 routes 自动扫描混进了静态路由。
- 未安装插件也显示菜单。
处理:
- 插件导航仅依赖后端导入菜单。
- 不把 apps 自动扫描结果混进后端菜单模式。
### 4. 模板目录编辑器报别名错误
现象:
- `@/components/crud`
- `@/locales`
- `@/enums`
原因:
- 模板源目录不是主前端 tsconfig 的真实编译入口。
结论:
- 这是编辑器静态提示,不等于安装后运行目录会构建失败。
- 但模板源文件内容仍要与运行目录保持一致。
### 5. 插件第二批模块可打开但接口 500
常见根因:
- Service 忘记显式导入 `core\base\BaseService`
- DAO 忘记显式导入 `core\base\BaseDao`
- Service 构造器手动 `new Dao()`,未按项目真实风格使用容器注入
- DAO 重写 `selectList()` 但方法签名与当前 `BaseDao` 不一致
处理:
- 优先对照同插件已可用一期模块的 DAO / Service 写法,不要只凭旧记忆生成。
- 修复顺序优先:`use` 导入 -> 构造注入 -> 方法签名 -> 关联字段 / validate。
### 6. 旧项目字段迁移后前端下拉不显示名称
常见根因:
- 模型没有提供 `name` 访问器
- `Crud::formatSelect()` 只能按 `title / name / real_name / id` 兜底取标签
处理:
- 为需要复用 `format=select` 的模型增加 `getNameAttribute()`,将实际显示字段映射到 `name`。
### 7. JSON 规则字段仅在后端可存,前端表单难以维护
处理建议:
- 后端 Validate 接受数组和 JSON 字符串双形态。
- 模型 `casts` 统一转为数组。
- 模板源 `schemas` 中优先使用 textarea 输入 JSON 文本,并同步中英文 placeholder。
### 8. Windows 下修改插件 Seeder / Install 后重装仍执行旧逻辑
现象:
- 已修改 `backend/plugin/{plugin}/resource/database/seeds/*.php` 或 `backend/plugin/{plugin}/Install.php`。
- 重新点击插件安装后,菜单等流程有变化,但 Seeder 结果仍像旧代码。
- 日志看不到新的种子插入 SQL。
常见根因:
- Windows 开发态 `config/process.php` 中 `process.monitor.constructor.options.enable_file_monitor` 被关闭。
- 运行中的 Webman 进程未重载,仍持有旧版类定义。
- `SeedTrait::runSeed()` 使用 `require_once`,同一进程内不会重新加载已声明 Seeder 类。
处理:
- 确认 `backend/config/process.php` 的 `enable_file_monitor` 在 Windows 开发态也开启。
- 修改插件 `Seeder / Install / Controller / Service / Dao` 后,若不确定是否已重载,先手动重启后端进程再验证。
- 排查“重装插件不生效”时,不要只盯安装接口日志,还要先确认当前进程是否加载了最新代码。
## 输出契约
当用户要求“完成一个插件开发”时,至少应交付:
- 后端 CRUD 可运行。
- Swagger 无冲突。
- 菜单、权限、路由一致。
- 模板源前端完整。
- 运行目录联调通过。
- 卸载再安装不回退。
若只完成运行目录修改,不能判定为插件开发真正完成。