Skip to content

打包与上架

这一章讲发布方要做的事:把应用打成可上传的包、签名、上架,最终让它在某个部署上真正跑起来。

前置阅读:应用清单 说明 app.json 每个字段的含义,生命周期 说明平台侧接住这些包之后做了什么。


一次完整的发布链路

① 生成密钥(只做一次,JDK 是硬前提)
   package.ps1 -Keygen
   └─ 产出 plugins/keys/private.pem(留发布方)+ public.pem(贴进宿主 yml)

② 打包 + 签名
   package.ps1 -Plugin ruoyi-xxx -SigningKey plugins\keys\private.pem
   └─ 构建前端 → 构建后端 → 组装包目录 → 签名 → 打 zip 并算校验值
   └─ 产出 dist-packages/<code>-<version>.zip + .package-info.json

③ 上传(只登记,不落位)
   POST /app/package/parse   multipart
   └─ 解包读清单 → 清单校验 → 安全检查(白名单 + 验签)→ 落对象存储 → 记录 URL/大小/MD5/SHA256

④ 保存版本
   POST /app/version
   └─ 登记版本行(含清单 JSON),状态 = 0 待发布

⑤ 发布
   POST /app/version/{versionId}/publish
   └─ 状态置 1 已发布,并回写 app_info.latest_version

⑥ 上架
   POST /app/console/{appId}/status?status=1
   └─ 应用状态置 1 已上架,市场里才能看到、才能被安装

⑦ 在部署里点「部署 / 安装」
   POST /app/version/{versionId}/deploy      ← 上架端「部署」按钮
   或 POST /app/install                      ← 市场页「安装」按钮
   └─ 下载 → 校验校验和 → 安全检查 → 解包落位 → (安装路径还要跑迁移)→ 加载插件 → 同步菜单

第 ⑦ 步是整条链路上最容易漏的一步

上传、保存、发布、上架都只是登记与状态变更,磁盘上什么都没有。少了「部署」这一步,应用在市场里看得见、装得上(状态正常),但后端 jar 不在宿主 classpath 上、前端产物不在 /remote-apps/ 下 —— 表现是接口返回「应用未安装或未启用」、菜单点开是「远程视图不可用」。

上传完成 ≠ 生效。打包 → 上传 → 发布 → 部署四步一件都不能少。


三个动作的区别

这三个动作经常被混为一谈。它们的执行者、是否跑迁移、是否热加载都不一样:

上传发布部署
谁做发布方(上架端)发布方(上架端)上架端操作者,「部署」按钮
接口POST /app/package/parsePOST /app/version/{id}/publishPOST /app/version/{id}/deploy
权限app:console:addapp:console:editapp:console:edit
干了什么解包读清单 → 校验 → 安全检查 → 落对象存储 → 记 URL/大小/MD5/SHA256版本状态置 1 已发布,回写 app_info.latest_version下载包 → 校验校验和 → 安全检查 → 解包落位到宿主磁盘热加载后端插件
动磁盘吗❌ 只落对象存储❌ 只改状态落位到 plugin-dirfrontend-dir
跑迁移脚本吗❌(控制台「部署」不跑迁移,见下)
热加载吗✅ 卸载旧插件 → 用新 jar 建类加载器与子上下文 → 注册接口
前端用重启吗————不用,刷新页面即可(entry?v=<版本> 破缓存)
后端用重启吗————热加载成功就不用(响应 hotLoaded: true / restartRequired: false);热加载失败才要(响应会带 hotLoadNote 说明原因)

「部署」按钮不跑迁移脚本 —— 这是刻意的

安装/升级走的是 deploy(versionId, false)只落位),加载交给排在迁移之后的步骤;而控制台「部署」按钮走单参版本(落位即加载),因为它不跑迁移脚本

所以:给已有部署补产物用「部署」是对的;要变更表结构必须走安装/升级。 顺序保证是 落位 → 变更表结构 → 切换代码 —— 反过来的话,新代码在启动时机读一个还没建的列就会炸。


包内目录结构规范

zip 的根目录就是包内容,没有多余的一层目录

<code>-<version>.zip
├── app.json                                 清单(唯一事实来源)
├── backend/
│   └── <artifactId>-<version>.jar           后端产物,文件名必须与 assets.backend 一致
├── frontend/                                前端产物整棵树
│   ├── index.js                             远程 ESM 入口(通常 300KB+)
│   └── assets/ruoyi-vue-plus.css            自带样式
├── migrations/                              建表/改表脚本(有才打)
│   ├── 01-create-<app>-tables.sql
│   └── 02-backfill-xxx.sql
└── signature.txt                            分离式签名(覆盖除它以外的全部文件)

产物校验清单:

检查项期望
app.json 位置zip 根目录,不是 <code>-<version>/app.json
后端 jar 名app.jsonassets.backend 最后一段完全一致
前端文件frontend/index.js + frontend/assets/*
*.map一个都没有(打包脚本自动剔除)
signature.txt在包根(mode=enforce 时必须有)
清单 code与上架端那个应用的 app_code 一致(上传接口会校验)
清单 version与要发布的版本号一致

包结构不是随便定的,白名单会拒

包根只允许 app.jsonsignature.txt 以及 backend/frontend/migrations/ 三个目录;backend/ 下只允许 .jar 且只能有清单声明的那一个;migrations/ 下只允许 .sqlfrontend/ 下只允许白名单内的静态资源扩展名,且 .jsp / .php / .war / .jar / .exe / .sh / .properties / .xml无论如何都拒绝(前端目录就是宿主的静态资源目录)。


前后端必须打成一个包的三个理由

这不是「顺手」的决定,是为了避开三类无法挽回的错误:

  1. 版本一致性 —— 分开传迟早出现「前端 1.0 + 后端 0.9」的错配,而且运行期极难排查:页面能打开、接口也返回 200,只是行为对不上,因为没有哪一层会报错。
  2. 一次签名 —— 签名是分离式的 signature.txt,它覆盖包内除自己以外的全部文件。MD5/SHA256 与签名只有覆盖整包才有防篡改意义;分开传就等于有两份可以各自被替换的产物。
  3. 卸载与回滚能整体回收 —— 「彻底删除」要按 appCode 回收后端 jar、前端目录与 active-plugins.txt 记录;降级回旧版本也要整体换一套。分散成多个包之后,「装的是哪个组合」没有任何地方记录。

顺带一个合法形态要记住:纯前端应用(清单里只写 assets.frontend、不写 assets.backend)是受支持的,包里只有 app.json + frontend/。它适合「只提供聚合视图、读别的应用现成接口」的场景。打包时跳过后端构建(连 JDK / Maven 都不需要),但签名仍要 JDK —— 签名工具是 Java 写的。


打包前检查清单

#检查怎么看
1app.jsoncode / version / assets 三者自洽assets.backend 的文件名 = <artifactId>-<version>.jarassets.frontend 指向 frontend/index.js
2菜单树含按钮type: "F"漏写按钮的后果是双向的且都不报错:全新部署装完没有按钮;彻底删除删不掉按钮(留成孤儿)
3建表脚本在 <插件>/migrations/(或 -MigrationPath 指得到)它不只是「安装时建表」,还是卸载时删表的唯一依据。包里没有建表脚本 → 那个应用的「彻底删除」一张表都删不掉
4已有部署上要补按钮时配了 seed SQL否则升级会建出一套重复按钮(把新 menu_key 映射到既有按钮 ID)
5前端工程构建通过,且自检干净npm run check:remote(模板组件解析 + 白名单名字 + 产物里有没有 process.env 残留);npm run check:globals(产物里有没有「宿主自动导入宇宙」里未绑定的标识符);npm run check:runtime(产物真正 import 的名字与垫片导出对账)
6远程产物目录名等于 codeapp-market-poc/<code>-appapp-market-dist/remote-apps/<code>、后端落位目录、宿主拼出的入口 URL 四处是同一个约定
7版本文案格式能过后端校验后端清单校验要求 x.y.z,比打包脚本严格 —— 见 上架与发布
8私钥就绪plugins/keys/private.pem 存在(脚本会自动发现),或显式传 -SigningKey;本机没有会自动 keygen
9宿主配的公钥与私钥成对打包脚本会顺手比对 application.ymlpublic-key,一致打 ,不一致/为空会警告
10改了表结构就新增脚本,不改老脚本已执行过的脚本内容不能再改(按 (app_code, script) 比对摘要),否则升级会被拒绝

之后按 打包应用密钥与签名上架与发布 走。


相关阅读

基于 MIT 协议开源 · 文档与官网由源码生成