Wujies 无界平台
部署

配置项

本文是 1.x 的归档文档(冻结于 1.x)。该分支只接收勘误修正, 内容基本不再变动;文中的源码路径、配置项与版本号均以 1.x 为准,不建议用于新项目。

新项目请查看 5.x 最新文档。已在 1.x 上运行的项目,本文仍是可靠依据。

本页是部署时该动哪些配置、动完怎么核对的操作清单,只覆盖三块:产物落位路径(app-market.deploy.*)、应用包安全(app-market.security.*)、平台形态与目录(app-market.edition 与 app-market.catalog.*),以及必须跟着一起确认的 security.* 与 xss.excludeUrls。

逐项索引(每个键在源码里的位置、注解兜底值、消费方)见 /reference/config。本页不重复那套表,只讲三件从索引表里看不出来的事:真正生效的值是哪一层给的、改完要不要重建 jar 或重启、哪几个键必须与别的组件对齐。

配置从哪来,谁覆盖谁

来源位置覆盖关系
jar 内 ymlwujies-platform/wujies-admin/src/main/resources/application.yml 的 app-market: 段唯一「写出来的值」;被下面两层覆盖
容器环境变量1Panel 包内 docker-compose.yml 的 APP_MARKET_*(值来自安装表单)覆盖 yml
JVM 系统属性 -D容器 deploy/docker/backend/entrypoint.sh;本机 script/prune/start-host.ps1优先级最高
@Value 注解兜底Java 类里的 @Value("${app-market.xxx:默认值}")只在该键完全不存在时生效(不是一层配置)

环境变量名与键名的换算规则是 Spring 的宽松绑定:点与横线都换成下划线、全大写。app-market.deploy.frontend-dir 对应 APP_MARKET_DEPLOY_FRONTEND_DIR。中间件地址(数据源 / Redis / RabbitMQ)不走 app-market.*,由 entrypoint.sh 导出成 SPRING_* 属性,见 /deploy/backend。

改完不重建、不重启,等于没改

application.yml 被打进 wujies-admin.jar。改源码目录里的 yml 之后必须重建再重启:

# 在 wujies-platform 目录下;Maven 不在 PATH 时用 IDEA 自带的 mvn.cmd,见 /deploy/backend
mvn -pl wujies-admin -am -B install -DskipTests
powershell -NoProfile -ExecutionPolicy Bypass -File script\prune\start-host.ps1

1Panel 部署改的是「应用商店 → 已安装 → 参数」,保存后面板会重建容器,不需要手工 docker build;只有改了 Java 或前端 dist/ 才需要重建镜像。

app-market: 的完整配置树

下面是 application.yml 里真实存在的内容(207–289 行),public-key 的值已换成占位符:

app-market:
  deploy:
    # 后端插件 jar 落位目录,必须与宿主启动参数 -Dloader.path 一致
    # ⚠️ loader.path 只加载该目录**根下**的 jar,不递归子目录
    plugin-dir: plugins-dist
    # 前端远程应用落位目录,必须与宿主 /remote-apps/ 的物理路径一致
    # ⚠️ 前后端分离部署时必须改成共享/分发目录,否则前端服务取不到产物
    frontend-dir: ../wujies-admin/app-market-dist/remote-apps
  # 应用包安全检查:产物白名单 + 签名校验
  security:
    # off     完全不检查(等价于改造前的行为)
    # warn    检查并记录问题,但照常安装/上架
    # enforce 有问题就拒绝    ← 当前
    mode: enforce
    # 包签名公钥(X.509 SubjectPublicKeyInfo 的 PEM,或裸 base64)
    # 留空表示尚未配置校验能力 —— 此时 enforce 会拒绝所有包并在报错里说明原因
    public-key: |
      -----BEGIN PUBLIC KEY-----
      <你的公钥>
      -----END PUBLIC KEY-----
    # frontend/ 下允许的文件类型。前端目录是宿主的静态资源目录,
    # 白名单之外一律拒绝;且 .jsp/.php/.war/.jar 等无论如何都不允许(代码里硬拒)
    allowed-frontend-extensions: >-
      .js,.mjs,.cjs,.css,.json,.svg,.png,.jpg,.jpeg,.gif,.webp,.ico,.bmp,
      .woff,.woff2,.ttf,.otf,.eot,.map,.txt,.html,.wasm
  # 版本形态:official(官方版,默认) / client(用户版)
  edition: official
  # 应用目录(catalog)的访问方式
  catalog:
    # local  直连本库(默认,单机部署用这个)
    # remote 走 base-url 指向的目录服务(多部署集中时用)
    mode: local
    # 目录服务地址;mode=remote 时必填,否则启动即失败
    base-url: ''
    # 服务间共享令牌。服务端与调用方配成一致;
    # 服务端留空表示 /app/catalog/** 不校验调用方 —— 内网自用可以,公网必须配
    token: ''
    # 远程模式的超时:目录读取不该拖垮安装流程,宁可快速失败
    connect-timeout-ms: 3000
    read-timeout-ms: 15000
源码里那把公钥是开发验证阶段的

public-key 当前值对应 wujies/wujies-plugins/keys/private.pem(该目录已 gitignore)。正式环境必须自己生成一对:wujies/wujies-plugins/package.ps1 -Keygen,然后把新公钥贴进 public-key 并重建宿主 + 重启。公钥不是秘密;私钥只留在发布方。流程见 /package/signing。

⚠️ yml 里那段注释仍写着 keygen plugins/keys、对应 plugins/keys/private.pem —— 那是过时路径,wujies-platform/plugins/ 目录已经不存在(插件本体与密钥都在 wujies/wujies-plugins/)。

逐项:哪个值真的生效

配置项yml 里写的注解兜底容器里被覆盖成
deploy.plugin-dirplugins-distplugins-dist/app/plugins-dist
deploy.frontend-dir../wujies-admin/app-market-dist/remote-apps../plus-ui/app-market-dist/remote-apps(与 yml 不一致)/app/remote-apps
security.modeenforcewarn(与 yml 不一致)安装表单的 SECURITY_MODE,默认 warn
security.public-key源码里的开发公钥空串无环境变量(只能改 yml + 重建镜像)
security.allowed-frontend-extensions22 个扩展名同一份列表无环境变量
editionofficialofficialofficial(服务端包)/ client(用户版包)
catalog.modelocallocallocal / remote
catalog.base-url空空客户端版由表单 CATALOG_BASE_URL 填
catalog.token空空表单 CATALOG_TOKEN(服务端预填随机值)
catalog.connect-timeout-ms / read-timeout-ms3000 / 150003000 / 15000无环境变量
plugin-runtime.enabledyml 里没有这个键true无环境变量(见下,只能 -D)
task.timeout-minutesyml 里没有这个键10无环境变量
task.sweep-initial-delay-ms / sweep-interval-msyml 里没有这个键30000 / 60000无环境变量

task.* 三个键由 AppTaskTimeoutSweeper 读取:任务「多久没有进度」就判定为失败(判据是 update_time,不是开始时间),以及清扫器的首次延迟与间隔。要调就得自己往 yml 里加这三行 —— 现在只有注解兜底。

三处必须写清的口径差

deploy.frontend-dir:yml 与注解兜底指向两个不同的目录

位置值
application.yml(实际生效)../wujies-admin/app-market-dist/remote-apps
@Value 兜底(AppPackageDeployService、PluginAppInstaller)../plus-ui/app-market-dist/remote-apps

两个值都是相对进程工作目录解析的(Paths.get(...).toAbsolutePath())。本机启动脚本 script/prune/start-host.ps1 会先把工作目录切到 wujies-platform/,所以 yml 那个值解析成工作区顶层的 wujies-admin/app-market-dist/remote-apps —— 那个 wujies-admin/ 目录里只有 app-market-dist/,没有 src/、没有 package.json。

⚠️ 别被名字骗到:宿主前端仓库是 plus-ui/,它的 package.json 里 name 恰好写着 wujies-admin(这是历史上误导过两次排查的根源)。plus-ui 的 dev server 与构建只认自己仓库根下的 app-market-dist/(plus-ui/vite/plugins/appMarketStatic.ts 里 path.resolve(root, 'app-market-dist'),URL 前缀只有 /app-runtime/ 与 /remote-apps/ 两个)。也就是说这两个目录是独立物理目录:后端默认写到工作区顶层,浏览器却从 plus-ui 取 —— 表现就是「安装成功、菜单也在,点开是远程视图不可用」。

本机想让 dev server / 构建直接读到后端落位的产物,把 frontend-dir 改成注解兜底那个值(../plus-ui/app-market-dist/remote-apps),然后重建宿主。容器化部署不走这条路:两边靠同一个卷对齐,见下面的覆盖表。

security.mode:同一个键有三个值

位置值
application.ymlenforce
注解兜底(PackageSecurityInspector、SignatureReadinessService)warn
1Panel 安装表单 SECURITY_MODE(data.yml 里 required: true、默认 warn)warn

compose 里是 APP_MARKET_SECURITY_MODE: ${SECURITY_MODE},环境变量优先级高于 yml —— 所以容器化部署装完默认是 warn,要 enforce 必须在安装表单(或装完去「参数」)显式改成 enforce。而 warn 是「检查并记录、照常安装」,属于为「存量包都还没签名」准备的过渡档。

结论:不要靠兜底值推断行为。确认实际值的两条路 —— 看部署/上传日志里的安全检查结论,或调 GET /app/console/signature-readiness(权限 app:console:list),返回里带 mode:

curl -H "Authorization: <你的登录 token>" \
     http://<host>:<后端接口端口>/app/console/signature-readiness
切 enforce 的前提是 public-key 已配

public-key 留空时,enforce 会拒绝所有包(包括你自己签的),报错里会明说原因。而这个键没有环境变量,只能改 yml + 重建镜像。三种模式的取舍见 /package/security-modes。

plugin-runtime.enabled:yml 里根本没有这个键

谁读它wujies-admin 的 PluginClassPathLoader(Spring 之前执行的启动加载器)与 PluginRuntimeManager 的 @Value
怎么读System.getProperty("app-market.plugin-runtime.enabled", "true")
代码默认值true(两处刻意保持一致)
怎么改JVM 参数:-Dapp-market.plugin-runtime.enabled=false

写进 yml 无效(启动加载器在 Spring 之前执行,只认系统属性)。写成环境变量 APP_MARKET_PLUGIN_RUNTIME_ENABLED 也不行 —— @Value 读得到,启动加载器读不到,于是变成最糟的组合:启动加载器让路(以为运行时接管了),运行时又没开 → 插件完全不加载。要用就用 -D。

机制触发开关为 true 时
PluginClassPathLoader(兼容)启动期扫插件目录挂到 TCCL让路,不加载
PluginRuntimeManager(默认)启动期 + 运行期,每插件独立类加载器接管

⚠️ 容器里不能关这个开关:deploy/docker/backend/entrypoint.sh 只传 -Dapp-market.deploy.plugin-dir="${PLUGIN_DIR:-/app/plugins-dist}",没有 -Dloader.path(那个参数只在打包成 jar 后由 PropertiesLauncher 识别)。关掉运行时,容器里就既没有 loader.path 也没有插件运行时,插件一个都不会加载。

必须与别的组件对齐的三项

配置项必须对齐到不对齐的表现怎么核对
deploy.plugin-dir本机:-Dloader.path(start-host.ps1 的 -PluginDir);容器:entrypoint.sh 的 -Dapp-market.deploy.plugin-dir(同一值域 /app/plugins-dist)后端把 jar 落到 A、加载器从 B 找 → 应用显示已启用但接口 404,「后端产物未加载」看启动命令:start-host.ps1 会打印 java … -Dloader.path=…;容器里 docker exec <后端容器> printenv APP_MARKET_DEPLOY_PLUGIN_DIR
deploy.frontend-dirnginx /remote-apps/ 的 alias 物理路径(同一卷)/remote-apps/<appCode>/index.js 404,远程视图不可用打开 wujies-platform/deploy/docker/frontend/nginx.conf 看 location ^~ /remote-apps/ 的 alias;容器里核对 docker exec <后端容器> printenv APP_MARKET_DEPLOY_FRONTEND_DIR
security.public-key发布方 wujies/wujies-plugins/keys/private.pem 对应的公钥换钥只贴了新公钥、没重签旧包 → 已发布版本全部验签失败GET /app/console/signature-readiness 的分类计数(signed / unsigned / invalid / unverifiable);没配公钥时有签名的包会落到 unverifiable 而不是 invalid

本机核对相对路径解析结果(工作目录必须是 wujies-platform/):

# 打印 frontend-dir 解析后的绝对路径
[System.IO.Path]::GetFullPath('..\wujies-admin\app-market-dist\remote-apps')

nginx 侧与卷的完整对齐关系(四处路径 + 两行挂载)见 /deploy/frontend。

容器化部署覆盖了哪些键

1Panel 包内的 compose(deploy/1panel/apps/wujies-platform-server/5.6.3/docker-compose.yml 与 …-client/…)用环境变量覆盖 yml:

容器内变量等价键服务端包用户版包
APP_MARKET_DEPLOY_PLUGIN_DIRapp-market.deploy.plugin-dir/app/plugins-dist/app/plugins-dist
APP_MARKET_DEPLOY_FRONTEND_DIRapp-market.deploy.frontend-dir/app/remote-apps/app/remote-apps
APP_MARKET_SECURITY_MODEapp-market.security.mode表单值,默认 warn同左
APP_MARKET_EDITIONapp-market.editionofficial(写死)client(写死)
APP_MARKET_CATALOG_MODEapp-market.catalog.modelocal(写死)remote(写死)
APP_MARKET_CATALOG_BASE_URLapp-market.catalog.base-url空串(写死)表单 CATALOG_BASE_URL
APP_MARKET_CATALOG_TOKENapp-market.catalog.token表单 CATALOG_TOKEN(面板预填随机值)表单 CATALOG_TOKEN,必须与服务端一致

三个必须记住的点:

  1. edition 与 catalog.mode 在容器里不是自由项:服务端包写死 official + local,用户版包写死 client + remote。用户版配 local 会直接启动失败并在报错里写明该改哪一行(MarketEditionStartupCheck),不是静默降级。见 /advanced/catalog-and-edition。
  2. public-key 没有环境变量:容器的公钥来自镜像里的 jar。换公钥 = 改 yml → 重建 wujies-admin.jar → 重建后端镜像 → 重装/重启。只改面板参数改不到它。
  3. plugin-runtime.enabled 在容器里同样只能靠 -D,而 entrypoint 没有设置它(保持默认 true)。关掉之前先读上一节。⚠️ 别指望用 JAVA_OPTS 追加:后端容器当前不读它 —— deploy/docker/backend/entrypoint.sh(deploy/dist/1panel/docker/backend/ 下的副本相同)最后一行是 exec java -Dapp-market.deploy.plugin-dir="${PLUGIN_DIR:-/app/plugins-dist}" -jar /app/app.jar,没有引用 $JAVA_OPTS;Dockerfile 里的 JAVA_OPTS="" 目前是死变量。要加 JVM 参数得改这一行或覆盖容器入口。

与应用市场相关的三处「排除名单」

配置被裁剪时,这三处最容易连带被删掉,删了就是运行期故障。

security.excludes 里的 /app/catalog/**

security:
  excludes:
    # …其它条目…
    # 应用目录服务接口:服务间调用,没有本机会话。
    # 已由 AppCatalogApiController 用共享令牌(app-market.catalog.token)兜底。
    - /app/catalog/**

/app/catalog/** 是服务间接口,调用方是另一个部署,没有本机登录会话,所以不能挂登录与权限注解。放行之后由 X-Catalog-Token 请求头兜底:服务端 catalog.token 为空 → 不校验调用方(内网自用可以,公网必须配);配了 → 比对请求头,不一致抛「目录接口令牌校验失败」。令牌是明文请求头,只防误连与扫描器,真正的防护是别把后端端口暴露到公网。

security.tenant.excludes 里应用市场相关的 6 张表

security:
  tenant:
    enable: true
    excludes:
      # …其余平台表(sys_menu / sys_tenant / sys_area / flow_spel 等)…
      - app_info
      - app_version
      - app_category
      - app_version_menu
      - app_dependency
      - app_plan
表为什么全局漏配的后果
app_info / app_version / app_category应用目录是平台资产,所有租户看同一份每个租户一份目录,上架一次只对自己可见;已发布版本查询也查不到
app_version_menu菜单归属映射(版本 → 菜单),菜单行本身也不分租户菜单打不上「归属应用」标签,彻底删除时也清不干净
app_dependency / app_plan依赖关系与套餐定义都是目录的一部分依赖解析在第二个租户下失效,套餐列表按租户分裂

不在此列的是安装侧数据:app_install、app_install_task、app_schema_history、app_subscription、app_audit_log 都带 tenant_id,必须按租户隔离 —— 它们记的是「这台部署的哪个租户装了什么、迁移执行到哪一步」。把它们加进 excludes,会让第二个租户装同一应用时重复执行 DDL。

xss.excludeUrls 里的 /app/console、/app/version、/app/package

xss:
  enabled: true
  excludeUrls:
    # …其它条目…
    # 应用市场:这些接口的 JSON 里合法地包含尖括号与富文本,
    # 若不排除,XssHttpServletRequestWrapper 会把整个请求体当 HTML 清洗,
    # 导致 "engine":">=5.6.2 <6.0.0" 这类版本约束把 JSON 拦腰截断
    - /app/console
    - /app/console/**
    - /app/version
    - /app/version/**
    - /app/package
    - /app/package/**

版本兼容约束(engine 字段)、应用描述 / README 富文本、包清单字段里都合法地含 < 与 >。被当成 HTML 清洗的结果不是丢几个字符,而是把 JSON 截断,Jackson 报 Unexpected end-of-input: was expecting closing quote —— 表现像「上架表单点保存就报错」的前端 bug,实际是 XSS 过滤器动了请求体。

排除之后要自己负责转义

这些内容不经 XSS 清洗就落库。前端展示应用描述这类富文本时必须自己做安全处理(用已转义或白名单化的渲染组件),不要把原始 HTML 直接 v-html 出去。

未实现的配置项:deploy.c-frontend-dir

文档站另有页面(/advanced/remote-app 与 /reference/config)描述 C 端整站落位目录 app-market.deploy.c-frontend-dir 与 /apps-c/ 前缀。当前平台源码里没有这个键:application.yml 没有、wujies-app-market 下没有任何 @Value 读它、两个 1Panel 包的 compose 与 nginx 模板里也没有 /apps-c/ 这个 location。

按现状配置即可,别照那个键去改 —— 写了不生效(@Value 不存在),真正的落位只有 frontend-dir 一个根。

相关阅读

目录