可视化开发台
本文是 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/overview | app:dev:view | 状态、插件仓库、已加载运行时、插件列表 |
| POST | /app/dev/scaffold | app:dev:scaffold | 从模板生成插件骨架 |
| POST | /app/dev/reload | app:dev:reload | 按本地路径热加载 jar |
| POST | /app/dev/package | app: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 | 用正式签名包上架,开发包只用于联调 |
更多运行时问题见故障排查;热加载的完整背景见 本地开发与联调方案。