安装、升级、卸载与启停
四个动作都在宿主前端里做,全过程不需要重启宿主。这一页讲"在哪点、点完会发生什么"; 每个动作后端逐步做了什么见 在部署里使用应用。
入口与权限
| 想做的事 | 页面 | 路由 | 前端源码 |
|---|---|---|---|
| 安装、市场内一键升级 | 应用市场 | /appmarket/market | plus-ui/src/views/appstore/market/index.vue |
| 升级、启用、停用、卸载 | 我的应用 → 已安装应用 | /myapps/installed | plus-ui/src/views/appstore/installed/index.vue |
| 看进度、步骤、失败原因 | 我的应用 → 应用任务 | /myapps/task | plus-ui/src/views/appstore/tasks/index.vue |
| 从工作台进应用 | 工作台「我的应用」卡片 | /index | plus-ui/src/views/workplace/index.vue |
权限:安装 app:install:add(或 app:install:edit),升级 / 启停 / 卸载 / 同步菜单 app:install:edit,
看任务 app:install:list。
接口先返回一个 taskId,过程在后台跑,任务记录在动作执行之前就已落库。
所以排查任何生命周期问题,第一站都是「应用任务」,见 任务中心怎么用。
一、全程不需要重启宿主
部署、安装、升级、卸载、停用、启用都不重启。每个应用有自己的类加载器与 Spring 子上下文, 装卸就是建 / 关那个上下文,宿主进程一直活着。
两个例外要说清:
| 例外 | 为什么 |
|---|---|
改了宿主自己的代码或配置(application.yml、签名公钥等) | 这些东西在启动期读入并打进 jar,必须重建宿主再启 |
改 app-market.plugin-runtime.enabled | 它由启动加载器读取,而加载器在 Spring 之前执行、读不到 yml —— 只能用 JVM 参数设置,改它必然要重启进程 |
二、安装
市场 → 选应用 → 安装 → 确认弹窗 → 装完自动启用(提示「安装成功,已自动启用」)。
确认弹窗会先说清三件事:会注册菜单并授权给本租户;过程与结果可在「我的应用 → 应用任务」里看; 带后端产物的应用需要它的插件已部署并被服务加载,否则会失败。
安装后端按固定顺序做四步:从市场拉包落位 → 执行迁移脚本 → 校验并加载后端产物 → 同步菜单与授权。 顺序是刻意的(先改表结构再切换代码),逐步细节见 在部署里使用应用。
某些失败会让接口直接抛异常,前端拿不到 taskId。但任务记录在动作执行之前就已落库,
所以日志一定在 —— 失败弹窗会直接把你送去「应用任务」。
依赖没装会怎样(这道闸在四步之前)
应用可以在清单里声明 dependencies(见 应用清单)。声明了就有三处表现:
| 位置 | 表现 |
|---|---|
| 市场卡片 | 必需依赖未满足时按钮是灰的,写着「依赖未满足」,悬停显示缺谁 |
| 详情页「简介」 | 顶部列出每个依赖与状态:已安装(绿)/ 未安装或已停用(红)/ 可选未安装(灰) |
| 安装 / 升级接口 | 未满足 ⇒ 直接拒绝,而且不会产生安装任务记录 |
拒绝时给的是一句能照着做的话,而不是一个 Java 堆栈:
应用「商城管理」依赖「会员管理」「支付管理」「即时通讯」,请先安装并启用它们,才能安装。依赖已装但被停用时会另说一句:停用会卸载它的插件后端(unload),依赖它的应用照样加载不了,
要去「我的应用」里先启用它。
装之前先点开应用详情看「简介」标签页顶部的依赖列表;也可以直接照上面那句报错里的名字去装。 平台不会自动帮你把依赖一起装上 —— 一次点击装一个应用是刻意的(自动安装要动的代码位置见 应用市场架构)。
三、升级会强制重新落位
「我的应用」列表里版本旁出现「可升级 x.y.z」时才显示升级按钮;市场卡片上也能一键升级到最新版。
相对安装,升级的行为差异是:
| 差异 | 说明 |
|---|---|
| 强制重新拉包落位 | 只要目标版本与当前版本不同,本机现有产物(按定义是旧版本)一律不复用 |
| 菜单按新版本清单幂等 upsert | 同名 menu_key 复用既有 menu_id,租户已分配的权限不失效 |
| 保留原启停状态 | 已停用的应用升级完仍是停用(状态迁移为 upgrading → disabled) |
「文件存在就跳过」只在同版本重装时成立。升级时若沿用旧产物,会同时踩两个静默坑:
前端还是旧版,包内 migrations/*.sql 根本不落位。
后端会直接拒绝:「当前已是该版本,无需升级」。想重装请走重新安装或控制台部署。
回到旧版本
从市场选一个更低的版本安装或升级即可,菜单会按旧版本清单同步回去。
但表结构不会回滚 —— 迁移脚本只做 up。回退前请自己确认旧代码能不能吃新表结构,
规则见 数据库迁移。
四、卸载:两种模式怎么选
卸载弹窗只给两个选项,默认落在可恢复的那条路:打开弹窗时就是「保留数据」, 「彻底删除」要把应用名完整敲一遍才亮确认按钮。
| 保留数据(默认) | 彻底删除 | |
|---|---|---|
| 本租户菜单授权 | 回收 | 回收(所有角色的,不只本租户) |
| 后端插件 | unload | unload |
菜单行(sys_menu) | 保留(重装即恢复可见) | 删除(含后代,如按钮) |
| 数据表 | 全部保留 | DROP TABLE,表名取自包内迁移脚本的建表语句 |
| 迁移记录 | 保留 | 删除(所以重装会重新建表) |
| 字典与站点配置 | 保留 | 删除 |
本机产物(后端 jar、前端目录、active-plugins.txt 记录) | 保留 | 删除(Windows 上 jar 被 JVM 占用时会如实报出删不掉) |
| 安装记录 | 保留,状态 uninstalled | 物理删除 |
| 防手滑 | 确认即可 | 要把应用名完整敲一遍 |
怎么选:
- 「暂时不用了」「以后可能还要装回来」「只是不想让人看到入口」→ 保留数据;
- 「确定不要了,而且要把表结构一起腾出来」→ 彻底删除(不可恢复)。
- 别人还在用就不动全局的东西。 菜单行、数据表、迁移记录、本机产物、字典与站点配置在一个部署内是 所有租户共享的。只要还有别的租户装着同一个应用,这些删除一律跳过,只在任务日志里说清楚。 判据会排除自己那条记录。
- 平台表前缀受保护。 只删迁移脚本声明过的表,表名命中
sys_/app_/gen_/flow_/act_/qrtz_/snailjob_/undo_log前缀时一律跳过并告警。
这两道闸的取舍见 多租户下的应用。
刻意不删的两样:任务记录(它是审计线索,代价是彻底删除后那些历史行显示「(应用已删除)」)、 对象存储里的文件(平台不知道它们属于谁,需要时自行清理)。
谁会被这次卸载影响
应用可以在清单里声明 dependencies。卸载 A 时,如果本租户还有已装应用依赖 A,
任务日志里会多一条提示(⚠️ 只是提示,不拦卸载):
注意:「商城管理」(mall) 依赖本应用。卸载后它们的相关功能会跟着不可用
(接口会回「应用不可用」);如需继续使用,请先安装回本应用,或先一并卸载它们。为什么只提示:卸载是用户明确要做的事,拦死会让"卸载顺序与依赖相反"这种正常操作做不了; 而且默认卸载保留数据,装回来即可恢复。依赖关系见 应用清单。
五、停用与启用
| 动作 | 后端真实行为 | 状态可迁移自 |
|---|---|---|
| 停用 | 只做 unload(关子 Spring 上下文与类加载器、注销接口),不动菜单行与授权 | enabled |
| 启用 | 先 load(把插件后端装回来)再补授权、放出菜单 | installed / disabled |
按钮的可用性刻意与后端的合法状态迁移表对齐:只有 installed / disabled 才显示「启用」按钮,
failed / uninstalled 的行不会出现它。
停用后菜单入口消失,是因为菜单过滤器按安装状态隐藏了它们,而不是授权被回收。 所以重新启用能立刻恢复,也不会破坏管理员此前的手工分配。
想彻底让入口和数据都消失,请用「卸载 → 彻底删除」。停用只适合"临时收起来"。
如果加载失败却"看起来启用了",就是一个「菜单在、点进去打不开」的坏状态。 这也是停用只隐藏入口、不删菜单的原因之一。
六、装完要刷新菜单才能看到
前端那份菜单树只在登录时生成一次。 安装 / 升级 / 启用 / 停用 / 卸载改的都是后端数据, 同一会话里那份旧树会继续生效 —— 表现就是「装完回工作台没有这个应用」「升级后页面还按旧路径解析」。
前端为此提供了重建入口 plus-ui/src/plugins/appmarket/menuRefresh.ts 的 refreshMenus()
(重新拉菜单并重建路由,幂等;失败不抛给调用方,只打 console.warn):
| 动作成功后自动重建菜单的地方 | 说明 |
|---|---|
| 应用市场页 | 安装成功、市场内一键升级成功后各调一次 |
| 已安装应用页 | 升级 / 启用 / 停用 / 卸载走同一个动作封装,任务成功即调一次 |
| 工作台 | 自愈:发现已装应用在旧树里找不到入口,先重建再判定 |
还是没出现时按顺序试:
- 手动刷新页面(等价于重新走一遍登录后的菜单生成);
- 去「应用任务」确认那次动作真的成功了 —— 失败时前端不会重建菜单;
- 菜单行本身漂移了(挂到了已不存在的菜单 ID 上),用
POST /app/install/{installId}/sync-menus(权限app:install:edit)重新同步,它是幂等的; 自愈逻辑丢弃死映射时会在任务日志里 WARN 出被丢弃的menu_key。
七、失败时先去哪儿
先看「应用任务」,不要去翻服务端日志。 顺序是:current_step(卡在哪一步)→
error_msg(服务端异常文本)→ 日志尾部(失败现场)。见 任务中心怎么用。
界面报错但任务日志里没线索,才是框架级原因(类加载、Spring 上下文、MyBatis 映射冲突), 去服务端日志,见 故障排查。
相关阅读
- 在部署里使用应用 —— 每个动作后端真实做了什么
- 任务中心怎么用 —— 进度、步骤、日志怎么看
- 装完以后:菜单与权限 —— 授权落到哪个角色上
- 应用生命周期 —— 五个动作的机制与顺序约束
- 数据库迁移 —— 迁移脚本的四条硬规矩
界面截图

我的应用 · 安装实例(截图占位)
/images/docs/my-apps.png建议 1920 × 1080,能看到版本、启停与卸载。