种子数据与站点配置
本文是 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-exam | 6 / 16 | 12 |
wujies-mall | 11 / 31 | 12 |
wujies-social | 4 / 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。
三条平台约定照抄,别自己发明:
- 主键用
MAX(config_id)+N,不用自增 ——sys_config是平台表,主键号段要给平台留着; - 每条都带
WHERE NOT EXISTS(按config_key+tenant_id判重)⇒ 幂等,重装 / 升级不重复插; - 子查询要包一层派生表
(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',多租户部署要给每个租户各补一份(或让管理员在界面上改)。
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⇒ 重装 / 升级不因主键冲突失败,也不会覆盖管理员后来改过的内容;- 显式主键 ⇒ 多次执行得到同一份数据,不会越跑越多。
例如 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.txt | dict_id / tenant_id / dict_name / dict_type / remark |
dict_data.txt | dict_code / tenant_id / dict_sort / dict_label / dict_value / dict_type / css_class / list_class / is_default / remark |
config.txt | config_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.enabled | false | 没配凭据却"启用",只会让发信静默失败 |
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 | 脚本执行结果 | 任务中心「我的应用 → 应用任务」里的「执行版本迁移脚本」步骤,见任务中心 |
摘要不一致 / 标签不生效 / 配置返回空这几类现场,对照表见故障排查。
相关阅读
- 数据库迁移 —— 执行链路、四条硬规矩、账本字段
- 应用清单 app.json —— 菜单与
assets在这里声明,字典 / 配置不在这里 - 跟着 hello 读源码 ——
01..03-*.sql与/open/hello/config的真实文件 - 必须遵守的不变量 —— 契约随插件走、已执行脚本不可改
- 升级与补丁 —— 存量部署怎么拿到新种子数据
- 数据模型 —— 三类数据的边界与卸载行为
- 术语表 —— 种子数据 / 字典 / 站点配置的统一定义