Wujies 无界平台
插件开发advanced

种子数据与站点配置

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

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

这一页解决一件事:表建好了、应用也装上了,页面上的标签 / 下拉项 / 站点名称却还是空的 —— 这些数据写在哪、怎么随应用包发出去。

一句话结论:表建好 ≠ 应用能用。字典、站点配置、示例数据都要写成 migrations/ 里的幂等脚本随包走。 平台不会替你补,也不会在安装时猜你想插什么。

三类必须随包走的数据

数据存哪谁读缺了的表现
字典sys_dict_type + sys_dict_data页面标签、筛选下拉(前端 useDict,见 plus-ui/src/utils/dict.ts)、dict-tag 渲染标签空白、筛选项是空的
站点配置sys_config,config_category = 应用编码宿主泛化端点 GET /open/<code>/config接口返回空对象:站点名 / logo / 版权 / 备案号全空
示例 / 必需基础数据应用自己的表应用业务表本身列表页空白,分不清"功能坏了"还是"只是没数据"

这三样都不在 app.json 里声明(清单的 migrations 字段是文档与回滚预留,当前不参与执行)。 执行方式是目录驱动:安装 / 升级时扫描插件目录的 migrations/,按文件名排序逐个执行。 机制细节见数据库迁移。

字典脚本与实体 / Mapper 一样,都放插件工程自己的目录里 —— 平台里没有 *-api 契约模块(数量为 0), 别为了一份字典去找它,见必须遵守的不变量。

实测缺口

2026-09-14 核对发现:mall 有 11 个自有字典、social 有 4 个,exam / gallery / im / mall 各有一批 sys_config(exam.* / gallery.* / im.* / mall.*)—— 它们当时只存在于开发库, 全新安装的应用全是空标签、/open/<code>/config 返回空对象。现在这几个应用的脚本已经补齐:

应用字典类型 / 字典项站点配置行
wujies-exam6 / 1612
wujies-mall11 / 3112
wujies-social4 / 19—
wujies-gallery—13
wujies-im—12
wujies-hello—10

(数字来自各应用 migrations/ 里脚本的 INSERT 条数,可自行核对。)

文件命名

wujies/wujies-plugins/wujies-<app>/migrations/
  01-create-<app>-tables.sql         建表,纯 DDL(CREATE TABLE IF NOT EXISTS)
  0N-create-<app>-seed-data.sql      示例 / 必需基础数据(INSERT IGNORE + 显式主键)
  0N-create-<app>-dictionaries.sql   字典(MAX(dict_id)+N / MAX(dict_code)+N + WHERE NOT EXISTS)
  0N-create-<app>-website-config.sql 站点配置(MAX(config_id)+N + WHERE NOT EXISTS)
  • 编号只表示执行顺序,取下一个空号 —— 别为了整齐重排既有脚本;
  • .sql 必须是 migrations/ 的顶层文件:打包只扫顶层,子目录里的会被告警跳过;
  • 已执行过的脚本内容不能再改(账本按 (app_code, script) 比 SHA-256),要变更就新增脚本。 改过 01 的存量部署要先跑 wujies-platform/script/sql/update/app_market_app_seed_split_repair.sql, 再升级该应用 —— 见升级与补丁。

范例应用 wujies-hello 是最小样本:01 一张表、02 两条示例数据、03 十条站点配置。 三条脚本的完整效果可以在菜单「Hello 示例 → 插件开发范例」里对照,源码索引见跟着 hello 读源码。

站点配置:config_category = 应用编码

把行写进 sys_config 之后,不需要自己写"读配置"的接口。 宿主的泛化端点 cn.wujies.system.controller.OpenWebsiteConfigController (@RequestMapping("/open/{code}/config"),类上有 @SaIgnore)会按 config_category 读出来 —— 宿主不认识具体应用,新应用接进来不用改宿主一行。

范本:wujies/wujies-plugins/wujies-hello/migrations/03-create-hello-website-config.sql。 三条平台约定照抄,别自己发明:

  1. 主键用 MAX(config_id)+N,不用自增 —— sys_config 是平台表,主键号段要给平台留着;
  2. 每条都带 WHERE NOT EXISTS(按 config_key + tenant_id 判重)⇒ 幂等,重装 / 升级不重复插;
  3. 子查询要包一层派生表 (SELECT ... FROM sys_config) c —— MySQL 不允许在 INSERT 的子查询里直接引用正在插入的那张表,不包会报 You can't specify target table 'sys_config' for update in FROM clause。

返回结构由 cn.wujies.system.controller.vo.OpenWebsiteConfigVo 定死,两块:

块键说明
8 个基础字段logo / favicon / name / nameEn / backgroundImage / description / copyright / icp各占一个属性。前三个库里存的是 OSS 行 id(可逗号分隔多个),读的时候换成可访问 URL,空值返回 null
settings其余 <code>.* 全部原样透传,键去掉 <code>. 前缀。前端各页面自己决定读哪些开关,宿主不需要认识它们

两个容易踩的点:

  • logo / favicon / backgroundImage 一律置空:开发库里存的是导出机器的 OSS 行 id,换一套部署就是死引用 —— 留空由管理员在「系统配置」里自己上传;
  • 配置按租户隔离:种子脚本只写默认租户 '000000',多租户部署要给每个租户各补一份(或让管理员在界面上改)。
别给插件再包一层 /config

wujies-hello 里有个 GET /open/hello/config-via-plugin,它是反面示范 —— 用来对照"插件自己读 ConfigService 再包一层"是多余的。真实场景什么都不用做: 配置行写完,GET /open/<code>/config 就自动可用(源码注释里也是这么写的)。

字典:类型与字典项一起发

范本:wujies/wujies-plugins/wujies-exam/migrations/02-create-exam-dictionaries.sql(生成器的参考风格就是它)、 wujies/wujies-plugins/wujies-mall/migrations/02-create-mall-dictionaries.sql。

表判重键主键
sys_dict_type(dict_type, tenant_id)MAX(dict_id)+N
sys_dict_data(dict_type, dict_value, tenant_id)MAX(dict_code)+N
  • 脚本开头先取两个基线(SET @base_dict_id := ... / SET @base_dict_code := ...),序号顺着往下排,避免与平台已有字典撞号;
  • 字典类型名用 <code>_ 前缀(exam_question_type / mall_order_status / social_*)—— 彻底卸载时平台就是按这个前缀认领的(见下文);
  • 不要 DELETE。exam 的脚本注释里记着这件事:原 wujies-exam 的 exam-dict.sql 开头是 DELETE FROM sys_dict_type / sys_dict_data,那对一个插件迁移脚本来说等于"装一次应用就删平台表的数据",已改成"缺就补";
  • 字典项与字典类型必须成对出现:生成器发现"有 sys_dict_data 却没有对应的 sys_dict_type"会直接报错。

示例 / 必需基础数据:INSERT IGNORE + 显式主键

范本:wujies-hello/migrations/02-create-hello-seed-data.sql(两条数据,有效代码 3 行)。

INSERT IGNORE INTO hello_item (id, name, remark, create_time)
VALUES (1, '第一条示例数据', '由 migrations/02 写入,用来验证建表与查询链路', CURRENT_TIMESTAMP);
  • INSERT IGNORE ⇒ 重装 / 升级不因主键冲突失败,也不会覆盖管理员后来改过的内容;
  • 显式主键 ⇒ 多次执行得到同一份数据,不会越跑越多。
INSERT IGNORE 也会吞掉别的插入错误

例如 NOT NULL、外键错误。所以脚本里的行必须与表结构保持一致 —— 改表结构时记得同步这个文件。

建表与种子数据刻意拆开:写在一起的话,"建表脚本"同时也是数据脚本, 没法只发结构不发示例数据(例如给客户交付时不想带示例商品)。拆分工具见下。

生成,而不是手写

从开发库人肉抄这些数据必然出错(漏行、抄错 id、把凭据发出去)。两个生成器都在平台仓库:

工具(wujies-platform/deploy/tools/)作用
gen-app-seed-sql.mjs从真实库导出 → 逐应用产出 -dictionaries.sql 与 -website-config.sql;从不覆盖已有脚本(账本按文件名钉内容)
split-app-migration-data.mjs把 01-create-<app>-tables.sql 里夹带的 INSERT 拆成 0N-create-<app>-seed-data.sql(DDL 逐行删除,其余格式逐字节不动)

gen-app-seed-sql.mjs 的输入是 DbTool 的查询结果文本,一个查询一个文件:

文件列
dict_type.txtdict_id / tenant_id / dict_name / dict_type / remark
dict_data.txtdict_code / tenant_id / dict_sort / dict_label / dict_value / dict_type / css_class / list_class / is_default / remark
config.txtconfig_id / tenant_id / config_name / config_key / config_value / config_type / config_category / config_description / default_value / value_type / sort_order / remark
# 在 wujies-platform/ 下运行。不带 --write 是试运行,只打印将要写哪些文件
node deploy/tools/gen-app-seed-sql.mjs --dir $env:TEMP\seed-export
node deploy/tools/gen-app-seed-sql.mjs --dir $env:TEMP\seed-export --write
用之前先确认这两个工具的"插件根"还是对的

两个脚本都把 <平台仓库>/plugins 当成插件根(gen-app-seed-sql.mjs 的 pluginsRoot、 split-app-migration-data.mjs 里同名字段),而平台仓库里已经没有 plugins/ 了 —— 插件本体在 wujies/wujies-plugins/。

在本仓库当前状态下实测:node deploy/tools/split-app-migration-data.mjs 直接 ENOENT ... wujies-platform\plugins;gen-app-seed-sql.mjs 更隐蔽 —— 它在 existsSync(pluginDir) 为假时 continue,于是安静地一个文件都不产出。

所以先跑一次试运行,确认输出里有 would <app>: ... 条 -> ... 这类行; 只看到 files : 0 就说明插件根不对。改脚本里的 pluginsRoot(或先确认它指的是哪一份检出)再跑。

平台自己的那三类参数(website / login 验证码 / email)由同一个生成器产出, 但它写的是 wujies-platform/script/sql/update/platform_config_site.sql,不进应用包 —— 全新部署的平台参数来自 script/sql/platform_init.sql(另一个生成器 gen-platform-init.mjs), 初始化链路见环境准备。

分发物的取值必须与开发库不同

生成器对 config_value 与 default_value 分别套同一套"清凭据 / 清 OSS 死引用"的策略 (shippedValue() / shippedDefault())—— 只改前者会把死引用留在默认值里。

配置分发物里的取值为什么
*.logo / *.favicon / *.backgroundImage置空库里存的是导出机器的 OSS 行 id,换部署就是死引用
mail.pass / mail.user / mail.from清空不能把开发库的邮箱凭据发出去
mail.enabledfalse没配凭据却"启用",只会让发信静默失败
captcha.enabled(config_category = login)保持 true开发库为调试关掉了,分发物不能跟着关
其它带 pass / secret / token / priv 字样的键清空生成器按关键字匹配

表里 captcha.enabled 那一行是生成器对平台级 login 分类的策略;全新部署的实际值以 script/sql/platform_init.sql 为准 —— 那份由 gen-platform-init.mjs 产出,是另一条线。

字典去重要"保留新的"

开发库里同一个字典类型可能有两代数据:值重叠、含义冲突。 实测:mall_order_status 的 501 在一代里是「已取消」、另一代里是「已退款」, 而前端 getOrderStatusLabel() 与状态机用的是新的那套。

生成器按「同 (dict_type, dict_value, tenant_id) 取 dict_code 最大者」去重, 并把每一条冲突打印出来(kept #x, dropped #y)—— 看到这行说明源库该清理了。 存量库的清理脚本:wujies-platform/script/sql/update/cleanup_duplicate_mall_order_status.sql(幂等)。

两个容易漏的收尾动作

迁移脚本插了字典 / 参数后必须失效缓存

平台把字典与参数缓存在 Redis(CacheNames.SYS_DICT / SYS_DICT_TYPE / SYS_CONFIG)。 不失效就会继续返回迁移前的标签 —— 现象是"应用装好了、标签却还是空的或旧的"。

AppMigrationService.applyPending 已经自动做了:只要本轮真的执行了脚本 (全部被跳过不算),就 CacheUtils.clear(...) 清这三个缓存。

⚠️ 如果你是手工执行的 SQL(跳过了安装 / 升级链路),要自己失效一次: DELETE /system/dict/type/refreshCache,或用「系统配置 / 字典管理」界面上的「刷新缓存」。

彻底卸载也要清这两类数据

不清理会留下孤儿;更麻烦的是重装时脚本用 WHERE NOT EXISTS / INSERT IGNORE, 会沿用上一次安装留下的旧值 —— 表现是"装成功了、标签却是旧标签",极难查。

AppPurgeService 的判据(与生成器同源),只在「彻底删除」(purge=true)时执行:

数据删除条件
字典dict_type LIKE '<code>_%'(类型与字典项都删)
站点配置config_category = <code> 或 config_key LIKE '<code>.%'

删完会失效缓存,任务日志里写明删了多少个类型 / 多少条字典项 / 多少条配置。 这一步和"删表"一样是全局动作,同样受「别的租户还在用就不动」那道闸约束 —— 被闸拦下时一行都不删,只在任务日志里说明原因。见数据模型。

自检清单

#检查怎么看
1脚本在 migrations/ 顶层、有数字前缀、幂等目录列表 + 通读 SQL
2字典类型名带 <code>_ 前缀,且每条 sys_dict_data 都有对应的 sys_dict_type全文搜 sys_dict_type 的取值
3站点配置的 config_category 正好等于应用编码与 app.json 的 code 对一遍
4*.logo / *.favicon / mail.* 没把开发库的值发出去看这几类键的值是否为空 / false
5包内真的打进去了解包看 migrations/(漏打包只表现为"升级成功但标签还是旧的")
6装完标签与配置真的对GET /open/<code>/config;GET /system/dict/data/type/<dict_type>
7脚本执行结果任务中心「我的应用 → 应用任务」里的「执行版本迁移脚本」步骤,见任务中心

摘要不一致 / 标签不生效 / 配置返回空这几类现场,对照表见故障排查。

相关阅读

目录