Wujies 无界平台
部署与运维

升级与补丁

升级这件事有两条互不相干的线:平台(宿主)自身的版本,和应用(插件)的版本。两者不会互相触发 —— 升应用不会改平台表,升平台也不会碰应用表。本页解决三个具体问题:改了什么要敲哪条命令、已经装着的实例怎么让改动生效、迁移脚本在这两条线里的位置。

前提是平台与插件仓库的边界(见 /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。
改了 yml / 公钥必须重建宿主再重启

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.ymldocker 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容器>
别用 DROP DATABASE 重来

面板建库时只给了 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。

两个必须先跑的修复脚本

app_market_schema_history_fix.sql —— 不跑会让多脚本应用装不上

账本唯一键曾经写成 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 生效即可)。

app_market_app_seed_split_repair.sql —— 改过已执行脚本后必须先跑它再升级

把 01-…-tables.sql 里夹带的种子数据拆到新脚本(0N-…-seed-data.sql)之后,那几个 01 的内容变了,已跑过老 01 的部署会在升级时被规矩 3 挡住。正确顺序是:

  1. 先跑修复脚本(删掉对应账本行)——涉及 mall / member / gallery / pay / exam;
  2. 再升级该应用。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。

相关阅读

目录