配置项索引
本文是 1.x 的归档文档(冻结于 1.x)。该分支只接收勘误修正, 内容基本不再变动;文中的源码路径、配置项与版本号均以 1.x 为准,不建议用于新项目。
新项目请查看 5.x 最新文档。已在 1.x 上运行的项目,本文仍是可靠依据。
本页收录「应用市场」的全部可配置项。每项都标注了它在源码中的位置。
- yml 显式值:
wujies-platform/wujies-admin/src/main/resources/application.yml里写出来的值。 - 注解兜底值:
@Value("${app-market.xxx:默认值}")里的默认值。没有在 yml 里出现的键,以注解为准; 两者不一致时必须按 yml 的实际值去理解运行行为,本页在「默认值」列用yml 值 / 注解兜底的形式标出。
spring.profiles.active 当前为 prod。仓库里只有 application.yml、application-dev.yml、application-prod.yml
三个 profile 文件(没有 application-docker.yml),其中后面两个都没有任何 app-market.* 配置 ——
所以 Spring profile 层面没有覆盖,应用市场的配置只在 application.yml 一处。
唯一的例外是容器化部署:1Panel 部署包里的 docker-compose.yml 用环境变量覆盖了其中若干项,见
容器化部署的覆盖方式。
public-key、edition、catalog.*、security.* 都是 Spring 启动期读入(@Value / @PostConstruct)。
改了这些,必须重建 wujies-admin.jar 并重启才生效 —— application.yml 被打进 jar,只改源码目录里的
yml 而不重新打包,等于没改。
部署路径
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
app-market.deploy.plugin-dir | string(路径) | plugins-dist / 注解兜底 plugins-dist(容器化部署被环境变量覆盖为 /app/plugins-dist) | 后端插件 jar 的落位目录,也是宿主启动参数 -Dloader.path 指向的目录。包内 assets.backend 释放到这里根下。loader.path 不递归子目录,所以迁移脚本落在 migrations/<appCode>/ 子目录里是安全的。消费方:AppPackageDeployService、PluginRuntimeManager、PluginAppInstaller、AppMigrationService、宿主的 PluginClassPathLoader(读系统属性)。 |
app-market.deploy.frontend-dir | string(路径) | yml 显式 ../wujies-admin/app-market-dist/remote-apps / 注解兜底 ../plus-ui/app-market-dist/remote-apps(两者不一致,见下) | 前端远程应用产物的落位目录。包内 frontend/ 整棵树释放到 <frontend-dir>/<appCode>/(去掉 frontend/ 前缀)。该物理目录必须就是宿主 /remote-apps/<appCode>/ 的位置。消费方:AppPackageDeployService、PluginAppInstaller。 |
app-market:
deploy:
# 后端插件 jar 落位目录,必须与宿主启动参数 -Dloader.path 一致
# ⚠️ loader.path 只加载该目录**根下**的 jar,不递归子目录
plugin-dir: plugins-dist
# 前端远程应用落位目录,必须与宿主 /remote-apps/ 的物理路径一致
# ⚠️ 前后端分离部署时必须改成共享/分发目录,否则前端服务取不到产物
frontend-dir: ../wujies-admin/app-market-dist/remote-apps两个值都是相对路径,按进程工作目录解析(Paths.get(...).toAbsolutePath())。script/prune/start-host.ps1
因此会先 Set-Location 到 wujies-platform 根目录,工作目录错了就会把产物写到别处。
⚠️ frontend-dir 的 yml 显式值与注解兜底值不一致:yml 里是 ../wujies-admin/...(工作区顶层的
前端产物落位目录,基准是工作目录 wujies-platform/),注解兜底是 ../plus-ui/...(plus-ui 自己的
仓库根)。以 yml 为准;而 plus-ui 的 dev server / 构建只认自己仓库根下的 app-market-dist
(vite/plugins/appMarketStatic.ts),两者是独立物理目录 —— 详见 仓库与目录结构。
插件运行时
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
app-market.plugin-runtime.enabled | boolean | yml 未配置 / 注解兜底 true | 是否启用「每插件独立上下文」运行时。开启时部署完直接热加载(不用重启);关闭则退回 PluginClassPathLoader + loader.path 的启动期加载,部署只落位、重启才生效。这个键必须用 JVM 参数 -D 设置,不能只写在 yml 里,见 不能只写在 yml 里的配置。 |
app-market.task.timeout-minutes | long(分钟) | yml 未配置 / 注解兜底 10 | 任务超过多久没有任何进度就判定为失败。由 AppTaskTimeoutSweeper 按 update_time 判定(不用 start_time,避免误杀耗时较长但一直在推进的任务)。只在进度卡死时触发,不是任务总时长上限。 |
app-market.task.sweep-initial-delay-ms | long(毫秒) | yml 未配置 / 注解兜底 30000 | 卡死任务清扫器的首次延迟(@Scheduled 的 initialDelayString)。 |
app-market.task.sweep-interval-ms | long(毫秒) | yml 未配置 / 注解兜底 60000 | 卡死任务清扫器的固定间隔(@Scheduled 的 fixedDelayString)。 |
@Value("${app-market.deploy.plugin-dir:plugins-dist}")
private String pluginDir;
@Value("${" + ENABLED_PROPERTY + ":true}") // ENABLED_PROPERTY = "app-market.plugin-runtime.enabled"
private boolean enabled;| 位置 | 默认值 |
|---|---|
PluginClassPathLoader.java(宿主,读系统属性) | "true" —— 让路 |
PluginRuntimeManager 的 @Value(实际注入值) | true |
PluginRuntimeManager.isEnabledByProperty()(读系统属性) | "false",且全仓无调用方(死代码) |
PluginRuntimeManager 的类注释 | 写成「默认 false(仍走旧路径)」 |
实际生效的是 true:启动加载器让路,由插件运行时按每插件独立上下文加载。类注释与
isEnabledByProperty() 的 "false" 都是未同步的残留。application.yml 里没有这个键,
全靠注解兜底;script/、Dockerfile、docker-compose 里也都不设置它。
应用目录(catalog)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
app-market.catalog.mode | enum local / remote | yml 显式 local / 注解兜底 local | 目录访问方式。local 直连本库,remote 走 base-url 指向的目录服务。装配用 @ConditionalOnProperty 二选一:LocalAppCatalogService 匹配 local(matchIfMissing = true),RemoteAppCatalogService 匹配 remote。刻意不用 @ConditionalOnMissingBean(那依赖 bean 定义顺序)。 |
app-market.catalog.base-url | string(URL) | yml 显式 '' / 注解兜底 ''(空串) | 目录服务地址。mode=remote 时必填,缺失则 RemoteAppCatalogService 构造时抛 IllegalStateException,启动失败。 |
app-market.catalog.token | string | yml 显式 '' / 注解兜底 ''(空串) | 服务间共享令牌,两端必须配成一致。服务端(AppCatalogApiController)留空表示 /app/catalog/** 不校验调用方,只在启动时打一条 WARN。该接口只暴露平台级目录数据,但不应该暴露在公网。 |
app-market.catalog.connect-timeout-ms | int(毫秒) | yml 显式 3000 / 注解兜底 3000 | 远程目录服务的连接超时。目录读取不该拖垮安装流程,宁可快速失败。 |
app-market.catalog.read-timeout-ms | int(毫秒) | yml 显式 15000 / 注解兜底 15000 | 远程目录服务的读取超时。 |
catalog:
# local 直连本库(默认,单机部署用这个)
# remote 走 base-url 指向的目录服务(多部署集中时用)
mode: local
# 目录服务地址;mode=remote 时必填,否则启动即失败
base-url: ''
# 服务间共享令牌。服务端(本文件的这个值)与调用方配成一致;
# 服务端留空表示 /app/catalog/** 不校验调用方 —— 内网自用可以,公网必须配。
token: ''
# 远程模式的超时:目录读取不该拖垮安装流程,宁可快速失败
connect-timeout-ms: 3000
read-timeout-ms: 15000目录服务接口 /app/catalog/** 已在 security.excludes 里(服务间调用没有本机会话),
身份由 app-market.catalog.token 兜底。详见 目录与版本形态。
版本形态(edition)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
app-market.edition | enum official / client | yml 显式 official / 注解兜底 official | 同一套代码扮演两个角色。official 官方版:目录在自己库里 + 含「应用上架」全套功能 + 对外提供 /app/catalog/**。client 用户版:不含上架端,目录从官方 API 读。取值经 StringUtils.trim + equalsIgnoreCase 规范化,大小写不敏感;任何非 client 的值都按 official 处理(不报错)。 |
edition: officialMarketEditionStartupCheck 在 @PostConstruct 里校验组合,不自洽就抛 IllegalStateException:
edition=client且catalog.mode != remote→ 启动失败(这个组合「能起来但什么都装不了」);edition=client且catalog.base-url为空 → 启动失败。
client 下上架端接口被 MarketEditionGuardFilter 拦截,返回 403 并附明确文案(不是 404、不是静默失效)。
例外只有 /app/console/owned-menus。详见 目录与版本形态。
包安全(security)
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
app-market.security.mode | enum off / warn / enforce | yml 显式 enforce / 注解兜底 warn(不一致);容器化部署被 APP_MARKET_SECURITY_MODE 覆盖(1Panel 安装表单默认 warn) | 包安全检查模式。off 完全不检查;warn 检查并记录问题但照常安装/上架;enforce 有问题就拒绝。这是一处 yml 与注解兜底值不一致的配置,见下方说明。消费方:PackageSecurityInspector、SignatureReadinessService。 |
app-market.security.public-key | string(PEM 或裸 base64) | yml 显式已配置一把公钥(BEGIN PUBLIC KEY 块)/ 注解兜底 ''(空串) | 包签名公钥(X.509 SubjectPublicKeyInfo 的 PEM,或裸 base64)。留空表示尚未配置校验能力 —— 此时 enforce 会拒绝所有包(包括自己签的),报错里会说明原因。启动期读入,改了必须重建宿主 + 重启。 |
app-market.security.allowed-frontend-extensions | string(逗号分隔的扩展名列表) | yml 显式 .js,.mjs,.cjs,.css,.json,.svg,.png,.jpg,.jpeg,.gif,.webp,.ico,.bmp,.woff,.woff2,.ttf,.otf,.eot,.map,.txt,.html,.wasm / 注解兜底同一份列表(字面量逐字符一致) | 包内 frontend/ 下允许出现的文件类型。白名单之外一律拒绝;.jsp / .php / .war / .jar 等由代码硬拒,不受此项影响。前端目录就是宿主的静态资源目录,所以这项管的是结构性问题。 |
security:
# off 完全不检查(等价于改造前的行为)
# warn 检查并记录问题,但照常安装/上架
# enforce 有问题就拒绝 ← 当前
mode: enforce
public-key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAowY2/3CZDZrqwo7jber8
...
-----END PUBLIC KEY-----
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两者不同,且以 yml 为准。application.yml 的注释解释了原因:warn 是给「存量包都还没签名」的过渡期用的,
默认 enforce 会让它们全部装不上;2026-09-13 把所有已发布版本就地重签后(signature-readiness 给出
ready=true、signed=13 / unsigned=0)才正式切到 enforce。
如果这段 yml 被删掉或被 profile 覆盖,运行行为会静默退回 warn —— 未签名包会被照常安装,没有任何报错。
排查「为什么未签名的包也装上了」时,先确认实际的 mode 值。
切 enforce 的前提是 public-key 已配置:没配公钥时 enforce 会拒绝所有包。
容器化部署更容易踩这一条:1Panel 安装表单的 SECURITY_MODE 默认值就是 warn,
compose 把它作为 APP_MARKET_SECURITY_MODE 传进来,覆盖掉镜像里 yml 的 enforce。
镜像里的 yml 是 enforce,实际生效的却可能是 warn。
密钥生成与轮换
公钥不是秘密,放在 yml 里没有安全问题 —— 它只回答「这个包是不是这把私钥签的」。私钥只留在发布方,
默认在插件仓库的 keys/(wujies/wujies-plugins/keys/,已 gitignore),绝不能进仓库。
java -cp wujies-modules/wujies-app-market/target/classes `
cn.wujies.appmarket.security.PackageSignerMain keygen wujies/wujies-plugins/keys换钥要同时做三件事:① 重新生成;② 把新 public.pem 贴进 public-key(package.ps1 -SyncPublicKey 可代劳);
③ 重建宿主并重启。旧钥签过的包届时全部作废。详见 签名与发布 与
安全模式。
其它宿主配置
应用市场本身没有独立的文件上传路径配置:安装包走 wujies-common-oss(SysOssService.upload),
对象存储键前缀是代码里的常量 app-market/package/(包)与 app-market/asset/(资产),不可配置。
本机落位路径由 app-market.deploy.* 决定。相关的宿主级上限与临时目录如下。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
spring.servlet.multipart.max-file-size | string(容量) | yml 显式 100MB | 单个请求文件大小上限。应用包上传(POST /app/package/parse,multipart)受它约束。 |
spring.servlet.multipart.max-request-size | string(容量) | yml 显式 1000MB | 整个请求大小上限。 |
spring.servlet.multipart.location | string(路径) | yml 未配置(用容器/系统临时目录);容器化部署由 SPRING_SERVLET_MULTIPART_LOCATION: /app/temp 指定 | 上传文件的临时落盘目录。挂在持久卷之外时,进程被 SIGKILL 会留下垃圾文件。 |
server.undertow.max-http-post-size | string(容量) | yml 显式 1GB | Undertow 的 HTTP POST 内容上限。 |
mybatis-plus.mapperPackage | string | yml 显式 cn.wujies.**.mapper | Mapper 接口扫描包。插件 jar 里若带 Mapper 接口,必须落在这个表达式覆盖的包名内才会被注册。 |
mybatis-plus.mapperLocations | string | yml 显式 classpath*:mapper/**/*Mapper.xml | Mapper XML 资源位置,用宿主类加载器扫描。插件自带的 XML 不靠它:插件 XML 随插件 jar 走,由 PluginMapperRegistrar 在插件加载时只读传入的那个 jar 动态注册进宿主 SqlSessionFactory(它刻意不写 classpath*: —— 那会把宿主的映射文件也捞一遍,撞 Result Maps collection already contains value,宿主直接起不来)。 |
mybatis-plus.typeAliasesPackage | string | yml 显式 cn.wujies.**.domain | 实体别名扫描包。 |
spring:
servlet:
multipart:
# 单个文件大小
max-file-size: 100MB
# 设置总上传的文件大小
max-request-size: 1000MBmybatis-plus:
# 多包名使用 例如 cn.wujies.**.mapper,org.xxx.**.mapper
mapperPackage: cn.wujies.**.mapper
# 对应的 XML 文件位置
mapperLocations: classpath*:mapper/**/*Mapper.xml
# 实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: cn.wujies.**.domain容器化部署的覆盖方式
wujies-platform/deploy/1panel/apps/wujies-platform-{server,client}/5.6.3/docker-compose.yml 用环境变量覆盖上面这些配置项。
Spring Boot 的 relaxed binding 会把大写下划线映射成小写点号,所以 APP_MARKET_CATALOG_BASE_URL 就是
app-market.catalog.base-url:
APP_MARKET_EDITION: official
APP_MARKET_CATALOG_MODE: local
APP_MARKET_CATALOG_BASE_URL: ""
APP_MARKET_CATALOG_TOKEN: ${CATALOG_TOKEN}
APP_MARKET_DEPLOY_PLUGIN_DIR: /app/plugins-dist
APP_MARKET_DEPLOY_FRONTEND_DIR: /app/remote-apps
APP_MARKET_SECURITY_MODE: ${SECURITY_MODE}这几个覆盖值与本地部署的默认值不同,替换了 yml 里的相对路径:
| 环境变量 | 容器内取值 | 说明 |
|---|---|---|
APP_MARKET_DEPLOY_PLUGIN_DIR | /app/plugins-dist | 容器里的绝对路径。entrypoint.sh 会把它同时传给 -Dapp-market.deploy.plugin-dir,所以 yml 与启动加载器看到的是同一个目录。 |
APP_MARKET_DEPLOY_FRONTEND_DIR | /app/remote-apps | 后端把前端产物释放到这里;nginx 容器挂同一个卷到 /var/www/remote-apps 并以 alias /var/www/remote-apps/ 对外提供 /remote-apps/。 |
APP_MARKET_SECURITY_MODE | 来自安装表单的 SECURITY_MODE | 注意它会覆盖 yml 里的 enforce。compose 注释说明:首次安装还没有签名公钥,enforce 会拒绝所有包,所以容器化部署默认走 warn。 |
APP_MARKET_EDITION / CATALOG_MODE / CATALOG_BASE_URL / CATALOG_TOKEN | 官方版 official+local;用户版 client+remote | 用户版(1Panel 包 wujies-platform-client)把这三个键设成 client/remote/官方地址,与 yml 默认值相反。 |
容器化部署里排查「为什么值和 application.yml 里写的不一样」时,先看 docker-compose.yml 的
environment: 段。APP_MARKET_SECURITY_MODE 是最容易踩的一个:镜像里 yml 是 enforce,
但如果安装表单里 SECURITY_MODE 选了 warn,实际生效的就是 warn。
必须与其它东西对齐的配置项
这一节里的配置改错了不会报错,只会静默失效 —— 表现为「装上了却跑不起来」「升级成功了页面还是老样子」。
plugin-dir ↔ -Dloader.path
app-market.deploy.plugin-dir 与宿主的启动参数 -Dloader.path 必须是同一个目录。
loader.path 只被打包后的 PropertiesLauncher 识别,从 IDE 直接跑主类时会被静默忽略
(PluginClassPathLoader 就是为消灭这个情况而存在的)。
# script/prune/start-host.ps1
$args += "-Dloader.path=$PluginDir" # 默认 $PluginDir = plugins-dist两处默认值同为 plugins-dist,都按工作目录解析。另外 PluginClassPathLoader 读的是系统属性
app-market.deploy.plugin-dir(不是 yml),兜底也是 plugins-dist —— 三处默认值必须一致。
实际设置它的地方只有两处:
| 启动方式 | 谁设置 | 取值 |
|---|---|---|
| 本机脚本 | script/prune/start-host.ps1 | -Dloader.path=$PluginDir(默认 plugins-dist),工作目录设为 wujies-platform 根 |
| 容器 | deploy/docker/backend/entrypoint.sh | -Dapp-market.deploy.plugin-dir="${PLUGIN_DIR:-/app/plugins-dist}",不传 loader.path |
# application.yml(相对路径)
app-market.deploy.plugin-dir: <dir>
# 系统属性兜底(PluginClassPathLoader.java 同名属性,默认 plugins-dist)
-Dapp-market.deploy.plugin-dir=<dir>
# 本机启动(script/prune/start-host.ps1)
-Dloader.path=<dir>script/prune/prune-plugins.ps1 / .sh 也各自硬编码同一个 plugins-dist 作为推断默认值;清理脚本与启动脚本
指向不同目录时会清理错地方。
它只加载指定目录根下的 jar —— 所以插件目录必须扁平,不能把 jar 放进子目录(迁移脚本落在
migrations/<appCode>/ 子目录里是安全的,它们不是 jar)。
至于"同一个 appCode 两个版本并存":两个加载时机都按 active-plugins.txt 选版本(2026-10-02 起),
启动期是 PluginClassPathLoader.listJars、运行期是 PluginRuntimeManager —— 不在清单里的残留 jar
会被刻意跳过。"两份同样的 mapper XML → Result Maps collection already contains value → 服务直接起不来"
只在 active-plugins.txt 缺失或为空(此时不过滤)时复现。script/prune/start-host.ps1 仍先跑
prune-plugins.ps1,是为了清磁盘与回收被占用的 jar 句柄。
-Dloader.path 只在 script/prune/start-host.ps1 这一条本机启动路径上被设置。
deploy/docker/backend/entrypoint.sh 走的是另一条路,只传插件目录属性:
exec java $JAVA_OPTS -Dapp-market.deploy.plugin-dir="${PLUGIN_DIR:-/app/plugins-dist}" -jar /app/app.jar所以容器里插件的加载完全依赖 PluginRuntimeManager(它既在启动期加载、也支持运行期热加载)。
这也意味着 plugin-runtime.enabled 在容器里不能被关掉 —— 关掉就既没有 loader.path
也没有插件运行时,插件不会被加载。
顺带一提:wujies-admin/pom.xml 里配了 <layout>ZIP</layout>,只有这个布局才认 -Dloader.path;
默认的 JarLauncher 会静默忽略它。
frontend-dir ↔ 宿主 /remote-apps/<appCode>/ 的物理路径
AppRuntimeController 拼出的入口是 /remote-apps/<appCode>/<文件名>,前端用动态 import() 加载它。
这个 URL 前缀由三处共同约定:
AppRuntimeController的常量REMOTE_APP_URL_PREFIX = "/remote-apps/";- 前端静态服务插件
plus-ui/vite/plugins/appMarketStatic.ts—— 把 URL 前缀/remote-apps/映射到 磁盘目录plus-ui/app-market-dist/(dev 中间件直接返回文件,build 时整目录复制进dist/); - 后端落位目录
app-market.deploy.frontend-dir,yml 当前值是../wujies-admin/app-market-dist/remote-apps(注解兜底../plus-ui/app-market-dist/remote-apps,与 yml 不一致)。
三者的相对位置关系:后端工作目录是 wujies-platform/,所以 ../wujies-admin/app-market-dist/remote-apps
指向工作区顶层那个 wujies-admin/ 目录;而 plus-ui 的 dev server / 构建只认自己仓库根下的
app-market-dist/(appMarketStatic.ts 里 path.resolve(root, 'app-market-dist'))。
两者是独立物理目录,"后端落位成功"与"浏览器能取到"可能对不上 —— 详见 仓库与目录结构。
这个物理目录随环境变化,代码里没有单一真相:
| 环境 | /remote-apps/<appCode>/ 的物理位置 | 由谁提供 |
|---|---|---|
| 本机 dev | plus-ui/app-market-dist/remote-apps/<appCode>/ | appMarketStatic.ts 在 Vite 内部中间件之前注册静态服务,直接返回文件 |
| 本机 build | 整棵 app-market-dist/ 被复制进 Vite outDir(默认 dist/)→ dist/remote-apps/<appCode>/ | 同上,writeBundle 阶段 fs.cpSync |
| 容器 | 宿主卷 ./data/remote-apps;后端容器挂到 /app/remote-apps、nginx 容器挂到 /var/www/remote-apps | nginx location ^~ /remote-apps/ { alias /var/www/remote-apps/; }(^~ 必需,否则被末尾正则段抢走 → 404,见 前端部署与 nginx 坑 3) |
plus-ui/vite.config.ts 的 server.proxy 没有 /remote-apps 规则(只有 VITE_APP_BASE_API → 后端端口),
所以 dev 下这个 URL 靠上面第 2 条的中间件、不是代理。
单体部署下 frontend-dir 就是本地目录,前后端在同一台机器上。前后端分离部署时它必须改成一个共享/分发位置
(容器化部署就是靠共享卷做到的),否则后端把产物释放到自己的磁盘,前端服务读的是另一份 ——
表现为「安装成功、菜单点开是远程视图不可用」。
c-frontend-dir ↔ 宿主 /apps-c/<appCode>/ 的物理路径
C 端(PC / H5)是整站,不是运行期模块 —— 它自带 index.html 与路由,由一个独立的
URL 前缀对外提供。应用包里的 frontend-c/ 整棵树被释放到 app-market.deploy.c-frontend-dir/<appCode>/:
| 环境 | /apps-c/<appCode>/ 的物理位置 | 由谁提供 |
|---|---|---|
| 本机 dev | plus-ui/app-market-dist/apps-c/<appCode>/ | 同一个 appMarketStatic.ts(/apps-c/ 已在它的 URL 前缀列表里) |
| 本机 build | dist/apps-c/<appCode>/ | writeBundle 的 fs.cpSync(apps-c 就在被复制的 app-market-dist/ 里) |
| 容器 | 宿主卷 ./data/apps-c;后端挂 /app/apps-c、nginx 挂 /var/www/apps-c | nginx location ^~ /apps-c/ { alias /var/www/apps-c/; } |
为什么与 /remote-apps/ 分两个根:托管方式不同。那份是被宿主 import() 的 ESM 模块
(只要 index.js 一个入口),这份是一个站点(要 index.html、可能的 assets/*.js)。
混在一个目录里,<appCode> 会撞车。
nginx 的匹配顺序是「= 精确 → ^~ 前缀 → 正则 → 普通前缀」。
配置里有一条 location ~* \.(js|css|png|…)$,它会抢在普通前缀 location 之前命中
/remote-apps/x/index.js 与 /apps-c/x/assets/index-abc.js,然后按 root(/usr/share/nginx/html)
去找文件 → 一律 404,表现是插件页面白屏 / C 端打不开。
加了 ^~ 的前缀 location 才会「命中即停止,不再看正则」。
outDir(wujies/wujies-plugins/frontend/<code>-app/vite.config.ts)、package.ps1 的 -FrontendDist 默认值、
后端落位目录、AppRuntimeController 拼出的 URL —— 四处是同一个约定。历史上 demo-app 的 outDir
写成了 remote-apps/demo(少个 -app),后果是库里两条 remote:demo-app/demo/* 菜单永远解析不出来。
(该应用现已从应用目录里去掉,示例应用改由 hello 承担,见 示例应用。)
前端侧的构建期变量
后端 app-market.* 之外,前端还有两个影响远程应用解析的构建期变量。
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
VITE_APP_MARKET_REMOTE_APPS | string(code=entry 用逗号分隔) | 未找到 —— .env.development / .env.production / .env.docker 三个文件里都没有定义它 | 静态远程应用清单。bootstrap.ts 的解析器静态清单优先,取不到才去问后端 GET /app/runtime/remote-apps;dev 环境另有内置的 demo-app / gallery 两条兜底(demo-app 已从应用目录去掉,那条兜底已失效),生产环境兜底为空。 |
VITE_APP_MARKET_EDITION | enum official / client | 三个 env 文件里都被注释掉 | 版本形态的构建期兜底。运行时以服务端 GET /app/edition 为准,这个变量只在取不到时回落(默认 official)。写死成 client 会制造第二个真相。 |
# plus-ui/.env.development 第 41 行(.env.production / .env.docker 同理)
# VITE_APP_MARKET_EDITION = 'client'plus-ui/.gitignore 忽略了 app-market-dist/ —— 那是构建产物,不进版本管理。
security.public-key:启动期读入,改了必须重建 + 重启
PackageSecurityInspector 与 SignatureReadinessService 都用 @Value 注入它,只在应用启动时解析一次。
运行时改 yml 无效,改源码目录里的 yml 也无效(application.yml 被打进 wujies-admin.jar)。
正确顺序:改 yml → 重新 mvn -pl wujies-admin -am package → 重启进程。
package.ps1 -SyncPublicKey 会把新公钥写进 application.yml 的 public-key: 块,但不会重建宿主 ——
它会提醒这一点。没重启就发布,表现为「刚用新私钥签的包被 enforce 拒了」。
不能只写在 yml 里的配置
| 配置项 | 为什么 | 正确做法 |
|---|---|---|
app-market.plugin-runtime.enabled | PluginClassPathLoader 在 Spring 之前执行,读不到 yml,只能读系统属性。两个读取方必须看到同一个值。 | 用 JVM 参数 -Dapp-market.plugin-runtime.enabled=false 退回旧行为。 |
app-market.deploy.plugin-dir | 同上:PluginClassPathLoader 用 System.getProperty(...) 解析插件目录。 | 用 -Dapp-market.deploy.plugin-dir=<dir>(或 start-host.ps1 -PluginDir)。 |
启动加载器读系统属性时兜底是 true,所以它让路;而 Spring 起来后 PluginRuntimeManager 读到 yml 的
false,于是也不加载。两边都不干活 —— 插件彻底不加载,PluginRuntimeManager 会就此打一条醒目告警:
插件运行时被配置为关闭(app-market.plugin-runtime.enabled = false)。注意:插件启动器读的是系统属性, 两者必须一致 —— 若要退回旧的 loader.path 行为,请用 JVM 参数
-Dapp-market.plugin-runtime.enabled=false, 而不要只改 yml,否则插件不会被加载。
正确的两种组合只有:默认(两边都按 true 走插件运行时),或显式 -D...=false(两边都退回 loader.path)。
security.excludes 里与目录接口有关的一条
security:
excludes:
# 应用目录服务接口:服务间调用,没有本机会话。
# 已由 AppCatalogApiController 用共享令牌(app-market.catalog.token)兜底。
- /app/catalog/**把 /app/catalog/** 从排除列表里删掉,会让远程目录模式(catalog.mode=remote)立刻失效 ——
调用方没有本机会话,拿不到 token 就会被 Sa-Token 拦下。
security.tenant.excludes 里与应用市场相关的表
application.yml 的 security.tenant.excludes 列出不参与多租户隔离的表。其中与应用市场相关的有 6 张:
tenant:
excludes:
# 应用市场:应用目录属于平台级数据,不按租户隔离
# (带 tenant_id 的 app_install / app_install_task / app_subscription / app_audit_log /
# app_schema_history 不在此列,交由多租户插件自动隔离)
- app_info
- app_version
- app_category
- app_version_menu
- app_dependency
- app_plan| 表 | 数据性质 | 为什么被排除 |
|---|---|---|
app_info | 应用主数据(编码、名称、上架状态、home_path) | 目录 = 平台级数据,全平台一份。多租户下同一款应用只能有一行,否则「上架一次要同步 N 遍」。 |
app_version | 应用版本(含 package_url / 校验和 / 清单) | 同上:版本是应用的属性,不是某个租户的资产。 |
app_category | 应用分类 | 市场页的筛选维度,必须全局统一,否则每个租户看到不同的分类树。 |
app_version_menu | 版本 → 菜单的归属映射(menu_id + menu_key) | 它映射的目标 sys_menu 本身就在排除列表里(菜单是全局行、不分租户),映射表跟着全局。 |
app_dependency | 应用依赖(app_id → dep_app_code + 版本约束) | 表结构里没有 tenant_id,只描述应用之间的关系,是目录的一部分。 |
app_plan | 应用套餐(价格、有效期、配额) | 表结构里没有 tenant_id,由平台方定义;哪个租户买了哪个套餐记在 app_subscription(带 tenant_id,不排除)。 |
相对的,以下表带 tenant_id 且刻意不排除,由多租户插件自动加上租户条件:
app_install(租户应用安装实例)、app_install_task(安装任务与日志)、app_subscription(订阅)、
app_audit_log(审计)、app_schema_history(迁移记账)。
未找到的项
以下内容在本仓库中未找到可配置项,因此本页不列出(列出来就是在编造默认值):
- 应用包解压上限(单条目 256MB、条目总数 5000):是
AppPackageDeployService/PackageDigest里的private static final常量,不可通过配置调整。 - 对象存储键前缀
app-market/package/与app-market/asset/:AppMarketConstants里的常量,不可配置。 - 应用市场专用的本地上传目录:未找到(落位路径由
app-market.deploy.*决定,上传走 OSS; 临时目录用的是 Spring 自己的spring.servlet.multipart.location,宿主 yml 里没设置)。 - Spring profile 级的
app-market.*覆盖:未找到(application-dev.yml/application-prod.yml均无app-market出现)。容器化部署的环境变量覆盖见上一节,不属于 profile。 security.excludes中其它与应用市场相关的条目:未找到(只有/app/catalog/**一条)。