Wujies 无界平台
插件开发

可视化开发台

本文是 1.x 的归档文档(冻结于 1.x)。该分支只接收勘误修正, 内容基本不再变动;文中的源码路径、配置项与版本号均以 1.x 为准,不建议用于新项目。

新项目请查看 5.x 最新文档。已在 1.x 上运行的项目,本文仍是可靠依据。

宿主后台内置了一个插件开发页面,把「新建骨架、热加载、打开发包」三件事从命令行搬到了浏览器里。 入口:应用上架 → 插件开发(前端组件 appstore/dev/index,权限 app:dev:view)。

这一页说明它能看到什么、每个按钮做了什么、背后调用了哪些接口,以及它刻意不做什么。

它属于开发模式

页面依赖 app-market.plugin-runtime.dev-mode=true。开关关闭时后端不会注册相关接口与控制器, 页面打开会显示「开发模式未就绪」,并给出配置提示。绝不要在生产环境开启。

一次性配置

在宿主 application.yml:

app-market:
  plugin-runtime:
    dev-mode: true
    # 插件仓库绝对路径;留空时自动探测(见下表)
    plugin-repo: ""
    # 可选:热加载只允许这个目录下的 jar;留空 = 不限制
    dev-base-dir: ""

改完重启宿主。启动日志会打印开发模式告警、可加载路径是否受限,以及开发端点地址。

plugin-repo 留空时,服务端按以下候选顺序探测(相对于宿主进程工作目录):

plugins
wujies/wujies-plugins
../wujies-plugins
../wujies/wujies-plugins
../../wujies/wujies-plugins
../../../wujies/wujies-plugins
../plugins

命中条件是目录里同时存在 package.ps1 与 wujies-hello/。找不到时页面会提示,插件列表为空。

权限

超级管理员天然持有 *:*:*,无需额外配置。其它账号需要被授予:

权限能做什么
app:dev:view打开开发台、查看概览
app:dev:scaffold新建插件骨架
app:dev:reload从本地路径热加载 jar
app:dev:package组装开发包

菜单与按钮 SQL 在 wujies-platform/script/sql/update/plugin_dev_menu.sql(全新初始化已包含在 platform_init.sql 的 70300–70303)。按钮权限必须先加进菜单,才能在「角色管理」里勾选。

页面上的四块内容

1. 状态概览

字段含义
开发模式固定显示「已开启」;接口 404 时整页会变成配置提示
插件仓库实际解析到的绝对路径
Node宿主机器的 Node 版本;为空表示新建插件不可用
已加载运行时当前 JVM 里成功装载的插件数量

2. 仓库插件

扫描插件仓库下所有 wujies-*/app.json,每个插件一行:

列说明
插件清单里的 name、code、version
产物后端 jar 是否存在、前端 dist 是否存在、是否正在运行、清单是否有效
迁移脚本migrations/ 顶层 .sql 的数量
最后修改app.json 的修改时间
操作热加载 / 打开发包 / 命令

「命令」弹窗给出三条可直接复制的命令:后端构建、前端构建、命令行打开发包。 页面不会替你执行构建。

3. 已加载运行时

热加载成功后出现一行,数据来自 PluginRuntimeManager 的运行时快照:

字段说明
appCode应用编码
jar实际加载的 jar 绝对路径
接口 / 组件 / Mapper / 转换器本次装载注册的数量
类加载器PluginClassLoader[<code> <- <jar>],每次重载都是新实例

4. 三个动作

新建插件

填写编码、名称、简介、版本、是否生成前端,服务端在插件仓库执行:

node scripts/new-plugin.mjs --code <code> --name <name> --version <version>
     [--description <description>] [--no-frontend]

生成内容与命令行完全一致:后端最小 CRUD 工程、可选前端工程、app.json、迁移脚本, 并自动登记到仓库根 pom.xml。结果弹窗会显示两个目录、生成日志和可复制的下一步命令。

约束:编码 ^[a-z][a-z0-9]{1,31}$,名称 ≤ 40 字,版本语义化;目标目录已存在时直接拒绝, 不会覆盖已有代码。

热加载

弹窗预填 appCode 与该插件最新的 jar 路径,也可以填一个只含一个 jar 的目录。 调用:

POST /app/dev/reload
Content-Type: application/json

{ "appCode": "demo", "path": "C:/.../wujies-demo/target/wujies-demo-plugin-1.0.0.jar" }

行为是「先卸载上一代,再装载新 jar」,重复调用幂等。报告里会返回接口数、组件数、Mapper 数、 耗时与类加载器名。重启宿主后需要重新热加载一次。

打开发包

不需要再填表:服务端按 code 找到插件目录,把已经构建好的产物组装成一个 zip:

<插件仓库的上一级>/.plugin-dist/<code>/dev/<code>-<version>.zip
  ├── app.json
  ├── backend/<jar>
  ├── frontend/**          (存在前端 dist 时)
  └── migrations/*.sql     (存在迁移脚本时)

它不触发 Maven / pnpm 构建。如果页面提示找不到后端 jar 或前端 dist,先在终端执行 「命令」弹窗里的构建命令,或直接跑 package.ps1。

开发包不签名,适合 security.mode=warn/off 的部署或本地展开核对; enforce 模式下不能上传安装。需要安装或上架时,用打包应用里的正式流程。

接口一览

所有接口都只在 dev-mode=true 时存在,关闭时返回 404。

方法路径权限作用
GET/app/dev/overviewapp:dev:view状态、插件仓库、已加载运行时、插件列表
POST/app/dev/scaffoldapp:dev:scaffold从模板生成插件骨架
POST/app/dev/reloadapp:dev:reload按本地路径热加载 jar
POST/app/dev/packageapp:dev:package组装未签名的开发包

POST /app/dev/package 的请求体只有 { "code": "demo" };响应包含 zipPath、size、 entries、signed=false 与一句说明。

overview 返回结构(节选)
{
  "devMode": true,
  "devBaseDir": "",
  "pluginRepo": "C:/Users/.../wujies/wujies-plugins",
  "nodeVersion": "v24.18.0",
  "reloadEndpoint": "POST /app/dev/reload",
  "loaded": [
    { "appCode": "demo", "jar": "...", "endpoints": 6, "components": 12,
      "mappers": 1, "converters": 1, "classLoader": "PluginClassLoader[demo <- ...]" }
  ],
  "plugins": [
    { "dir": "wujies-demo", "code": "demo", "name": "演示应用", "version": "1.0.0",
      "valid": true, "jar": "...", "frontendDist": "...", "migrationCount": 1,
      "loaded": true, "buildCommand": "...", "frontendCommand": "...", "packageCommand": "..." }
  ],
  "hint": "热加载会绕过签名与包校验,只允许在开发环境开启。"
}

它不做什么

不做原因 / 后果
不执行数据库迁移迁移由安装 / 升级链路执行。热加载后新表仍不存在,接口会报缺表
不同步菜单菜单来自安装时对 app.json 的解析。改菜单后要走一次安装 / 升级
不部署前端产物前端是静态 ESM,构建到宿主 app-market-dist/remote-apps/<code>/ 后刷新即可
不校验签名、白名单、校验和开发端点绕过 AppPackageDeployService,这是刻意的,也是它必须只在内网使用的原因
不写 active-plugins.txt这份清单由部署链路维护;代价是宿主重启后要重新热加载
不构建代码页面只组装已有产物;Maven / pnpm 仍在终端执行
不覆盖已存在的插件目录新建时目录已存在会直接报错,避免误删开发中的代码

因此,一个完整的开发闭环是:

新建骨架 → 构建后端 → 热加载 → 验证接口
         → 构建前端 → 刷新页面
         → 首次安装一次(建表 + 菜单 + 落位)
         → 之后循环:改代码 → 构建 → 热加载 / 刷新

排查

现象原因处理
页面「开发模式未就绪」dev-mode 未开 / 未重启改 yml 后重启;确认 /app/dev/overview 返回 200
插件列表为空插件仓库没解析到看概览里的「插件仓库」;显式配置 plugin-repo
「新建插件」按钮不可见没有 app:dev:scaffold给角色授权,或使用超级管理员
新建失败:node 不存在宿主进程 PATH 没有 Node安装 Node 18+ 后重启宿主进程
新建失败:目标目录已存在同名插件已生成过换编码,或先手动移走旧目录
热加载按钮灰掉该插件还没有构建出 jar先执行后端构建命令
热加载报路径不存在jar 路径写错 / 用了相对路径填绝对路径;或指向 target 目录
热加载成功但接口 404新 jar 没包含该接口,或权限不足确认构建的是最新代码;检查接口权限
打开发包失败:找不到前端 dist前端未构建先构建前端,或该插件不生成前端
开发包上传被拒未签名,宿主 enforce用正式签名包上架,开发包只用于联调

更多运行时问题见故障排查;热加载的完整背景见 本地开发与联调方案。

相关阅读

目录