升级与补丁
本文是 1.x 的归档文档(冻结于 1.x)。该分支只接收勘误修正, 内容基本不再变动;文中的源码路径、配置项与版本号均以 1.x 为准,不建议用于新项目。
新项目请查看 5.x 最新文档。已在 1.x 上运行的项目,本文仍是可靠依据。
升级这件事有两条互不相干的线:平台(宿主)自身的版本,和应用(插件)的版本。两者不会互相触发 —— 升应用不会改平台表,升平台也不会碰应用表。本页解决三个具体问题:改了什么要敲哪条命令、已经装着的实例怎么让改动生效、迁移脚本在这两条线里的位置。
前提是平台与插件仓库的边界(见 /design/repo-layout):平台里没有 *-api 契约模块,实体 / BO / VO / Mapper 接口与 Mapper XML / Service / controller / listener / task / app.json / migrations 全在插件仓库 wujies/wujies-plugins/wujies-<app>/ 下,平台对 cn.wujies.<app>.* 的 Java 引用为 0。所以升级一个应用不需要动平台一行,这也是两条线能彻底分开的原因。
两条升级线
| 平台(宿主) | 应用(插件) | |
|---|---|---|
| 谁负责 | 你(运维) | 平台自动(你只点「升级」) |
| 产物 | wujies-admin.jar + 前端 dist + 平台 SQL | 应用包(后端 jar + 前端产物 + migrations/) |
| 怎么升 | 换 jar / 重建镜像 → 重启;表结构按需执行平台补丁脚本 | 上架端发布新版本 → 本机「我的应用 → 升级」 |
| 表结构 | 手工执行 script/sql/update/ 下的脚本 | 升级时自动执行包内 migrations/*.sql |
| 是否自动 | ❌ 不会自动 | ✅ 落位 → 迁移 → 热加载,全程不重启 JVM |
| 失败影响 | 应用起不来 / 接口 500 | 单个应用的升级任务失败,平台本身不受影响 |
1Panel 应用包(deploy/tools/build-1panel.ps1 产出,装的是平台自己)与 业务应用包(package.ps1 产出,上架到应用市场里的 mall / gallery / …)是两回事。本节讲的是前者;业务应用包的升级见下面的「应用升级」。
平台升级
路线一:jar 直跑
先停掉正在跑的实例 —— 运行中的 JVM 会锁住 jar,mvn install 写目标文件会失败。
# 1) 存量库补结构:只跑这次发布需要的脚本(全新库跳过,见下面的「补丁脚本从哪来」)
mysql -h <host> -u <user> -p <db> < script/sql/update/app_market_schema_history_fix.sql# 2) 重新打包
$env:JAVA_HOME = "C:\Users\<you>\.jdks\corretto-23.0.2"
$mvn = "C:\Program Files\JetBrains\IntelliJ IDEA 2025.3.3\plugins\maven\lib\maven3\bin\mvn.cmd"
& $mvn -pl wujies-admin -am -B install -DskipTests
# 3) 启动(脚本会先清掉上一版残留的插件 jar,再启动)
powershell -NoProfile -ExecutionPolicy Bypass -File script\prune\start-host.ps1 -Build-Build可以省掉(第 2 步已手工打包过);script\prune\start-host.ps1先跑同目录的prune-plugins.ps1是有顺序讲究的:-Dloader.path只加载目录根下的 jar,同appCode两个版本同时在位会撞重复的 mapper XML。现在启动瞬间的loader.path与运行期一样按active-plugins.txt过滤,残留 jar 不再致命,但清理仍然是推荐动作(且 Windows 上被占用的 jar 只能停服务后清);- 全部启动参数见 /deploy/backend。
app-market.security.public-key、app-market.security.mode、目录与形态开关都是启动期从 jar 内 yml 读入的。只改文件不重建、不重启,等于没改 —— 换钥后会继续用旧公钥拒掉你刚签出来的包。见 /deploy/configuration。
路线二:1Panel 应用包(什么时候要重建镜像)
包的构成决定了「哪类改动要重建镜像」:SQL / data.yml / compose 是包里的文件,jar 与前端 dist/ 是打进镜像的。
| 你改了 | 重新打包 | 重建镜像 | 同步到主机 | 已装实例怎么生效 |
|---|---|---|---|---|
初始化 SQL(script/sql/**)、应用目录种子 | ✅ -SkipFrontend -SkipBackend | ❌ 不用 | 换实例目录里的 sql/files/*.sql(bind mount) | 删标记 + 重启(见下一节) |
安装表单(gen-appstore-data.mjs → data.yml)、README / logo | ✅ 同上 | ❌ | 换文件 + 面板「本地应用 → 同步」 | 重新安装,或改实例的「参数」 |
| compose(端口 / 挂载 / 环境变量) | ✅ 同上 | ❌ | 换 docker-compose.yml | docker compose up -d |
后端 Java(wujies-admin.jar) | ✅ 默认(会跑 maven) | ✅ docker build backend | 同步 docker/ + 重建镜像 | 容器重建即生效 |
| 前端(宿主前端仓库源码) | ✅ 默认(会跑 vite) | ✅ docker build frontend | 同上 | 同上 |
deploy/docker/*/entrypoint.sh | ✅ 同上 | ✅ 对应那个镜像 | 同上 | 同上 |
| 调度中心 / 监控中心的 Java | ✅ 不加 -SkipBackend | ✅ 各自 docker build | 同上 | 同上 |
# 只改 SQL / data.yml / compose(几十秒,不重建镜像)
powershell -ExecutionPolicy Bypass -File wujies-platform\deploy\tools\build-1panel.ps1 -SkipFrontend -SkipBackend
# 只改了后端 Java
powershell -ExecutionPolicy Bypass -File wujies-platform\deploy\tools\build-1panel.ps1 -SkipFrontend
# 全量(前端 vite build --mode docker + 后端 mvn install + 组装 + tar.gz)
powershell -ExecutionPolicy Bypass -File wujies-platform\deploy\tools\build-1panel.ps1主机侧(只有镜像变了才需要 docker build,标签必须与 compose 里的 image: 一致):
tar -xzf wujies-platform-1panel-5.6.3.tar.gz -C /root/pkg
cd /root/pkg/docker/backend && docker build -t wujies-platform/backend:5.6.3 .
cd ../frontend && docker build -t wujies-platform/frontend:5.6.3 .
cd ../snailjob && docker build -t wujies-platform/snailjob-server:5.6.3 .
cd ../monitor && docker build -t wujies-platform/monitor-admin:5.6.3 .
# 应用包同步进面板(走 tar 解包,别用 scp 直传资源目录)
cp -r /root/pkg/apps/* /opt/1panel/resource/apps/local/
chmod -R a+rX /opt/1panel/resource/apps/local/wujies-platform-*
# 然后:面板 → 应用商店 → 本地应用 → 同步- ⚠️ 改版本号要同时动四处:
build-1panel.ps1的$Version、包内5.6.x/目录名、compose 里的镜像 tag、gen-appstore-data.mjs的VERSION。漏一处就是「面板装的是新包、拉的是旧镜像」。 - ⚠️
build-1panel.ps1必须保持 UTF-8 with BOM;*.sh会被脚本统一成 LF(CRLF 的 shell 脚本在容器里报bad interpreter)。理由与修复片段见 /deploy/backend。 - ⚠️ 用
scp直接传进/opt/1panel/resource/apps/local/会建出0700目录,容器里的非 root 用户读不到挂载文件;走 tar 解包不会踩。 - 中间件(MySQL / Redis / RabbitMQ)与对象存储都不在包里,由面板的官方应用或外部实例提供,所以升级平台不需要动它们。
已装实例:换镜像 ≠ 换 SQL
后端容器的入口脚本只在库里没有 sys_config.platform.init.version 这一行时导入 /app/sql/files/*.sql(该目录由 compose 的 ./sql:/app/sql:ro 从实例目录挂进来)。也就是说:
- SQL 是 bind mount,换文件不用重建镜像;
- 但标记行在,导入照样会跳过 —— 换完镜像、
docker compose up -d之后,新 SQL 不会被重跑。
| 改的是 | 做法 | 代价 |
|---|---|---|
| 只改数据(种子数据、字典、参数、应用目录) | 删掉标记行 + 重启后端容器;包内脚本本身幂等(CREATE TABLE IF NOT EXISTS + INSERT IGNORE / WHERE NOT EXISTS) | 无 |
| 改了表结构(加列 / 改列 / 加索引) | IF NOT EXISTS 不会改已有表,所以要么清空该库的表再重启重新导入,要么手工执行 script/sql/update/ 里对应的幂等脚本 | 清表会丢数据;有真实业务数据的部署请走手工执行那条 |
# 只改数据:删标记 + 重启(脚本幂等,可重复执行)
docker exec <mysql容器> mysql -uroot -p<root密码> -e \
"delete from \`wujies_xxxxxx\`.sys_config where config_key='platform.init.version'"
docker restart <backend容器>面板建库时只给了 GRANT ALL PRIVILEGES ON `wujies_xxxxxx`.*,没有全局 CREATE。库一删,入口脚本那句 CREATE DATABASE IF NOT EXISTS 就建不回来,导入会全部落在不存在的库上,最后卡在「缺 N 张关键表」起不来。要重来请清空库里的表(保留库与授权),或者干脆按上面的表格手工执行补丁脚本。
平台补丁脚本从哪来、该跑哪些
| 位置 | 是什么 | 谁用 |
|---|---|---|
script/sql/platform_init.sql | 平台初始化(平台表 + 平台数据),唯一权威;打包成包里的 01-platform.sql | 全新部署 |
script/sql/app_catalog_seed.sql | 应用目录种子(app_info / app_version),包里叫 03-app-catalog.sql | 全新部署 |
script/sql/update/ 里的 7 个 | 随包走的最小集合,build-1panel.ps1 按顺序拼成 02-app-market.sql:app_market.sql、app_market_app_model.sql、fix_app_version_unique_index.sql、app_market_home_path.sql、app_market_sync_icon.sql、app_market_schema_history_fix.sql、app_market_register_modules.sql | 全新部署(随包导入);存量部署按需手工执行 |
script/sql/update/ 里其余的一次性脚本 | 给存量库补结构 / 补数据、做菜单归属迁移 | 存量部署,按需 |
script/sql/archive-upstream/ | 上游 RuoYi-Vue-Plus 的版本升级脚本,本项目不跟随上游 | 不要执行 |
-
这一堆 SQL 的权威地图在
script/sql/README.md(哪个权威、哪个只给存量库、哪个已归档),照它判断比照记忆猜安全。 -
升级前先对账,缺表 / 缺列一次报全:
# 拿真实库的列清单(information_schema 导出成 TSV)与「包里会执行的所有 SQL」逐列比对 node wujies-platform\deploy\tools\platform-schema-gap.mjs --live <导出文件> # 加 --write 会重新生成 platform_align_columns.sql / platform_align_collation.sql -
⚠️ 生成物不要手改:
platform_align_columns.sql/platform_align_collation.sql由platform-schema-gap.mjs生成,platform_config_site.sql由gen-app-seed-sql.mjs生成。要改内容就改生成器或加一条新的幂等脚本。 -
⚠️ 平台只支持 MySQL 8.x(排序规则通篇
utf8mb4_0900_ai_ci,MariaDB 没有这个排序规则)。
应用升级
升级链路:落位 → 迁移 → 加载 → 同步菜单
| # | 步骤 | 做什么 |
|---|---|---|
| 1 | 落位 | 从市场按 packageUrl 下载包 → 校验 MD5/SHA256(以及签名,取决于 app-market.security.mode)→ 解包释放到插件目录与前端产物目录,migrations/*.sql 落到 <pluginDir>/migrations/<appCode>/ |
| 2 | 迁移 | 扫描该目录 → 按文件名排序 → 逐个执行,结果记入 app_schema_history,并失效字典 / 参数缓存 |
| 3 | 加载 | 热加载插件(不重启 JVM):建子上下文 + 独立类加载器 → 注册接口 |
| 4 | 同步菜单 | 按清单幂等 upsert 菜单(按 menu_key 复用既有 menu_id) |
后端与前端两条产物、菜单与状态机细节见 /advanced/lifecycle。
接口(权限 app:install:edit,返回任务 ID;versionId 不传则升到最新已发布版本):
curl.exe -X POST "http://<host>/app/install/<installId>/upgrade?versionId=<versionId>" \
-H "Authorization: Bearer <token>"上架一个新版本是升级的上游动作,见 /package/publish。
⚠️ 迁移脚本只在 install / upgrade 里跑
这是本页最容易被误解的一条,因为「把包传上去」和「控制台点部署」看起来都像在升级:
| 入口 | 端点 | 跑迁移脚本? |
|---|---|---|
| 安装 | POST /app/install | ✅ |
| 升级 | POST /app/install/{installId}/upgrade | ✅ |
| 控制台「部署」按钮 | POST /app/version/{versionId}/deploy | ❌ 只落位 + 立即加载,不跑迁移 |
代码依据:迁移由安装器的 install() / upgrade() 在「落位之后、加载之前」调 applyPending;而控制台「部署」直接调的是只做「下载 → 校验 → 解包落位 → 热加载」的那条路径。
所以拿到新表结构 / 新种子数据的路径只有两条:发一个新版本再升级;或者彻底删除(purge=true)后重装 —— 后者会连数据表一起删,只在数据可丢时用。上架端只换包内容、不升版本号,存量部署不会有任何变化。
升级会强制重新落位
判据是 fromVersion != toVersion:「本机文件已存在就跳过」只在同版本重装 / 修复时成立。升级时若沿用旧产物,会同时踩两个静默坑 —— 前端还是旧版、migrations/ 根本不落位(表结构没变,但任务显示成功)。所以升级一律重新拉包落位,多一次下载换「前端、后端、表结构三者同版本」。
升级完成后怎么确认
- 任务中心(我的应用 → 应用任务)是排查第一站:看「执行版本迁移脚本」这一步里的
已执行:/已执行过,跳过:,以及失败原因;用法见 /ops/task-center。 - 部署报告里有
hotLoaded=true与hotLoadNote(组件数 / 接口数);纯前端应用没有后端产物可加载,任务日志会写明。 - 菜单行数不变(升级是幂等 upsert,不该多出菜单)。
- 远程应用入口带版本号查询串(
GET /app/runtime/remote-apps返回的entry形如/remote-apps/<code>/index.js?v=<版本>),升级后浏览器缓存会自动失效;如果页面还是老样子,先核对这里而不是先怀疑后端。
迁移的四条硬规矩 + 一条表结构约束
| # | 规矩 | 为什么 |
|---|---|---|
| 1 | 只做 up,不回滚 | MySQL 的 DDL 隐式提交,包事务是假的安全感。app.json 的 migrations(含 down)是预留字段,当前不参与执行 |
| 2 | 脚本必须幂等 | 重试时已成功的会跳过(已登记账本),失败的那个必须能重跑 |
| 3 | 已执行过的脚本内容不能改 | 同一 script 名再来时比对 checksum,不一致直接报错拒绝执行。要变更请新增脚本 |
| 4 | 去重判据不带租户 | 表结构在所有租户间共享;带上租户会让第二个租户重复执行 DDL。读写账本一律走 TenantHelper.ignore(...) |
| 5 | 账本唯一键必须正好是 (app_code, script) | 与第 4 条是同一个判据,表结构与代码必须一致;写成 (tenant_id, app_code, version) 时一个版本里只要有第二个脚本就撞唯一键 |
第 3 条的 checksum 比对容忍 LF / CRLF 差异(记账统一存 LF 归一化摘要),真正的改动照旧拒绝。写脚本的完整规范见 /plugin-dev/advanced/migrations,种子数据的取名与生成见 /plugin-dev/advanced/seed-data。
两个必须先跑的修复脚本
账本唯一键曾经写成 UNIQUE KEY (tenant_id, app_code, version),而判据是 (app_code, script)。后果:一个版本里只要有第二个迁移脚本,第二次记账就撞唯一键:
Duplicate entry '000000-exam-1.0.1' for key 'app_schema_history.uk_schema_history'整个安装 / 升级直接失败。它不是升级专有的毛病 —— 全新部署安装一个多脚本应用同样会炸。脚本做的事:先去重(同 (app_code, script) 只留最早那行)、再删旧键、再建新键,最后打印自检。幂等;改完不需要重启(DDL 生效即可)。
把 01-…-tables.sql 里夹带的种子数据拆到新脚本(0N-…-seed-data.sql)之后,那几个 01 的内容变了,已跑过老 01 的部署会在升级时被规矩 3 挡住。正确顺序是:
- 先跑修复脚本(删掉对应账本行)——涉及 mall / member / gallery / pay / exam;
- 再升级该应用。
01会重新执行(纯 DDL,幂等),新脚本接着执行(INSERT IGNORE/WHERE NOT EXISTS),存量数据零变化,只是账本重新对齐。
⚠️ 迁移只在 install / upgrade 触发,所以「先跑修复脚本 → 再升级」是唯一路径;光把包传上去(或点控制台「部署」)不会有任何变化。被「脚本已被修改」挡住是刻意的保护,不要绕过它。
📌 一般规律:已经执行过的迁移脚本永远不要改。要加字典 / 参数 / 示例数据,新增一个 0N-*.sql,让全新安装走 01、已有部署走 0N,两条路到同一状态。
存量部署的补数据脚本
script/sql/update/ 下的一次性脚本按用途分四类,全部幂等、可重复执行,脚本头部都写了背景与手工回滚方式。随包走的只有上一节列的那 7 个,其余都不进安装包(全新部署由 platform_init.sql 一步到位)—— 包里塞三十多个补丁只会让人不知道该跑哪些。
一、表结构修正类
| 脚本 | 一句话用途 |
|---|---|
app_market_schema_history_fix.sql | 迁移账本唯一键改成 (app_code, script)(见上一节) |
app_market_home_path.sql | 给 app_info 加 home_path(工作台卡片的入口路由) |
app_market_app_model.sql | 给 app_info 加 install_mode(能不能卸载)与 audience(谁能看见) |
fix_app_version_unique_index.sql | 版本唯一键改成只对「未删除行」生效的生成列唯一键 —— 逻辑删除后重新加同名版本不再报 409 |
sys_user_todo.sql | 建 sys_user_todo 表(工作台「待办事项」,不在平台初始化 SQL 里) |
二、菜单与归属修复类
| 脚本 | 一句话用途 |
|---|---|
app_market_<code>_migrate.sql(quote / enterprise / member / pay / exam / im / social / mall 各一份) | 把宿主时代已有的菜单登记到对应应用的版本上,并为存量租户回填一条 enabled 安装实例 |
app_market_gallery_migrate.sql | 图床的菜单归属迁移(复用既有菜单行,不新建菜单) |
app_market_gallery_remote_views.sql | 把图床菜单的 component 从宿主路径改成 remote:gallery/<视图键> |
fix_app_menu_component_remote.sql | 一次性修正 13 个应用页面的 component(宿主旧路径 → remote:<code>/<viewKey>),治「页面白屏 / component 解析不出来」 |
fix_gallery_menu_tree.sql | 补回图床被误删的菜单行,并把按钮登记进 app_version_menu(让下次升级复用既有按钮而不是新建一套) |
fix_dead_mall_finance_menus.sql | 清掉「库里存在、代码里根本没有」的商城财务菜单(表现为控制台持续报 component 解析失败) |
finance_menu_grants.sql | 给财务相关菜单补角色授权(清单表达不了授权,只能补 SQL) |
app_market_menu_restructure.sql | 菜单结构重构(平台 / 租户两套菜单分法)。⚠️ 不进安装包:全新部署由平台初始化 SQL 提供统一号段的菜单,再跑会插出重复菜单 |
app_market_task_center.sql | 应用市场「任务中心」菜单。⚠️ 同上,不进安装包 |
app_market_demo_app_remove.sql | 下架 + 软删历史遗留的示例应用 demo-app(del_flag='1',含执行前后核对与台账只读报告)。⚠️ 示例应用已改由 hello 承担,新部署的目录种子里不含 demo-app;这份只给存量库清理用,不进安装包(不在 build-1panel.ps1 的白名单里) |
三、清理重复类
| 脚本 | 一句话用途 |
|---|---|
cleanup_duplicate_app_menus.sql | 清掉「重新同步菜单」误建的整棵重复菜单树(幂等,可重复执行) |
cleanup_duplicate_mall_order_status.sql | 清掉 mall_order_status 旧一代的字典行(值重叠、含义冲突),只删旧的且要求新的那套已存在 |
四、平台表对齐类
给「用上游 SQL 起的存量库」补平台的表 / 列 / 数据,缺哪张会怎样写在脚本头部。
| 脚本 | 一句话用途 |
|---|---|
platform_missing_tables.sql | 补 sys_area / sys_message / sys_message_log / sys_phone_verify_config —— 缺了会启动即退 |
platform_align_columns.sql | 补平台缺失的列(生成物)—— 缺了会登录 500 Unknown column |
platform_align_collation.sql | 把平台表排序规则统一到 utf8mb4_0900_ai_ci(生成物)—— 否则跨表 JOIN 报 Illegal mix of collations |
platform_config_seed.sql | 补登录 / 注册流程必须存在的 sys_config 行 —— 缺了登录 400 For input string: "" |
platform_config_dicts.sql | 补参数配置页面用的两个字典(sys_config_category / sys_config_value_type) |
platform_config_site.sql | 补平台站点配置(website / login / email 三类,生成物)—— 缺了登录页站点名与图标为空 |
回滚能力
| 动作 | 支持 | 说明 |
|---|---|---|
| 升级失败后重试 | ✅ | 已成功的迁移脚本会跳过,失败的那个必须能重跑(这就是「必须幂等」的原因) |
| 把应用退回旧版本 | ✅ | 对本机同一应用升级到旧版本即可(versionId 指向旧版本;菜单按旧版清单同步)。注意它不改变已经 up 过的表结构 |
| 卸载应用 | ✅ | 两种模式:保留数据(撤菜单 + 停后端,数据留着)/ 彻底删除(连菜单、数据表、迁移记录、产物一起删,不可恢复)。两道安全闸与删除清单见 /advanced/lifecycle |
| 自动回滚表结构 | ❌ | 迁移只做 up。要回退表结构请自己写一个反向的新迁移脚本(幂等) |
| 回滚平台表结构 | ❌ | 同上,script/sql/update/ 下的脚本都只做 up,脚本头部写清了手工回滚语句 |
| 回滚平台 jar / 镜像 | ✅ | 换回旧 jar / 旧镜像 tag 即可(docker compose up -d)——但不会把库变回旧结构 |
退回旧版本只换回 jar 与前端产物,库里已经被 up 改过的列不会自己变回去。如果新版本做过破坏性变更(改列类型、删列、改语义),回退旧版本会撞上「旧代码读新表」。这类变更必须在升级前做备份。
升级前的备份
升级前至少确认这几项的备份,丢失代价从高到低:
| 要备份 | 丢了会怎样 |
|---|---|
app_schema_history | 最痛的一项:账本没了会重复执行 DDL;更糟的是 checksum 一起丢,无法判断「脚本有没有被改过」 |
app_install / app_version_menu | 不知道本机该有哪些应用、菜单归属对不上;「彻底删除」删不干净、菜单打不上标签 |
平台表(至少 sys_menu、sys_role_menu、sys_config、sys_dict_type、sys_dict_data) | 菜单授权与平台参数丢失;彻底卸载会动 sys_menu 与角色授权 |
签名私钥(wujies/wujies-plugins/keys/,已 gitignore) | 找不回来,只能换钥并重签所有已发布包,且每套部署都要更新公钥并重启 |
app-market.deploy.plugin-dir / frontend-dir(可选) | 可恢复:从市场重新部署 / 安装即可;丢了的表现是接口 404、「远程视图不可用」 |
备份与恢复的完整步骤、密钥纪律见 /ops/backup-security。
相关阅读
- /deploy/backend —— 三条部署路线、镜像构建与启动参数
- /deploy/configuration —— 哪些配置是启动期读入的(改完必须重建宿主)
- /deploy/multi-deployment —— 目录集中时升级的目录侧变化
- /plugin-dev/advanced/migrations —— 迁移脚本的编写规范与自检清单
- /plugin-dev/advanced/seed-data —— 字典 / 站点配置 / 示例数据怎么随包发
- /ops/task-center —— 升级任务的分步日志怎么看
- /ops/troubleshooting —— 摘要不一致、撞唯一键、表不存在的排查路径
- /advanced/lifecycle —— 安装 / 升级 / 停用 / 卸载的状态机与卸载安全闸
- /package/publish —— 上架新版本(升级的上游动作)