16 KiB
name, description, globs
| name | description | globs | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| cross-plugin-fullstack-development | 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. |
|
Madong 插件全流程开发技能
用于开发类似 article 这样的完整插件,覆盖从目录结构、后端 CRUD、Swagger、菜单导入、前端模板源、安装复制机制、国际化,到卸载重装后的防回退验证。
适用场景
- 新建一个完整业务插件。
- 为已有插件补齐后台 CRUD、前台模板、菜单和语言文件。
- 修复插件卸载再安装后的页面回退、菜单异常、Swagger 冲突。
- 将一次真实插件开发过程沉淀为可复用工作流。
总原则
- 先核对项目真实实现,再使用 skill 或模板。
- 插件前端的权威源目录是
backend/plugin/{plugin}/resource/template/admin,不是运行目录。 - 安装后复制出来的
frontend/admin/src/apps/{plugin}属于运行产物,不作为正式开发目录;若只改这里,卸载重装会回退。 - 后端 Swagger 路由必须唯一,旧演示接口不可与新 CRUD 路由重复。
- 插件菜单以安装导入的系统菜单为准,不依赖前端自动扫描插件路由生成侧边栏。
- 前后端路径、权限码、菜单 component、i18n key 必须一致。
- 插件 skill 若与真实项目不一致,先更新 skill 再继续生成或修改。
- 插件开发顺序默认采用 template-first:后端完成后直接开发
resource/template/admin,不再单独维护运行目录。
先理解插件安装与复制机制
1. 模板源与运行目录
模板源目录:
backend/plugin/{plugin}/resource/template/admin/
安装后目标目录:
frontend/admin/src/apps/{plugin}/
当前项目会在插件安装时通过 core\plugin\traits\TemplateTrait::copyTemplateDir('admin')
把模板源整体复制到前端运行目录。
2. 关键结论
- 改
frontend/admin/src/apps/{plugin}只会影响当前运行结果。 - 改
backend/plugin/{plugin}/resource/template/admin才能影响未来安装结果。 - 只要模板源还是旧版,卸载再安装就会把旧版重新覆盖回来。
3. 正确维护顺序
修改插件前端时遵循:
- 先完成后端开发与接口联调。
- 后端完成后,直接在
backend/plugin/{plugin}/resource/template/admin下开发前端模板源。 - 不要把
frontend/admin/src/apps/{plugin}作为正式开发目录。 - 完成模板源后,通过插件安装或复制流程生成运行目录并验证结果。
- 仅当用户明确要求排查运行态问题时,才临时查看运行目录,但不要把它作为主维护对象。
开发前检查
1. 确认插件真实目录结构
以 article 为例:
backend/plugin/article/
├── app/
├── config/
├── resource/
│ ├── menu/
│ ├── template/admin/
│ └── ...
至少核对:
app/adminapi/controllerapp/service/adminapp/daoapp/modelapp/validate或app/adminapi/validateresource/template/admin/apiresource/template/admin/viewsresource/template/admin/langresource/menu/admin.phpconfig/translation.phpconfig/route.php
2. 确认参考实现
优先寻找以下真实参考:
- 同类已完成插件
- 主项目现有
BasicCrud页面 - 主项目已接好 Swagger 的
Crud控制器 - 现有插件菜单定义
- 现有插件语言文件加载方式
3. Windows 开发环境检查
在 Windows 下开发插件时,注意新插件目录创建本身未必会立即触发外层监控重启。
如果新插件接口返回 404,需要核对监控是否覆盖 backend/plugin 根目录变更。
插件目录与产物约定
后端
backend/plugin/{plugin}/app/
├── adminapi/controller/
├── adminapi/validate/
├── api/controller/
├── dao/
├── model/
├── schema/request/
├── schema/response/
└── service/admin/
模板源前端
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
安装后前端运行目录
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. 数据库迁移
路径:
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 = 0int $limit = 0bool $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
控制器通常放在:
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. 路由与权限码
建议保持如下映射:
GET /{plugin}/{resource}
GET /{plugin}/{resource}/{id}
POST /{plugin}/{resource}
PUT /{plugin}/{resource}/{id}
DELETE /{plugin}/{resource}/{id}
要求:
addAuth、editAuth、removeAuth、viewAuth与后端权限码一致。- 菜单按钮权限码与控制器
Permission属性一致。 - 不要出现前端权限码、后端权限码、菜单权限码三套不一致。
前端模板源开发流程
1. 后端完成后直接开发模板源
插件前端正式维护目录:
backend/plugin/{plugin}/resource/template/admin/
后端完成后,直接在该目录开发插件前端。
默认不要单独开发 frontend/admin/src/apps/{plugin}。
运行目录只作为安装产物和验证结果使用。
2. index.vue 统一使用官方 BasicCrud
标准页面入口应为:
BasicCruduseCrudcrudSchema
不要继续保留插件 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 通常放在:
backend/plugin/{plugin}/resource/template/admin/api/index.ts
要求:
- 使用
BaseService生成标准 CRUD Service。 - 仍可保留兼容旧页面的
pluginApi包装方法。 baseUrl必须与后端控制器路径一致。- 自定义只读接口可继续使用
request.get()封装。 - 如运行目录为调试临时新增了 API 类型、Service 或语言键,最终必须同步回模板源,否则插件重装后会回退。
5. i18n 语言文件
路径:
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.indexmodules.*.search.*modules.*.form.*modules.*.columns.*则中英文都必须补齐。
- 不要只改中文不改英文,也不要只改运行目录不改模板源。
插件菜单与路由加载规则
1. 插件菜单来源
插件侧边栏应来自安装时导入的系统菜单,不是前端自动扫描插件 routes/index.ts。
因此:
- 不要依赖
frontend/admin/src/apps/{plugin}/routes/index.ts自动显示菜单。 - 插件安装后应由
sys_menu数据决定导航是否出现。
2. 菜单 component 路径
菜单中的 component 应填写相对 views 的组件路径,并包含插件 key 目录段。
例如页面文件:
frontend/admin/src/apps/article/views/article/content/index.vue
则菜单 component 应使用类似:
/article/content/index
3. meta.module
安装导入菜单时,app 字段应默认使用插件名 {plugin},以便运行时生成:
meta.module = {plugin}
这样组件加载器才会到:
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. 最终交付规则
若用户要求“以后重装不要丢”,交付标准不是“当前页面能看”,而是:
- 运行目录正常。
- 模板源正常。
- 卸载后重装仍正常。
推荐开发顺序
- 明确插件名、模块名、路由、权限码。
- 建迁移、模型、DAO、Service、Validate、Controller。
- 接好 Swagger 注解,先排除路由冲突。
- 配置菜单与权限。
- 在模板源下实现前端
api + index.vue + schemas + lang。 - 安装插件,复制模板到运行目录。
- 在运行目录联调。
- 如联调时临时改了运行目录,最终再同步回模板源。
- 卸载再安装,确认不回退。
验证清单
后端验证
- 启动后无 Swagger
Unable to merge @OA\Get()等冲突。 - CRUD 路由与权限码可正常访问。
- 列表接口返回
items/total,与Crud::formatNormal()对齐。 - 删除接口支持单个
id或批量ids,与项目基类能力一致。
前端验证
- 页面使用官方
BasicCrud外观。 - 列表数据结构与
BaseService返回兼容。 - 中英文切换无缺失 key。
- 菜单能正常打开到对应插件页面。
重装验证
- 卸载插件。
- 重新安装插件。
- 确认
frontend/admin/src/apps/{plugin}被重新复制后,页面仍保持最新结构。 - 确认没有回退到旧
el-tabledemo 版本。
常见坑位
1. Swagger 冲突
原因:
- 新旧控制器共用了同一路径。
- 旧
ArticleControllerdemo 接口未迁移。
处理:
- 迁移旧接口到
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 无冲突。
- 菜单、权限、路由一致。
- 模板源前端完整。
- 运行目录联调通过。
- 卸载再安装不回退。
若只完成运行目录修改,不能判定为插件开发真正完成。