快速开始
这一页只解决一件事:在开发机上从空库走到「装上第一个应用」,把数据库、后端宿主、插件运行时、前端、迁移与菜单这条链路走通。
读完之后你能回答下面这些问题:
| 问题 | 本文位置 | 权威依据 |
|---|---|---|
三个仓库各是什么,wujies-admin 指的到底是哪一个 | §1 | wujies-platform/AGENTS.md §0 |
| 要导哪些 SQL、按什么顺序导 | §2 | script/sql/README.md、deploy/tools/build-1panel.ps1 |
| 怎么编译、怎么启动后端(并真的加载插件) | §3–§4 | script/prune/start-host.ps1、wujies-admin/src/main/resources/application.yml |
| 怎么起前端,它连的是哪个后端 | §5 | plus-ui/vite.config.ts、plus-ui/.env.development |
| 怎么装一个应用,怎么判断它真的生效了 | §6–§7 | AGENTS.md §3.2、§3.4 |
| 怎么跑测试 | §8 | 根 pom.xml |
本页不讲:怎么写一个应用(见 应用清单、必须遵守的不变量)、怎么打包上架(见 发布应用包)、生产部署(见 在部署里使用应用、升级)。
1. 先认清楚三个仓库
三个仓库没有共同的 git 根,各自提交、各自分支。
| 目录(相对工作区根) | 是什么 | 怎么认出来 |
|---|---|---|
wujies-platform/ | 后端(Java / Spring Boot)+ SQL 脚本 | 有 pom.xml、wujies-admin/、script/sql/ |
wujies/wujies-plugins/ | 应用本体(插件)+ 应用前端工程(pnpm workspace)+ 打包器 | 有 package.ps1、wujies-<app>/、frontend/<app>-app/、keys/ |
plus-ui/ | 宿主前端(Vue 3 / Vite) | 有 vite.config.ts、src/ |
| 写法 | 实际指 | 判据 |
|---|---|---|
wujies-platform/wujies-admin/ | 后端启动模块,产物 wujies-admin/target/wujies-admin.jar | 有 pom.xml、src/main/、Dockerfile |
工作区顶层的 wujies-admin/ | 只有 app-market-dist/,前端产物的落位目录 | 没有 package.json、没有 src/ |
plus-ui/ | 宿主前端仓库;它的 package.json 里 name 就是 wujies-admin(混淆的根源) | 有 vite.config.ts、src/ |
后端模块地图
| 路径 | 内容 |
|---|---|
wujies-admin/ | 启动模块。PluginClassPathLoader 在这里,在 Spring 之前执行 |
wujies-common/ | 基础设施:wujies-common-core、-web、-mybatis、-job、-plugin-sdk 等 |
wujies-modules/wujies-app-market/ | 应用市场:目录、插件运行时、上架端、包安全 |
wujies-modules/wujies-system/ | 用户 / 角色 / 菜单 / 租户等底座 |
wujies-modules/wujies-workflow/ | 工作流 |
wujies-modules/wujies-plugin-probe/ | 插件运行时探针 |
wujies-extend/ | 独立服务:调度中心、监控中心 |
有三样东西不存在,别去找:
- 平台里没有任何给应用准备的契约模块。
wujies-modules/下只有上表那几个,平台对cn.wujies.<app>.*的 Java 引用数量是 0。契约随插件走。 - 没有
wujies-job/wujies-generator/wujies-demo模块(已删除)。定时任务随应用走,宿主只提供wujies-common-job。 - 平台仓库里没有
plugins/目录。插件本体在另一个仓库。
应用长什么样
每个应用是 wujies-plugins/wujies-<app>/ 下一个自包含工程,契约与实现同处一室:
wujies-plugins/wujies-gallery/
├── app.json 清单:code / version / assets / menus
├── pom.xml 平台依赖一律 provided
├── migrations/01-create-gallery-tables.sql
└── src/main/
├── java/cn/wujies/gallery/{domain,domain/bo,domain/vo,mapper,service,service/impl,controller,open,listener}
└── resources/mapper/gallery/*.xml ← Mapper XML 在插件里,随 jar 走- 实体 / BO / VO / Mapper 接口 / Mapper XML / Service 接口与实现 / controller / open / listener / task 全在插件模块自己的
src/下; - Mapper XML 由
PluginMapperRegistrar在插件加载时动态注册进宿主的SqlSessionFactory,不是靠宿主扫mapperLocations; - 插件之间要对方的契约时,依赖兄弟插件构件(同样
provided)。
「契约单独放一个平台模块」「Mapper XML 必须留在平台侧」「靠宿主的 mapperLocations 扫插件里的 XML」都是 2026-09-30 之前的规则,已经不成立。看到这类描述(包括本站其它页面里的)以 AGENTS.md §2.2 为准。
应用有两种形态,都不奇怪:
| 形态 | 判据 | 例子 |
|---|---|---|
| 全栈应用 | 有 app.json、pom.xml、后端 src/,也有 frontend/<app>-app/ | wujies-gallery、wujies-exam、wujies-mall |
| 纯前端应用 | 目录下只有 app.json,包里没有 jar | wujies-finance |
2. 建库:按这个顺序导 SQL
全新部署只认平台自己的初始化脚本。应用自己的表不在这里,它们随应用包走。
| 顺序 | 文件 | 内容 |
|---|---|---|
| ① | wujies-platform/script/sql/platform_init.sql | 平台初始化:25 张 sys_* 表 + 地区 / 字典 / 参数 / 菜单 / 角色授权 / 租户 / 默认账号 |
| ② | wujies-platform/script/sql/update/ 下的 7 个脚本(见下) | 应用市场的表结构 |
| ③ | wujies-platform/script/sql/app_catalog_seed.sql | 应用目录种子(app_info / app_version / app_category)。不导市场里什么都没有 |
第 ② 步的清单与顺序(不是"按文件名排序",是这份固定清单):
app_market.sql 应用市场核心表(app_info / app_version / app_install / app_schema_history …)
app_market_app_model.sql install_mode / audience 列
fix_app_version_unique_index.sql UNIQUE(app_id, version_active)
app_market_home_path.sql home_path 列
app_market_sync_icon.sql app_info.icon 跟随菜单表
app_market_schema_history_fix.sql 迁移账本唯一键 (app_code, script)
app_market_register_modules.sql 把平台自带模块登记成应用依据是 deploy/tools/build-1panel.ps1 里的 $appMarketScripts 数组与 script/sql/README.md 第四节 —— 这也是安装包实际导入的那 7 个。
script/sql/ry_job.sql只在启用 SnailJob 调度中心时需要,它建的是调度中心自己的sj_*表。script/sql/update/里其余的platform_*.sql、app_market_<app>_migrate.sql、fix_*/cleanup_*是存量库专用的一次性脚本,全新部署不需要。
它已经不在仓库里了(script/sql/ 下没有这个名字的文件)。它是上游项目的种子数据,不是本仓库的库结构:缺 82 行参数、31 个字典类型、几百行菜单、整个地区数据,列结构也是上游版本,拿它起库要一层层打补丁。
两个容易混的地方:
script/sql/archive-upstream/里是上游的版本升级脚本update_<旧版本>-<新版本>.sql,不是种子数据,也不要执行;script/sql/backup/ry-vue-*.sql是工具链的输入(全库快照),同样不是种子数据。
本仓库的起点虽然是 RuoYi-Vue-Plus 5.X,改名为 Wujies 无界平台后已经独立演进、不跟随上游。唯一的交付库初始化是 platform_init.sql。
每个应用的表由包内 migrations/*.sql 在安装时自动执行。手工导入会让平台在"彻底删除"时不知道这个应用占了哪些表 —— 删表依据就是那些 CREATE TABLE 语句。
初始化是否完整,不要看 sys_config 里的 platform.init.version 这一行 —— 它不在 platform_init.sql 里,
而是后端容器首次启动、导入完成之后才写进去的标记(wujies-platform/deploy/docker/backend/entrypoint.sh,
约 L103–106,值取环境变量 PLATFORM_INIT_VERSION,插入是幂等的)。
所以手工导库时这一行必然不存在,拿它当判据会把一次成功的初始化误判成失败。
手工导库要判断初始化是否完整,去看表和数据在不在:
- 表:
sys_user/sys_menu/sys_config/app_info是否都建出来了; - 行:
sys_user里有没有user_id = 1的默认账号、sys_menu里有没有菜单行; app_info有没有目录种子行 —— 0 行说明第 ③ 步(app_catalog_seed.sql)没导进去,市场会是空的。
改连接串
| 改什么 | 文件 |
|---|---|
| MySQL / Redis / RabbitMQ 连接 | wujies-platform/wujies-admin/src/main/resources/application-dev.yml |
| 应用市场上架端、形态、目录、签名公钥、多租户排除表 | wujies-platform/wujies-admin/src/main/resources/application.yml |
它的数据源 url、Redis host 指向的是仓库作者的开发环境,直接启动会连到那台机器(或者连不上)。先按你的环境改这两处再启动。
3. 编译并打包后端
所有命令的工作目录都是 wujies-platform/。
& $mvn -B -q compile -DskipTests # 全反应堆编译(根 pom 默认 skipTests=true,编译不跑测试)
& $mvn -pl wujies-admin -am -B install -DskipTests # 打可运行 jar → wujies-admin/target/wujies-admin.jar只跑 mvn compile 不会更新 wujies-admin.jar,你启动的还是旧代码,于是很容易得出「我的改动没生效」的错误结论,然后去改本来是对的代码。
compiler 插件会先清空输出目录再全编。正在运行、按需懒解析类的 JVM 会抛 NoClassDefFoundError。先停服务,或只做 compile。
4. 启动后端宿主
手工启动(推荐,最少魔法)
java "-Dloader.path=plugins-dist" "-Dspring.profiles.active=dev" -jar wujies-admin\target\wujies-admin.jar三个要点:
- 工作目录必须是
wujies-platform/。app-market.deploy.plugin-dir(plugins-dist)与frontend-dir(../wujies-admin/app-market-dist/remote-apps)都是相对路径,见wujies-admin/src/main/resources/application.yml。 - profile 要显式传。
application.yml里的spring.profiles.active是构建期由 Maven 属性@profiles.active@替换的(根pom.xml里dev是默认激活的 profile),别依赖它:本地用-Dspring.profiles.active=dev(读application-dev.yml),部署用prod。 -Dloader.path只在打包后的 jar 上生效。wujies-admin/pom.xml配了layout=ZIP,只有它才认这个参数;从 IDE 直接跑主类时它会被静默忽略 —— 表现为plugins-dist里明明有 jar,安装应用却报「当前 JVM 没有加载 xxx.jar」。
PowerShell 会把 -Dloader.path=plugins-dist 拆成 /path=plugins-dist,报 ClassNotFoundException: /path=plugins-dist。
调整端口用 -Dserver.port=8087(application.yml 的默认值是 8086)。
启动脚本:先读这段再决定用不用
wujies-platform/script/prune/start-host.ps1 的意图是「先清理上一版插件 jar,再打包,再用正确的 loader.path / profile 启动」。
| 参数 | 作用 |
|---|---|
-PluginDir | 插件落位目录,默认 plugins-dist |
-Profile | Spring profile,默认 prod |
-Build | 启动前重新打包 |
-SkipPrune | 跳过清理 |
-NoPlugin | 不加载插件(排查「没有插件时宿主是否正常」) |
-Port | 覆盖端口 |
脚本第 60 行是 $root = Split-Path $PSScriptRoot -Parent,即「脚本目录的上一层」;而脚本实际在 script/prune/ 下,于是推断出的根是 wujies-platform/script,不是 wujies-platform。后果是:插件目录被算成 script/plugins-dist、jar 路径被算成 script/wujies-admin/target/wujies-admin.jar(不存在 → 触发打包 → 在 script/ 下跑 Maven 失败)。同目录的 prune-plugins.ps1 有同一个问题,而且它更隐蔽 —— 目录不存在时它打印「目录不存在,无需清理」并以 0 退出,看着像成功。
docs/开发问题与解决方案.md 第 9 节把这条列为未改(建议改成"向上找 pom.xml")。我没有运行这两个脚本,本页的结论是逐行读源码得出的,未经实测。要用就先修根推断,否则按上面手工启动。
两个加载时机现在都按 active-plugins.txt 选版本(2026-10-02 起):运行期是
PluginRuntimeManager,JVM 启动瞬间是 PluginClassPathLoader.listJars —— 不在清单里的残留 jar
会被刻意跳过(启动日志打印「跳过 N 个不在 active-plugins.txt 里的 jar」),所以
「同一 appCode 留下两个版本 → 两份同样的 mapper XML → Result Maps collection already contains value」
这个老坑只在清单不可用(active-plugins.txt 缺失或为空,此时不过滤)时才复现。
清理仍然值得做:清磁盘、回收被占用的 jar 文件句柄(Windows 上不清理会导致卸载/彻底删除删不掉)。 清理依据就是这份清单:只删同一个 appCode 名下非当前生效版本的 jar,手工放置的 jar 一律不碰。
5. 启动前端
cd plus-ui
npm install
npm run dev| 事实 | 值 / 出处 |
|---|---|
| dev server 端口 | .env.development 的 VITE_APP_PORT = 80 → 访问 http://localhost |
| 接口代理 | vite.config.ts 把 /dev-api 转到 http://127.0.0.1:8086 并去掉前缀;后端换了端口要同步改这里 |
| 运行期垫片 | npm run dev 会先跑 predev → gen:runtime,不用手工执行 |
| 守门脚本 | npm run check:globals、npm run check:remote、npm run audit:menus |
- 后端落位到
app-market.deploy.frontend-dir,当前值是../wujies-admin/app-market-dist/remote-apps(相对wujies-platform/,即工作区顶层那个wujies-admin/),见application.yml; - dev server 从
<plus-ui>/app-market-dist提供/remote-apps/*,见plus-ui/vite/plugins/appMarketStatic.ts(它用process.cwd()解析,而npm run dev的 cwd 就是plus-ui)。
两个目录目前都存在且内容不同(工作区根那份 10 个应用目录,plus-ui 那份 11 个)。装完应用菜单点开报「远程视图不可用」时,先比对这两个目录。
未实测:本页没有真的装一个应用来验证这条链路,以上是读源码与目录得出的结论。
6. 登录
打开 http://localhost/login:
| 项 | 值 |
|---|---|
| 租户 | 000000(platform_init.sql 的 sys_tenant 种子行) |
| 账号 | admin(platform_init.sql 的 sys_user 种子行,user_id = 1) |
| 密码 | 见 platform_init.sql 的 sys_user 种子行 —— 存的是 BCrypt 哈希,明文不由仓库文件给出。本站其它页面写作 admin123,本页未核对 |
接口请求要同时带 Authorization 与 clientid 两个头,缺一不可 —— clientid 会被 wujies-common-security 的 SecurityConfig 与 token 里记录的值比对。前端已处理好(plus-ui/src/utils/request.ts 从 VITE_APP_CLIENT_ID 设默认头),手工调接口时注意。
7. 装一个应用,验证整条链路
登录后的菜单结构来自 platform_init.sql 的 sys_menu 种子行:
| 菜单 | path | component |
|---|---|---|
| 应用市场 → 应用市场 | appmarket / market | appstore/market/index |
| 我的应用 → 已安装应用 | myapps / installed | appstore/installed/index |
| 我的应用 → 应用任务 | myapps / task | appstore/tasks/index |
| 应用上架 → 应用管理 / 分类管理 | appconsole / … | appstore/console/** |
步骤:
- 应用市场里挑一个应用(例如「图床管理」),点安装;
- 打开 我的应用 → 应用任务,看进度、当前步骤与日志 —— 这是排查问题的第一站,步骤与日志是边跑边落库的,失败原因(含服务端异常文本)都在那里,见 任务中心与日志;
- 安装成功后左侧菜单出现该应用的入口。
全程不需要重启后端。
一次安装实际发生什么:
1. 生成 app_install_task 记录(任务中心看的就是它)
2. 检查本机有没有产物
├─ 已有 → 就地使用(同一版本重装)
└─ 没有 / 正在升级 → 按 packageUrl 从市场拉包
├─ 校验 MD5 / SHA256
├─ 安全检查:产物白名单 + 签名校验
├─ 解包落位:jar → plugin-dir 根下;前端 → frontend-dir/<appCode>/
└─ 记录 active-plugins.txt
3. 执行包内 migrations/*.sql(按文件名排序,幂等,只做 up)
4. 加载后端插件 ← 在迁移之后
5. 同步菜单与授权(按 app.json 的 menus 幂等 upsert)
6. 任务置为成功三条值得记住的设计:
-
迁移在加载之前是刻意的:先改表结构、再切换代码。反过来的话,新代码在启动时机读一个还没加的列就会炸。
-
升级会强制重新落位(按
fromVersion != toVersion判断),「文件存在就跳过」只在同版本重装时成立。 -
plugins-dist/active-plugins.txt是运行期选版本的依据,一行一条appCode=jarFileName:gallery=wujies-gallery-plugin-1.0.5.jar mall=wujies-mall-plugin-1.0.3.jar插件 jar 与前端目录都落位在部署内全局共享的位置,所以「彻底删除」受两道闸约束(别的租户还在用就不动、平台表前缀受保护),详见 生命周期。
8. 跑测试
工作目录同为 wujies-platform/:
& $mvn -pl wujies-modules/wujies-app-market -B test "-DskipTests=false" "-Dprofiles.active=dev"根 pom.xml 的 surefire 配了 <groups>${profiles.active}</groups>。没打 @Tag("dev") / @Tag("local") / @Tag("prod") 的测试根本不会被执行 —— 症状是 Tests run 总数不变 + BUILD SUCCESS,看着全绿,其实没跑。新增后核对一下总数。
起不来?先看这几条
| 症状 | 原因 | 处理 |
|---|---|---|
Result Maps collection already contains value | plugins-dist 里同一应用有两个版本的 jar,MyBatis 解析了两份同样的 XML | 只留 active-plugins.txt 里登记的那一个;prune-plugins.ps1 因上述根推断 bug 目前不可用 |
| 插件完全没加载 | 只在 yml 里改了 app-market.plugin-runtime.enabled | 这个开关启动器读的是系统属性,它在 Spring 之前执行。用 JVM 参数切换:-Dapp-market.plugin-runtime.enabled=false |
expected single matching bean but found 2 | 宿主上下文里注册了与插件实现同类型的 bean | 宿主不能按类型注入插件 bean,改走 PluginServiceProvider.getPluginBean(appCode, type) |
ReferenceError: useStorage is not defined / Failed to resolve component: KTable | 远程应用的自动导入清单与宿主不一致 | 见 前端远程应用、本地联调;守门脚本 npm run check:globals |
ClassNotFoundException: /path=plugins-dist | PowerShell 把 -D 参数拆了 | 两个 -D 参数都加引号 |
| 市场里一个应用都看不到 | 没导 app_catalog_seed.sql | 按 §2 补导 |
| 安装/部署被安全检查拒绝 | app-market.security.mode 当前是 enforce,包没有签名 | 见 安全模式;发布时统一带 -SigningKey |
更多症状见 wujies-platform/AGENTS.md 第 5 节「已知坑」与 docs/开发问题与解决方案.md;故障排查 一节的部分命令仍是改造前的写法,读时按本页的命令替换。
相关阅读
- 环境要求 —— 版本、端口、依赖矩阵
- 核心概念 —— 宿主 / 应用 / 契约这些词在当前架构下的含义
- 必须遵守的不变量 —— 改代码前扫一遍
- 应用清单、菜单注册、迁移脚本 —— 写应用时的三份规范
- 在部署里使用应用 —— 安装、升级、停用、卸载的完整操作
- 数据模型、生命周期 —— 表与状态机
界面截图

后台登录页(截图占位)
/images/docs/quickstart-login.png建议 1920 × 1080,截登录表单与验证码。