初始化本地仓库
This commit is contained in:
@@ -0,0 +1,536 @@
|
||||
---
|
||||
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 无冲突。
|
||||
- 菜单、权限、路由一致。
|
||||
- 模板源前端完整。
|
||||
- 运行目录联调通过。
|
||||
- 卸载再安装不回退。
|
||||
|
||||
若只完成运行目录修改,不能判定为插件开发真正完成。
|
||||
Reference in New Issue
Block a user