Skip to content

插件开发 ​

这一页回答一个问题:在 Wujies 无界平台上加一个业务应用,我该动哪里、顺序是什么。

三个前提要先说清(它们是这一章所有页面的共同背景):

  1. 平台极小,应用外置:新增业务模块 不改平台一行代码。这是"契约随插件走"改造的全部意义。
  2. 一个应用就是一个自包含工程:实体 / BO / VO / Mapper 接口 与 Mapper XML / Service 接口与实现 / controller / open / listener / task / app.json / migrations/ 全在它自己那个目录里。 平台里 wujies-modules/*-api 的数量是 0,平台对 cn.wujies.<app>.* 的 Java 引用也是 0。
  3. 开发者面对两套工程:后端插件(Maven,在插件仓库)+ 前端远程应用(pnpm workspace,同在插件仓库)。

不要按"契约放 *-api、实现放 plugins/*"来写

那是 2026-09-30 之前的旧架构,已被整个推翻。连带作废的还有两条: 「Mapper XML 必须放 *-api」和「靠宿主 mapperLocations 把插件里的 XML 扫进来」。

现状是:XML 随插件 jar 走,由 PluginMapperRegistrar 在插件加载时动态注册进宿主 SqlSessionFactory。动态注册刻意不用 classpath*:mapper/**/*Mapper.xml —— 那个写法是拿宿主类加载器扫整个 classpath,会把宿主的映射文件再捞一遍, 撞 Result maps collection already contains value,宿主直接起不来。

权威说法见必须遵守的不变量。

四个仓库/目录:谁是谁 ​

名字是什么别混淆
wujies-platform/后端平台(Java / Spring Boot):wujies-admin 启动模块、wujies-common-*、wujies-modules/*这里没有 *-api 契约模块
wujies/wujies-plugins/插件仓库:每个应用一个 wujies-<app>/(后端插件工程)+ frontend/<code>-app/(前端工程)+ package.ps1 + sandbox/打包器与插件本体都在这里,平台仓库里没有 plugins/
plus-ui/宿主前端(Vue 3 / Vite)它的 package.json 里 name 写的是 wujies-admin —— 名字会骗人,认目录
website/本站(VitePress 文档站)—

⚠️ wujies-admin 这个名字在三处含义不同,读任何文档时按上下文判断:

写法实际指判据
wujies-platform/wujies-admin/后端启动模块有 pom.xml / src/main,产物 wujies-admin.jar
工作区顶层的 wujies-admin/前端产物的落位目录里面只有 app-market-dist/,没有 package.json
plus-ui/宿主前端仓库有 vite.config.ts / src/,但 package.json 的 name 是 wujies-admin

一个应用长什么样 ​

后端插件工程(以范例应用 hello 为准,wujies/wujies-plugins/wujies-hello/):

wujies-hello/
├── app.json          应用清单:code / version / assets / menus   ← 唯一权威描述
├── pom.xml           后端工程(parent = wujies-plugins-parent)
├── migrations/       建表 / 种子数据 / 字典 / 站点配置(随包走)
│   ├── 01-create-hello-tables.sql
│   ├── 02-create-hello-seed-data.sql
│   └── 03-create-hello-website-config.sql
└── src/main/java/cn/wujies/hello/
    ├── domain/           实体(也在插件里)
    ├── mapper/           Mapper 接口(自定义 SQL 的 XML 放 src/main/resources 下)
    ├── service/ + impl/  Service 接口与实现
    ├── controller/       管理端接口
    ├── open/             对外开放接口(`/open/<app>/**`)
    └── task/             定时任务(`@Scheduled` / SnailJob `@JobExecutor`)

前端远程应用(wujies/wujies-plugins/frontend/hello-app/):

frontend/hello-app/
├── package.json      name = @wujies-plugins/hello-app,scripts.build = vite build
├── vite.config.ts    唯一的工程配置:defineRemoteApp({ appCode, root, ... })
└── src/
    ├── index.ts      入口契约:默认导出 install(runtime),返回 { manifest, views }
    ├── runtime.ts    宿主运行时持有者
    └── views/        页面(DemoHome / DemoPluginDev / DemoAbout)

命名约定(四处必须一致):目录名、vite.config.ts 的 appCode、后端 app.json 的 code、 菜单里 component 的 remote:<code>/<viewKey> 前缀,都是同一个 <code>。

范例应用 hello:五种写法都有真实源码 ​

要照着做时,优先打开这些文件,而不是照着文档猜:

我想知道打开这个文件验证端点
插件怎么调宿主的系统服务cn.wujies.hello.controller.HelloDemoHostController(ConfigService / OssService / LoginHelper)GET /hello/demo/host/whoami
插件怎么调另一个插件cn.wujies.hello.controller.HelloDemoCrossPluginController(注入兄弟 Mapper vs 走 SDK 定位器)GET /hello/demo/cross/service?userId=1
怎么从插件开放接口给别人cn.wujies.hello.open.HelloDemoOpenControllerGET /open/hello/greet
定时任务的两种写法cn.wujies.hello.task.HelloDemoScheduledTask / HelloDemoSnailJobTask服务端日志 / SnailJob 控制台
清单怎么写wujies-hello/app.json菜单能否点开
表结构与种子数据怎么随包走wujies-hello/migrations/01..03-*.sqlGET /hello/item/list

前端页面在菜单**「Hello 示例 → 插件开发范例」**下,对应 frontend/hello-app/src/views/DemoPluginDev.vue。 更细的两栏对照见跟着 hello 读源码。

从零做一个应用:五步 ​

  1. 复制 hello:wujies-hello → wujies-<code>。
  2. 改四处:pom.xml 的 artifactId、app.json 的 code / name / version / menus、 Java 包名 cn.wujies.hello → cn.wujies.<code>、迁移脚本里的表名前缀。
  3. 挂进 reactor:插件仓库根 pom.xml 的 <modules> 加一行。 ⚠️ 漏了这步的表现是"改了代码没生效",很难第一时间想到。
  4. 写迁移脚本:migrations/01-create-<code>-tables.sql 建表,02-* 种子数据, 需要字典 / 站点配置再各加一条 —— 见数据库迁移与种子数据与站点配置。
  5. 建前端工程:复制 frontend/hello-app → frontend/<code>-app,改 package.json 的 name 与 vite.config.ts 的 appCode,页面键与 app.json 里 component 的视图键对齐。

构建、打包、验证 ​

powershell
# 1) 平台构件装进本地仓库(插件编译期要用;平台升级后要重跑)
#    在 wujies-platform/ 下:
mvn -B -pl wujies-admin -am -DskipTests install

# 2) 只构建一个插件
#    在插件仓库根 wujies/wujies-plugins/ 下:
mvn -B -pl wujies-<code> package -DskipTests

# 3) 打成可上架的应用包(脚本会自己构建前端 + 后端 → 组装 → 签名 → 出 zip)
cd <插件仓库根>
.\package.ps1 -Plugin wujies-<code> -SigningKey keys\private.pem

# 4) 上架:控制台「应用上架 → 版本管理 → 安装包上传」→ 发布 → 上架 → 客户端部署

打包器参数(-FrontendProject / -MigrationPath / -SkipFrontend / -NoSign 等)见 打包应用;签名与密钥纪律见密钥与签名。

验证的顺序(能一步定位"装载问题"还是"数据问题"):

打什么验的是什么期望
GET /hello/greet?name=无界路由挂上了、依赖注入通了(不碰库)code=200
GET /hello/item/list建表脚本执行了、Mapper 装配了、SQL 真打到库上code=200,2 条种子数据
GET /open/hello/greet(不带 token)/open 开放链路与 @SaIgnore200
GET /hello/item/list(不带 token)插件路由也过鉴权(不能裸奔)401

不改平台、不装市场也能验:插件仓库自带沙箱 sandbox/(H2 内存库,不连 MySQL / Redis / RabbitMQ), 用法见本地联调。

五个必须记住的约定 ​

  1. 契约随插件走:实体 / BO / VO / Mapper 接口与 XML / Service 接口全在插件模块自己的 src/ 下; 平台里没有 *-api 契约模块,也不要试图新建一个。插件之间要对方契约时, 依赖兄弟插件构件(例如 wujies-member-plugin,scope 为 provided)。
  2. 平台依赖一律 provided:Spring / MyBatis / wujies-common-* / wujies-common-plugin-sdk / 兄弟插件构件都如此。打进插件就会出现"同一个接口两个 Class 对象",注入与类型判断全部失效。 插件仓库父 pom 的 dependencyManagement 已经统一设好,不要在子模块里改它。
  3. 菜单只能在 app.json 的 menus 里声明,不要手写 INSERT INTO sys_menu。 type: "F" 的按钮也必须写,否则全新部署装完没有按钮,"彻底删除"时也删不掉它们。 详见菜单与权限。
  4. 表结构与种子数据随包走:脚本放 migrations/,文件名排序即执行顺序, 必须幂等,已执行过的脚本内容不能再改(改了会被摘要校验拒绝执行,要变更就新增脚本)。 这些脚本同时是"彻底删除时 DROP 哪些表"的唯一依据。
  5. 宿主看不到插件:Spring 父子关系是单向的 —— 插件能注入宿主,宿主不能注入插件。 宿主侧要用插件服务,只能走 SPI 或服务定位器代理;绝不要在宿主上下文里注册与插件实现 同类型的 bean(哪怕只是个转发代理),那会让插件的控制器启动即报 expected single matching bean but found 2。

这一章的其余页面 ​

我想…看这里
扫一遍不能违反的规矩必须遵守的不变量
搞清 app.json 每个字段应用清单 app.json
写后端工程后端工程
写前端远程应用前端远程应用
声明菜单与权限菜单与权限
加表 / 改表数据库迁移
让应用装完就有字典和站点配置种子数据与站点配置
本地跑起来调试本地联调
照着真实源码写跟着 hello 读源码
把宿主里已有的业务模块抽成应用从宿主模块抽成应用(SOP)
改平台代码(贡献者)开发指南
打包与上架打包应用 / 上架与发布
排查装不上 / 起不来故障排查

相关阅读 ​

我们坚信,即使再复杂的技术,也可以用清晰、干练、易懂的文字描述清楚。如果你在阅读时有难以理解的章节,那一定是我们还没有优化好它 —— 欢迎反馈。