Files
MrleeAdmin/.windsurf/skills/cross-plugin-fullstack-development/SKILL.md
T
2026-08-31 11:33:59 +08:00

16 KiB
Raw Blame History

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.
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. 模板源与运行目录

模板源目录:

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. 正确维护顺序

修改插件前端时遵循:

  1. 先完成后端开发与接口联调。
  2. 后端完成后,直接在 backend/plugin/{plugin}/resource/template/admin 下开发前端模板源。
  3. 不要把 frontend/admin/src/apps/{plugin} 作为正式开发目录。
  4. 完成模板源后,通过插件安装或复制流程生成运行目录并验证结果。
  5. 仅当用户明确要求排查运行态问题时,才临时查看运行目录,但不要把它作为主维护对象。

开发前检查

1. 确认插件真实目录结构

以 article 为例:

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 根目录变更。

插件目录与产物约定

后端

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 = 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

控制器通常放在:

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

标准页面入口应为:

  • 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 通常放在:

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.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 目录段。

例如页面文件:

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. 最终交付规则

若用户要求“以后重装不要丢”,交付标准不是“当前页面能看”,而是:

  • 运行目录正常。
  • 模板源正常。
  • 卸载后重装仍正常。

推荐开发顺序

  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 无冲突。
  • 菜单、权限、路由一致。
  • 模板源前端完整。
  • 运行目录联调通过。
  • 卸载再安装不回退。

若只完成运行目录修改,不能判定为插件开发真正完成。