fix(sql): 补充 iam_user_expand_info 超管种子并修复脱敏脚本行级保留

登录后 getLoginAfterUserInfo 按 id 查 iam_user_expand_info, 脱敏脚本清空该表后 id=1 缺行, 抛 UserInfoNotExistsException 导致 bootx 账号 token 签发成功却无法进入系统.

- data.sql: 补 id=1 种子 INSERT (17 列对齐 table.sql, register_time 对齐 iam_user_info.create_time)
- redact-data.mjs: BOOTX_ADMIN_TABLES 改为集合, 将 iam_user_expand_info 纳入行级保留 (id=1 留/其余清), 防止重新脱敏时再次丢失
- redact-data-report.md: 行级保留与清除清单、伪代码同步更新
This commit is contained in:
daxpay
2026-08-06 11:20:26 +08:00
parent 600b567759
commit 36fd5eb824
3 changed files with 26 additions and 15 deletions

View File

@@ -86,8 +86,10 @@
| 表 | 保留行 | 标识 |
|----|--------|------|
| `iam_user_info` | 1 行 | `id=1`username=`bootx`name=`超级管理员`client_code=`admin`)—— 系统内置超级管理员 |
| `iam_user_expand_info` | 1 行 | `id=1`——内置超管的扩展信息。**必须与 `iam_user_info` 同 id 保留**:登录后 `getLoginAfterUserInfo` 按 id 查此表,缺行则抛 `UserInfoNotExistsException`token 签发成功也无法进入系统 |
> 该账号在 `iam_user_role` 中无角色绑定记录,推断为代码层面识别的内置超管(拥有全部权限),故 `iam_user_role` 可整表清除。
> `iam_user_password_security` 虽也属超管关联表,但 `getPasswordStatus` 用 `.orElse(null)` 优雅降级(返回 `initialPassword=true`),缺行不卡登录,故**不**纳入行级保留,保持整表清除。
### 5.2 清除清单(约 63 张表,整表跳过)
@@ -101,7 +103,7 @@
`mch_credential``pay_easy_pay_credential`
#### D. 用户认证与隐私
`iam_user_info`(仅留 id=1`iam_user_password_history``iam_user_two_factor``iam_user_social``iam_user_role``iam_user_expand_info``iam_user_password_security``iam_user_dashboard_preference``mch_user`
`iam_user_info`(仅留 id=1`iam_user_password_history``iam_user_two_factor``iam_user_social``iam_user_role``iam_user_password_security``iam_user_dashboard_preference``mch_user`(注:`iam_user_expand_info` 已改为行级保留 id=1见 §5.1
#### E. 平台配置
`system_platform_config`(含明文 OSS 密钥,整表清)、`system_platform_encrypt_config`(加密平台认证配置)。
@@ -143,12 +145,13 @@ const KEEP_TABLES = new Set([
'system_sensitive_word',
])
// iam_user_info 行级保留id=1 (bootx 超管)
const BOOTX_ADMIN_RE = /^INSERT INTO public\.iam_user_info\s+VALUES\s*\(\s*1\s*,/
// 内置超管关联表行级保留id=1 (bootx) —— iam_user_info + iam_user_expand_info
const BOOTX_ADMIN_TABLES = new Set(['iam_user_info', 'iam_user_expand_info'])
const BOOTX_ADMIN_RE = /^INSERT INTO public\.(?:iam_user_info|iam_user_expand_info)\s+VALUES\s*\(\s*1\s*,/
function handleInsert(table, lines) {
if (KEEP_TABLES.has(table)) return output(lines) // ① 种子表保留
if (BOOTX_ADMIN_RE.test(lines[0])) return output(lines) // ② bootx 行保留
if (KEEP_TABLES.has(table)) return output(lines) // ① 种子表保留
if (BOOTX_ADMIN_TABLES.has(table) && BOOTX_ADMIN_RE.test(lines[0])) return output(lines) // ② bootx 行保留
// ③ 其余清除
writeRedactedMark(table)
}

View File

@@ -6,8 +6,10 @@
* 策略:保留 18 张系统种子表 + bootx 内置超管(id=1),其余整表清除。
*
* 三原则决策:
* ① 表 ∈ KEEP_TABLES系统种子 → 整表原样保留
* ② iam_user_info 且 id=1 (bootx 超管) → 该行保留,同表其余行清除
* ① 表 ∈ KEEP_TABLES系统种子 → 整表原样保留
* ② 内置超管关联表且 id=1 (bootx) → 该行保留,同表其余行清除
* iam_user_info 账号主表 + iam_user_expand_info 扩展信息,
* 后者登录后拉用户信息必需,缺则抛 UserInfoNotExistsException
* ③ 其余 → 整表跳过(写审计注释)
*
* 格式假设pg_dump --insertsINSERT INTO public.表名 VALUES (...),无列名)。
@@ -55,9 +57,11 @@ const KEEP_TABLES = new Set([
'system_sensitive_word',
])
// ---------- 行级保留:iam_user_info 的 bootx 超管id=1 ----------
const BOOTX_ADMIN_TABLE = 'iam_user_info'
const BOOTX_ADMIN_RE = /^INSERT INTO (?:public\.)?iam_user_info\s+VALUES\s*\(\s*1\s*,/
// ---------- 行级保留:内置超管 bootx(id=1) 的关联表 ----------
// iam_user_info: 账号主表
// iam_user_expand_info: 用户扩展信息(登录后拉用户信息必需,缺则 getLoginAfterUserInfo 抛 UserInfoNotExistsException
const BOOTX_ADMIN_TABLES = new Set(['iam_user_info', 'iam_user_expand_info'])
const BOOTX_ADMIN_RE = /^INSERT INTO (?:public\.)?(?:iam_user_info|iam_user_expand_info)\s+VALUES\s*\(\s*1\s*,/
// ---------- 解析规则 ----------
const INSERT_RE = /^INSERT INTO (?:public\.)?(\w+)\s+VALUES\b/i
@@ -102,8 +106,8 @@ function handleInsert(table, lines) {
for (const ln of lines) output.write(ln + '\n')
return
}
// ② iam_user_info 行级保留bootx 超管 (id=1)
if (table === BOOTX_ADMIN_TABLE && BOOTX_ADMIN_RE.test(lines[0])) {
// ② 内置超管关联表行级保留bootx (id=1)
if (BOOTX_ADMIN_TABLES.has(table) && BOOTX_ADMIN_RE.test(lines[0])) {
bump(table, true)
for (const ln of lines) output.write(ln + '\n')
return
@@ -149,11 +153,11 @@ rl.on('close', () => {
function printReport() {
const keptSeedTables = []
const partialTables = [] // 行级保留表(iam_user_info
const partialTables = [] // 行级保留表(内置超管关联表
const clearedTables = []
for (const [t, s] of stats) {
if (KEEP_TABLES.has(t)) keptSeedTables.push([t, s])
else if (t === BOOTX_ADMIN_TABLE) partialTables.push([t, s])
else if (BOOTX_ADMIN_TABLES.has(t)) partialTables.push([t, s])
else clearedTables.push([t, s])
}
@@ -165,7 +169,8 @@ function printReport() {
lines.push('')
lines.push('========== 脱敏统计(白名单模式) ==========')
lines.push(`[保留·种子表] ${keptSeedTables.length} 张表INSERT ${sumKept} 条(全保留)`)
lines.push(`[保留·行级] iam_user_infoINSERT ${partialTables[0]?.[1].total ?? 0} → 保留 ${partialTables[0]?.[1].kept ?? 0} (bootx id=1),清除 ${sumPartialCleared}`)
const partialDetail = partialTables.map(([t, s]) => `${t}(${s.total}${s.kept})`).join(', ') || '无'
lines.push(`[保留·行级] 内置超管(id=1)关联表:${partialDetail},清除 ${sumPartialCleared}`)
lines.push(`[清除] ${clearedTables.length} 张表,丢弃 INSERT ${sumCleared}`)
lines.push('---- 清除明细 ----')
for (const [t, s] of clearedTables.sort((a, b) => a[0].localeCompare(b[0]))) {

View File

@@ -3705,6 +3705,8 @@ INSERT INTO iam_perm_code VALUES (2072377871723388928, 'trade:order:view', 'trad
INSERT INTO iam_perm_code VALUES (2072990657130180608, 'trade:refund:manage', 'trade:refund', true, '', 1, 1, 1, false, '2026-07-03 10:27:41.620782+00', '2026-07-15 03:59:23.715999+00', 'perm.trade:refund:manage');
INSERT INTO iam_perm_code VALUES (2072990657130180609, 'trade:refund:view', 'trade:refund', true, '', 1, 1, 1, false, '2026-07-03 10:27:41.620782+00', '2026-07-15 03:59:23.717505+00', 'perm.trade:refund:view');
INSERT INTO iam_perm_code VALUES (2079866295577419776, 'trade:callback-record:view', 'trade:callback-record', true, '', 1, 1, 0, false, '2026-07-22 09:49:01.56951+00', '2026-07-22 09:49:01.575017+00', 'perm.trade:callback-record:view');
INSERT INTO iam_perm_code VALUES (2083000000000000001, 'trade:transfer:view', 'trade:transfer', true, '', 1, 1, 0, false, '2026-08-05 16:00:00+00', '2026-08-05 16:00:00+00', 'perm.trade:transfer:view');
INSERT INTO iam_perm_code VALUES (2083000000000000002, 'trade:transfer:manage', 'trade:transfer', true, '', 1, 1, 0, false, '2026-08-05 16:00:00+00', '2026-08-05 16:00:00+00', 'perm.trade:transfer:manage');
INSERT INTO iam_perm_code VALUES (2079866295615168512, 'trade:mch-notice:manage', 'trade:mch-notice', true, '', 1, 1, 0, false, '2026-07-22 09:49:01.578524+00', '2026-07-22 09:49:01.578524+00', 'perm.trade:mch-notice:manage');
INSERT INTO iam_perm_code VALUES (2079866295619362816, 'trade:mch-notice:view', 'trade:mch-notice', true, '', 1, 1, 0, false, '2026-07-22 09:49:01.579524+00', '2026-07-22 09:49:01.579524+00', 'perm.trade:mch-notice:view');
INSERT INTO iam_perm_code VALUES (2082811057154473984, 'merchant:gateway-pay-config:manage', 'merchant:gateway-pay-config', true, NULL, 1, 1, 0, false, '2026-07-30 12:50:27.440009+00', '2026-07-30 12:50:27.44601+00', 'perm.merchant:gateway-pay-config:manage');
@@ -4165,6 +4167,7 @@ INSERT INTO iam_role_menu VALUES (2083360959857102849, 2, NULL, 91112);
--
-- REDACTED: iam_user_expand_info (整表清除)
INSERT INTO iam_user_expand_info VALUES (1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, false, NULL, NULL, NULL, '2026-03-28 14:26:08+00', NULL);
--