Wujies 无界平台
部署与运维

后端部署

本页只讲后端:wujies-admin.jar 怎么打出来、三种起法(主机直跑 / 手工 Docker / 1Panel 本地应用)各敲什么命令、首次启动的初始化 SQL 由谁跑、中间件地址从哪来。

运行期的机制不在这里重复:平台形态(官方版 / 用户版)与目录集中见 /advanced/catalog-and-edition,插件的加载与回收见 /advanced/plugin-runtime,app-market: 配置项逐条说明见 /deploy/configuration。

先认清三个目录

三个仓库没有共同 git 根,各自分支、各自提交。本页所有命令都按这个布局写(平台的上游出处是 RuoYi-Vue-Plus,更名后的模块与路径一律以 wujies-* 为准):

仓库目录内容
后端wujies-platform/Java / Spring Boot + script/sql/** + deploy/**
插件wujies/wujies-plugins/插件本体 + 插件前端工程 + package.ps1
宿主前端plus-ui/Vue 3 / Vite —— ⚠️ 它的 package.json 里 name 写的是 wujies-admin,别被这个名字带偏
后端仓库里没有业务模块,也没有契约模块

平台里没有 *-api 契约模块(2026-09-30 起为 0 个),也没有 wujies-job / wujies-demo / wujies-generator。每个业务应用都是自包含的:实体 / BO / VO / Mapper 接口与 Mapper XML / Service / controller / app.json / migrations/ 全在插件仓库的 wujies-<app>/ 下,平台对 cn.wujies.<app>.* 的 Java 引用为 0。所以在这里找不到 mall / gallery 之类的业务代码,这是设计如此 —— 不变量见 /plugin-dev/basics/invariants。

后端产物:一个可运行 jar

启动模块是 wujies-platform/wujies-admin/,产物固定为:

wujies-platform/wujies-admin/target/wujies-admin.jar

它的依赖链不含任何业务应用:插件 jar 与远程应用前端产物都是运行期由应用市场落位到磁盘的(见 /advanced/lifecycle)。所以"部署后端"始终是"部署一个 jar + 给它两个可写目录"。

这两个目录必须记住 —— 在 application.yml 里是相对路径,相对启动时的工作目录:

配置项默认值容器内
app-market.deploy.plugin-dirplugins-dist/app/plugins-dist
app-market.deploy.frontend-dir../wujies-admin/app-market-dist/remote-apps/app/remote-apps

⚠️ 这里的 wujies-admin 是工作区顶层那个只放 app-market-dist/ 的目录(前端产物的落位处),既不是后端模块 wujies-platform/wujies-admin/,也不是宿主前端仓库 plus-ui/——三处同名,按路径判断。

编译与打包

# 环境(本机没有把 JDK / Maven 加进 PATH 时)
$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"

# 全量编译(根 pom 默认 skipTests=true,编译不会跑测试)
& $mvn -B -q compile -DskipTests

# 打可运行 jar —— 改了后端代码必须做这一步才能验证
& $mvn -pl wujies-admin -am -B install -DskipTests
  • install 会把依赖装进本地仓库,运行中的 JVM 会锁住旧 jar:服务还跑着的时候执行它会中途失败(Windows 上尤其明显)。先停服务,或先只做 compile。
  • 跑单模块测试要显式打开跳过开关并指定 profile:& $mvn -pl wujies-modules/wujies-app-market -B test "-DskipTests=false" "-Dprofiles.active=dev"。

profile 是构建期写进 jar 的,默认是 dev

application.yml 里的 spring.profiles.active: @profiles.active@ 是 Maven 过滤占位符(根 pom 对 application* 开了 filtering),构建时被替换。根 pom 有 local / dev / prod 三个 profile,其中 dev 是 activeByDefault:

构建命令jar 内写死的 profile
mvn -pl wujies-admin -am installdev
mvn -pl wujies-admin -am install -Pprodprod

deploy/tools/build-1panel.ps1 走的是第一条(没有 -Pprod),容器里靠 compose 的 SPRING_PROFILES_ACTIVE: prod 覆盖(环境变量优先级高于 jar 内 yml)。直跑时没有这层覆盖,必须自己传 -Dspring.profiles.active=prod,否则起的是 dev 配置。

⚠️ application-prod.yml 里带着一整套开发验证阶段的连接信息(数据库、Redis、RabbitMQ 的地址与密码都写在文件里)。正式环境必须先按 /deploy/index 的脱敏清单替换,或用环境变量覆盖。

路线一:主机直跑

工作目录必须是 wujies-platform/(上面两个目录是相对路径):

cd wujies-platform
java "-Dloader.path=plugins-dist" "-Dspring.profiles.active=prod" -jar wujies-admin\target\wujies-admin.jar

四个必须知道的点:

点说明
-Dloader.path 的引号不能省PowerShell 会把 -Dloader.path=x 拆成 /path=x,报 ClassNotFoundException: /path=plugins-dist
工作目录必须是 wujies-platformplugin-dir 与 frontend-dir 都是相对路径;工作目录错了会把产物写到别处
profile 必须显式传见上一节:jar 内默认是 dev
插件运行时开关只能走 -D-Dapp-market.plugin-runtime.enabled=false 才能退回旧的共享类加载器行为;写进 yml 无效——启动器在 Spring 之前执行,读的是系统属性

-Dloader.path 只加载该目录根下的 jar,不递归子目录(所以迁移脚本的 migrations/ 子目录是安全的)。同一个 appCode 名下留着两个版本 jar 也不会再直接把服务打挂:2026-10-02 起启动器与插件运行时都按 active-plugins.txt 选版本,不在清单里的 jar 刻意不挂载;读不到或为空时不过滤,退回改造前的宽容行为。细节见 /advanced/plugin-runtime。

启动脚本:script/prune/start-host.ps1

推荐用它代替手敲 java:它先清理上一版插件 jar,再按正确的工作目录 / profile / loader.path 启动,jar 不存在时还会自动打包。

# 默认:清旧插件 jar -> 用已有 jar 启动
powershell -NoProfile -ExecutionPolicy Bypass -File wujies-platform\script\prune\start-host.ps1

# 启动前重新打包 wujies-admin
powershell ... -File wujies-platform\script\prune\start-host.ps1 -Build

# 换端口(例如并排起第二个实例做验证)
powershell ... -File wujies-platform\script\prune\start-host.ps1 -Port 8087

# 不加载插件,用于排查「没有插件时宿主是否正常」
powershell ... -File wujies-platform\script\prune\start-host.ps1 -NoPlugin

全部参数:

参数默认作用
-PluginDirplugins-dist插件落位目录,必须与 -Dloader.path 一致
-ProfileprodSpring profile
-Build关启动前执行 mvn -pl wujies-admin -am -B package -DskipTests
-SkipPrune关启动前不清理旧插件 jar
-NoPlugin关不传 -Dloader.path,即不加载任何插件
-Port空追加 -Dserver.port=<Port>

脚本会自己推断 JDK(JAVA_HOME,否则扫 %USERPROFILE%\.jdks)与 Maven(PATH,否则扫 C:\Program Files\JetBrains 下的 mvn.cmd),并把工作目录切到 wujies-platform。

⚠️ 脚本头部注释里的用法示例还写着 scripts\start-host.ps1(旧路径),实际文件在 script\prune\ 下——以文件位置为准。

清理旧插件 jar

# Windows / PowerShell
powershell -NoProfile -ExecutionPolicy Bypass -File wujies-platform\script\prune\prune-plugins.ps1
powershell ... -File wujies-platform\script\prune\prune-plugins.ps1 -PluginDir D:\app\plugins-dist
powershell ... -File wujies-platform\script\prune\prune-plugins.ps1 -DryRun
# Linux / macOS(容器或只读挂载场景的兜底)
bash wujies-platform/script/prune/prune-plugins.sh
PLUGIN_DIR=/opt/app/plugins-dist bash wujies-platform/script/prune/prune-plugins.sh
DRY_RUN=1 bash wujies-platform/script/prune/prune-plugins.sh
脚本参数作用
prune-plugins.ps1-PluginDir(留空自动推断为 <仓库根>/plugins-dist)、-DryRun(只报告不删)按 active-plugins.txt 只删「同 appCode 下非当前生效版本」的 jar
prune-plugins.sh环境变量 PLUGIN_DIR、DRY_RUN同上,Bash 版本

判定依据是部署时写下的 active-plugins.txt(一行一条 appCode=jarFileName)。手工放进来的 jar、不属于任何已登记应用的 jar 一律不碰;没有这个文件说明还没通过应用市场部署过后端产物,脚本直接跳过。被 JVM 占用的 jar 删不掉时脚本会告警并返回非 0——这是「服务还在跑」的正常现象,不是脚本故障。

路线二:手工 Docker

deploy/docker/ 下是镜像构建上下文(Dockerfile + entrypoint + 待 COPY 的产物),不是 compose 目录:仓库里没有独立的 compose 文件,唯一一份在 1Panel 应用包内(deploy/1panel/apps/<包名>/<版本>/docker-compose.yml,见 /deploy/index)。镜像里不做构建——先在开发机打好 jar,再 COPY 进去。

镜像上下文目录基础镜像端口关键点
wujies-platform/backenddeploy/docker/backend/eclipse-temurin:17-jre-jammy8080额外装 default-mysql-client(首次初始化要连库);ENTRYPOINT 用 exec 形式,保证 java 是 PID 1,能收到 SIGTERM 优雅停机
wujies-platform/frontenddeploy/docker/frontend/nginx:1.27-alpine80预先建好 /var/www/remote-apps 与 /var/www/upload;nginx 配置必须 COPY 到 /etc/nginx/templates/default.conf.template
wujies-platform/snailjob-serverdeploy/docker/snailjob/eclipse-temurin:17-jre-jammy8800(控制台)、17888(执行器)自带 mysql 客户端,首次启动导入 ry_job.sql 建自己的库
wujies-platform/monitor-admindeploy/docker/monitor/eclipse-temurin:17-jre-jammy9090无数据库;控制台路径 /admin,HTTP Basic 认证

构建(build-1panel.ps1 会把四个上下文准备到 deploy/dist/1panel/docker/,产物已在同目录):

cd 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 .

⚠️ wujies-platform/* 只是本地构建的镜像名,Docker Hub 上没有这个命名空间。多机部署与上架官方商店要 tag 成自己的仓库再 push,并同步改 compose 里的 image:。

数据卷

来自包内 docker-compose.yml(1Panel 约定所有数据走相对路径 ./data/...):

宿主相对路径容器内路径后端前端说明
./data/plugins-dist/app/plugins-dist读写—插件 jar 落位目录,等于 app-market.deploy.plugin-dir
./data/remote-apps/app/remote-apps读写—远程应用前端产物,等于 app-market.deploy.frontend-dir
./data/remote-apps/var/www/remote-apps:ro—只读与上一行是同一个卷,nginx 按 /remote-apps/ 对外提供
./data/upload/app/upload / /var/www/upload:ro读写只读本地文件存储(sys_oss_config 选 local 时用)
./data/logs/app/logs读写—日志
./data/temp/app/temp读写—上传临时目录(SPRING_SERVLET_MULTIPART_LOCATION)
./sql/app/sql:ro只读—初始化 SQL,首次启动由 entrypoint 导入
后端与前端必须看到同一个 remote-apps 卷

后端写产物、nginx 读产物。这两行挂的不是同一个宿主目录,表现就是「安装成功、菜单也在,点进去 404」。四处路径的对齐关系见 /deploy/frontend。

JVM 参数:三个入口脚本行为不一致

这处容易照着「三个带 java 的镜像同一套语义」去配,实际不是(2026-10 按文件核对):

镜像启动行读 JAVA_OPTS 吗
backendexec java -Dapp-market.deploy.plugin-dir=… -jar /app/app.jar不读
snailjob-serverexec java $JAVA_OPTS -jar /app/app.jar读
monitor-adminexec java $JAVA_OPTS -jar /app/app.jar读

所以 backend 的 Dockerfile 里那行 JAVA_OPTS="" 在后端容器上是死配置:往 compose 的 environment: 里加 JAVA_OPTS 对后端无效,堆上限仍是 JDK 默认(容器内存的 25%,或宿主内存的 1/4)。要改后端 JVM 参数有两条路:

  1. 不改镜像(先用这条验证):JDK 自己会读 JAVA_TOOL_OPTIONS,加到后端容器的 environment: 即可,启动日志里会出现 Picked up JAVA_TOOL_OPTIONS: …。
    environment:
      # 4GB 机器:把上限写死比让 JVM 按 25% 自己猜更可控
      JAVA_TOOL_OPTIONS: "-Xms256m -Xmx1024m -XX:MaxMetaspaceSize=384m"
      # OOM 取证:dump 落在 /app/logs(日志卷里能取走)
      # JAVA_TOOL_OPTIONS: "-Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs"
  1. 改 entrypoint 并重建镜像:在 deploy/docker/backend/entrypoint.sh 最后一行给 java 加上 $JAVA_OPTS(与另外两个入口脚本对齐),docker build 之后 docker compose up -d。
怎么确认生效

docker logs <容器> 里能看到 Picked up JAVA_TOOL_OPTIONS(路线 1);包内 README 给的内存口径只有一条:≥ 2GB(前端 nginx + 后端,中间件按所选服务的规格另计)。

Metaspace 要封顶

每个插件一个独立类加载器。类加载器一旦被引用泄漏,Metaspace 会一路涨到 native OOM —— 显式给一个 -XX:MaxMetaspaceSize 至少能换到一个能看懂的报错。

路线三:1Panel「本地应用」

打安装包

# 全量:plus-ui 前端 vite build --mode docker + 后端 mvn install + 组装 + 打 tar.gz
powershell -ExecutionPolicy Bypass -File wujies-platform\deploy\tools\build-1panel.ps1

# 只改了后端
powershell ... -File wujies-platform\deploy\tools\build-1panel.ps1 -SkipFrontend

# 只改了 SQL / data.yml / compose(最快,不重建镜像)
powershell ... -File wujies-platform\deploy\tools\build-1panel.ps1 -SkipFrontend -SkipBackend
参数默认作用
-Version5.6.3包裹版本号,同时决定目录名与 tar.gz 文件名
-OutDirdeploy\dist\1panel输出根目录
-SkipFrontend关跳过 plus-ui 的前端构建,复用已有 dist/
-SkipBackend关跳过后端与两个 extend jar 的构建,复用已有 jar
-NoTar关不打 wujies-platform-1panel-<版本>.tar.gz

前置:Node 22+、JDK 17+($env:JAVA_HOME)、Maven(脚本先找 IDEA 自带的 mvn.cmd,找不到回落 PATH)。脚本做 5 步(第 2b 步是后加的):

  1. 前端:在 plus-ui/ 里跑 node scripts/gen-runtime-shims.mjs,再 vite build --mode docker(-SkipFrontend 跳过);
  2. 后端:mvn -pl wujies-admin -am -B install -DskipTests(-SkipBackend 跳过);
  3. 2b:mvn -pl wujies-extend/wujies-snailjob-server,wujies-extend/wujies-monitor-admin -am -B install -DskipTests —— 这两个 jar 不在 wujies-admin 的依赖链上,必须单独构建(-SkipBackend 时一起跳过);
  4. 组装 4 个应用包(生成各自 data.yml + 拼初始化 SQL);
  5. 准备 4 个 docker 构建上下文,并打 wujies-platform-1panel-<版本>.tar.gz。

产出的 4 个包(deploy/dist/1panel/apps/):

包是什么初始化 SQL
wujies-platform-server官方版:目录在自己库 + 含上架端 + 对外提供 /app/catalog/**01-platform + 02-app-market + 03-app-catalog
wujies-platform-client用户版:目录读远端(edition=client + catalog.mode=remote)同上
wujies-snailjob-server定时任务调度中心(自己的库)01-ry-job.sql
wujies-monitor-admin服务监控中心无(不连库)

两个主包用的是同一个 jar,差别只在 compose 里的 APP_MARKET_EDITION / APP_MARKET_CATALOG_MODE:用户版的后端不映射对外端口(只有前端端口对公),官方版额外映射后端端口给客户端读目录。形态差异与验证见 /advanced/catalog-and-edition。

上传与安装

# 1) 上传并解包(走 tar,别用 scp 直接传进资源目录)
scp wujies-platform-1panel-5.6.3.tar.gz root@<host>:/root/
cd /root && tar -xzf wujies-platform-1panel-5.6.3.tar.gz -C /root/pkg
cp -r /root/pkg/apps/* /opt/1panel/resource/apps/local/
chmod -R a+rX /opt/1panel/resource/apps/local/wujies-platform-*

要点:

  1. 别用 scp 直接传进 /opt/1panel/resource/apps/local/:Windows 的 OpenSSH scp 建出来的目录是 0700,容器里的非 root 用户读不到挂载进去的文件。传完一律 chmod -R a+rX;走 tar 解包不会踩。
  2. 面板 → 应用商店 → 本地应用 → 同步/刷新,即可看到应用。
  3. 表单里 MySQL 必选、Redis 必选(且该实例要已设密码,否则必填项是空的)、RabbitMQ 可选;库名 / 账号 / 密码由面板随机生成(random: true,default 是前缀不是初值),装完就别改。
  4. 面板装应用时无条件先 docker pull 一遍镜像:本地构建的 wujies-platform/backend:5.6.3 会被解析成 Docker Hub 的命名空间并 denied,日志接着打「镜像已存在,使用存量镜像」并继续——这不是错误。想省掉那次超时就取消勾选安装抽屉「高级」里的「拉取镜像」。
  5. 多机部署 / 上架官方商店时镜像必须公开可拉:推到自己的仓库后同步改 compose 里的 image:。

首次启动日志长什么样

[entry] MySQL=mysql:3306/wujies_k3m8pq  Redis=redis:6379  RabbitMQ=rabbitmq:5672
[entry] 等待 MySQL 就绪…
[entry] 首次启动:初始化数据库 wujies_k3m8pq
[entry] 导入 01-platform.sql
[entry] 导入 02-app-market.sql
[entry] 导入 03-app-catalog.sql
[entry] 应用目录:app_info=18 行  app_version=12 行
[entry] 数据库初始化完成,已写入标记 platform.init.version=5.6.3
[entry] 启动应用(profile=prod)

第二次启动看到的是 数据库已初始化过(platform.init.version=…),跳过导入。库名 / 账号是表单随机生成的(形如 wujies_<6位随机>),所以日志里不是 wujies。

首次启动的初始化逻辑

初始化由 deploy/docker/backend/entrypoint.sh 负责(中间件不随包安装,拿不到 MySQL 容器的 docker-entrypoint-initdb.d 时机),顺序固定:

顺序文件内容
101-platform.sql平台初始化:平台表 + 平台数据(地区 / 字典 / 参数 / 菜单),源头 script/sql/platform_init.sql
202-app-market.sql应用市场自己的表与列(由 build-1panel.ps1 把 script/sql/update/ 下的 7 个脚本拼成一份)
303-app-catalog.sql应用目录种子(app_info + app_version,从发布方库导出,源头 script/sql/app_catalog_seed.sql)

导入用 mysql --force(容忍个别语句报错),随后做两道复核,任一不过就让容器失败并不写标记:

  1. 关键表全检(19 张):sys_user、sys_role、sys_menu、sys_dept、sys_post、sys_config、sys_dict_type、sys_dict_data、sys_area、sys_oss_config、sys_tenant、sys_user_todo、app_info、app_version、app_category、app_install、app_install_task、app_version_menu、app_schema_history。宁可安装当场失败,也不要交付一个残库。
  2. 目录数据非空复核:存在 03-app-catalog.sql 时校验 app_info ≥ 1 行。表在 ≠ 数据在,一个字符集错误就会让目录整段静默失败,交付出去就是「装成功了、市场里一个应用都没有」。

判据是 sys_config 里的标记行 platform.init.version——不是「有没有 sys_user 表」:半途失败的库会缺表但已有表,用表存在与否判断会失真。

SQL 是挂载进去的,不是打进镜像的

compose 里是 - ./sql:/app/sql:ro,所以改 SQL 不用重建镜像,换文件即可:已安装实例用它自己那份(/opt/1panel/apps/<应用>/<实例名>/sql/files/),资源目录那份只给以后的新安装用。只有改了 jar 或前端 dist/ 才要 docker build。

让已经装过的实例用上新的初始化 SQL

entrypoint 只在第一次启动(没有标记行)时导入,所以换完文件还要让它重跑一次:

改的是做法
只改数据(种子数据 / 字典 / 参数 / 地区 / 应用目录)删标记行 + 重启(脚本幂等:CREATE TABLE IF NOT EXISTS + INSERT IGNORE / WHERE NOT EXISTS),库和表都不动
改了表结构(加列 / 改列 / 加索引)IF NOT EXISTS 不改已有表 → 必须清空库里的表(不是删库)再重启
# 只改数据:删标记行再重启
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/update/ 下对应的脚本,见 /deploy/upgrade。

中间件:不在包里,怎么选 / 怎么填

MySQL、Redis、RabbitMQ 都不在应用包里,也不打进镜像。安装表单里每个中间件都是两条路:

中间件必填面板选服务填外部都空
MySQL是下拉选已装的 MySQL,面板据此建库 + 授权填 EXTERNAL_DB_*,要自己先建库并授权启动失败:没有数据库地址
Redis是下拉选已装的 Redis,面板带入 root 密码填 EXTERNAL_REDIS_*启动失败:没有 Redis 地址
RabbitMQ否面板不会带入账号密码,默认按官方应用的 rabbitmq / rabbitmq 预填填 EXTERNAL_RABBITMQ_*应用照常启动,只是消息中心 / 站内信不可用

解析规则是「外部字段非空 → 用外部;否则用面板选的服务;都空 → 启动失败并打印原因」,这段逻辑写在 entrypoint.sh 里——compose 与 Spring 都表达不了这种条件。数据库下拉只提供 MySQL:平台初始化 SQL 通篇 utf8mb4_0900_ai_ci,MariaDB 没有这个排序规则。

解析结果会被导出成 Spring 属性(环境变量优先级高于 jar 内 yml):

SPRING_DATASOURCE_DYNAMIC_DATASOURCE_MASTER_URL / _USERNAME / _PASSWORD
SPRING_DATA_REDIS_HOST / _PORT / _PASSWORD
SPRING_RABBITMQ_HOST / _PORT / _USERNAME / _PASSWORD

定时任务调度中心与监控中心同理,留空 = 不启用:SNAIL_JOB_SERVER_HOST 非空才导出 SNAIL_JOB_ENABLED=true(不启用则订单自动取消 / 自动收货、优惠券状态与过期、促销启停、统计刷新、会员积分过期这些任务都不会跑,而且执行器已随各应用搬进插件);MONITOR_ADMIN_URL 非空才导出 SPRING_BOOT_ADMIN_CLIENT_ENABLED=true(否则监控中心里看不到这个实例,功能本身不受影响)。两者都填独立包的固定网络别名(snailjob-server / monitor-admin),不要填面板随机生成的容器名。

为什么不把中间件打进包

数据会落在应用目录里(卸载 / 迁移麻烦)、每个应用各跑一套中间件(内存翻倍)、版本与安全更新各自为政。复用面板的官方应用之后,中间件由面板统一升级与备份,应用只负责自己的库。

对象存储同理不在包里:装完在「系统工具 → 系统配置 → 文件存储配置」里启用一种(MinIO / 阿里云 / 腾讯云 / 七牛)。默认所有配置都是停用状态,所以刚装完上传会失败——这是刻意的。

五条必须遵守的纪律

1. build-1panel.ps1 必须保持 UTF-8 with BOM

Windows PowerShell 5.1 对没有 BOM 的脚本按 ANSI 解码,中文注释与字符串会被双重编码(典型报错是 Missing closing '}')。用编辑器改完确认前三字节是 EF BB BF。

2. *.sh 统一 LF

CRLF 的 shell 脚本在容器里报 bad interpreter。build-1panel.ps1 在拷 entrypoint.sh 时会做 \r\n → \n 归一化,但手工改过脚本再手工拷进容器就会绕过这一步。

3. 改版本号要同时动四处

位置内容
deploy/tools/build-1panel.ps1$Version 默认值
包内目录名deploy/1panel/apps/<包>/5.6.x/
docker-compose.yml各处 image: 的 tag
deploy/tools/gen-appstore-data.mjsVERSION 常量

漏改任何一处,表现是「面板装的是新包、拉的是旧镜像」或「包版本目录与 compose 里的 tag 对不上」。

4. 不要在正在运行的 JVM 上跑 mvn install

运行中的 JVM 锁住了 wujies-admin.jar 与插件 jar,mvn install 会在写目标文件时失败(Windows 上尤其明显)。先停服务,或先只做 compile。

5. 插件目录在「-D 参数」与「yml / 环境变量」两处都要一致

容器里 entrypoint 用 -Dapp-market.deploy.plugin-dir="${PLUGIN_DIR:-/app/plugins-dist}" 传插件目录(启动器在 Spring 之前跑,只认系统属性),compose 又用 APP_MARKET_DEPLOY_PLUGIN_DIR 设了同名 Spring 属性。改插件目录要两处一起改,并且与宿主启动参数 -Dloader.path 指向同一个目录。

相关阅读

目录