BitzOrcas.SeedData.Exporter 是一次性/受控迁移工具:它从 legacy SQL Server staging 读取 14 类平台基础数据,修正旧表名和关系键,并把 CSV 分发给 Framework、MasterData、Authorization 与 Menu owner。它不是生产备份、通用 ETL,也不会替你判断数据是否可以进入源码仓库。
数据流与责任边界
导出器只负责技术转换。数据 owner 负责批准源环境、目标租户、数据最小化、CSV 内容和最终提交。
当前限制先读
当前实现没有 --dry-run、预览 manifest、临时 staging、原子目录替换或 --no-overwrite。它在启动后创建输出目录,并通过 File.Create(path) 直接截断/覆盖同名 CSV;中途失败可能留下“前几张已更新、后几张仍旧”的混合目录。
配置加载与安全来源
配置按 Configuration provider 后写覆盖前写的顺序加载:appsettings.json、appsettings.local.json、SEED_ 前缀环境变量、命令行。属性位于 Exporter section,环境变量因此使用 SEED_Exporter__SourceConnectionString 形态。
# 连接串只进入当前 shell;不要写入命令历史、CI 日志或已跟踪 JSON。export SEED_Exporter__SourceConnectionString="$LEGACY_STAGING_CONNECTION"export SEED_Exporter__TargetTenantId='1000001'export SEED_Exporter__PlatformTenantId='0'
# 四个 owner 输出先全部重定向到独立临时目录。export SEED_Exporter__OutputDirectory="$TMPDIR/bitz-seed/framework"export SEED_Exporter__MasterDataOutputDirectory="$TMPDIR/bitz-seed/master-data"export SEED_Exporter__AuthorizationOutputDirectory="$TMPDIR/bitz-seed/authorization"export SEED_Exporter__MenuOutputDirectory="$TMPDIR/bitz-seed/menu"程序会把日志中的 Password/Pwd 值替换为 ***,但这不保证异常堆栈、数据库驱动日志或 shell 历史都已脱敏。运行日志仍按敏感制品处理。
运行前检查
- 源库必须是批准的、已脱敏且版本明确的 staging snapshot,不直接连生产主库。
- 使用最小只读 SQL 登录,仅允许读取列出的 legacy 表。
- 明确
TargetTenantId;默认1000001只是配置样例,不是安全默认业务租户。 PlatformTenantId必须匹配旧系统的平台数据约定,通常是0。- 四个临时输出目录必须为空或其旧内容已有校验和备份。
- 记录源码 Commit、源快照标识、执行人、审批单与运行时间。
# 从仓库根目录构建 Release;先让编译和架构规则暴露工具漂移。dotnet build src/Tooling/BitzOrcas.SeedData.Exporter \ --configuration Release
# 确认输出变量没有误指向受版本控制的正式 Assets。env | rg '^SEED_Exporter__(Output|MasterDataOutput|AuthorizationOutput|MenuOutput)Directory='执行导出
工具以当前工作目录解析相对配置路径。README 的默认值假设进入项目目录后运行;从仓库根执行时应使用绝对输出目录,避免相对路径落到意外位置。
# 在项目目录运行,保持 appsettings.local.json 与默认相对路径语义一致。cd src/Tooling/BitzOrcas.SeedData.Exporterdotnet run --configuration Release启动时先执行 ValidateOptions,再创建四个目录、打开 SqlConnection,按固定顺序查询和写文件。日志显示连接串掩码、租户和每个 owner 的绝对目录;发现目录不对时立即取消并丢弃临时输出。
14 类导出与归一化
| Legacy 来源 | 输出文件 | Owner | 关键转换 |
|---|---|---|---|
SysTenant | 100-sys_platform_tenant.csv | Framework 兼容 | Status bool→enum、Connection→ConnectionString |
SysLanguage | 110-sys_language.csv | MasterData | 平台租户、未删除 |
SysCountry | 120-sys_country.csv | MasterData | 平台租户、未删除 |
SysIndustrySetting | 130-sys_industry_setting.csv | MasterData | 平台租户、未删除 |
SysGeneralCodeGroups | 140-sys_general_code_group.csv | MasterData | 旧复数表名归一化 |
SysGeneralCodes | 141-sys_general_code.csv | MasterData | 表名与树字段兼容 |
SysGeneralCodeTexts | 142-sys_general_code_text.csv | MasterData | 表名归一化 |
SysExchangeRates | 150-sys_exchange_rate.csv | MasterData | 表名归一化 |
SysPubicHoliday | 160-sys_public_holiday.csv | MasterData | 修正 legacy 拼写缺失的 l |
SysRoleTypes | 200-sys_role_type.csv | Authorization | 表名归一化 |
SysRoles | 210-sys_role.csv | Authorization | 平台模板 + 目标租户覆盖,按角色名归一化 |
SysModules | 220-sys_module.csv | Menu | Id/模块码建立稳定字典 |
SysPermission | 230-sys_permission.csv | Authorization | Mid 归一化为模块码 |
SysRoleModulePermission | 240-sys_role_module_permission.csv | Authorization | Id 关系改为角色名/模块码/权限码 |
源码 README 是这 14 类映射的权威维护说明。新增或移除类别时,应同步 exporter、owner seed step、资产清单、验证测试和本页源码事实门禁。
Fail-closed 关系处理
模块、权限、角色关系不能“尽量导出”。实现通过 AddStableKey 同时登记 legacy Id 与业务码:空 key 直接失败,同一个 key 指向两个不同值视为歧义并失败。权限缺少 code、引用未知 module,或用户独占关系无法归属角色时也必须停止。
目标租户角色会覆盖同名平台角色模板,最终投影到目标 TenantId。这是一项迁移规则,不是多租户运行时的通用合并语义;评审时必须检查覆盖数量和角色权限差异。
CSV 实际格式
输出不是逗号分隔,而是与当前 CsvSeedReader 一致的 ^ 分隔。CsvHelper 写 header,DateTime 格式限定为带毫秒、不带时区的 ISO 风格;写入使用当前 .NET Encoding.UTF8。文件名顺序与 seed Order 约定配合,但 Runner 最终以注册步骤的 Order 和 DependsOn 调度。
# 检查文件数量、分隔符和首行;内容可能敏感,不把完整行贴入公开日志。find "$TMPDIR/bitz-seed" -type f -name '*.csv' -print | sorthead -n 1 "$TMPDIR/bitz-seed/authorization/230-sys_permission.csv"
# 拒绝意外逗号表头和空文件。find "$TMPDIR/bitz-seed" -type f -name '*.csv' -size 0 -printrg -n '^[^^]+,[^^]+' "$TMPDIR/bitz-seed" --glob '*.csv'SysTenant 查不到目标租户时当前只打印 WARNING 并继续,不会令进程失败。这是一个必须由运行后清单补偿的现状:确认 100-sys_platform_tenant.csv 是否存在且来自本次执行。
与正式 owner Assets 比较
不要整目录复制。逐 owner 比较相对文件名、header、行数、稳定业务键和敏感字段,再选择性更新。
# 先看清单和统计,再看逐行 diff。find "$TMPDIR/bitz-seed" -type f -name '*.csv' -exec wc -l {} + | sort
# 示例:只比较权限资产;路径按当前仓库 owner 目录核对。git diff --no-index \ src/Platform/Authorization/BitzOrcas.Platform.Authorization.Infrastructure/Seeders/Assets/230-sys_permission.csv \ "$TMPDIR/bitz-seed/authorization/230-sys_permission.csv" || true重点检查新增/删除权限、角色覆盖、模块码变化、租户 ID、审计人、连接字符串、邮箱/电话、固定 token 和演示凭据。任何无法解释的批量变化都应回到 source snapshot 和查询规则定位。
Seed 验证与幂等
# 对最终候选 owner Assets 做值长度/格式验证,不需要数据库连接串。dotnet run --project src/Tooling/BitzOrcas.SchemaMaintenance -- \ --validate-seed
# 验证 Seed 相关架构规则和真实集成合同。dotnet test tests/BitzOrcas.Architecture.Tests \ --configuration Release \ --filter 'FullyQualifiedName~Seed'dotnet test tests/BitzOrcas.Integration.Tests \ --configuration Release \ --filter 'Category=Docker&FullyQualifiedName~Seed'在空库验证首次插入,在已有库验证 replay:行数不膨胀、owner 管理字段收敛、运行时字段不被覆盖、依赖顺序正确。只验证“第二次不抛异常”不够。
失败恢复
当前写入不是原子事务。任意异常后都应把整个临时根目录标记为失败并删除,修复源数据或配置后从空目录重跑;不要只重跑缺失的后半部分,再把不同 run 的文件拼接为一个候选集。
数据库读取取消或网络中断同样按失败处理。保留掩码日志、首个异常、已写文件清单和源 snapshot ID用于定位,但不要保留含真实 secret/PII 的共享附件。
交付清单
- 源 staging、快照、只读账号和数据审批可追溯;
- 四个输出目录均为临时目录,没有直接覆盖正式 Assets;
- 14 个预期文件的存在性、header、行数与 owner 已核对;
- 未知 module、空 permission、歧义 role/key 均 fail-closed;
- 目标租户 seed 未因 WARNING 悄然缺失;
- PII、Secret、连接串、演示凭据扫描通过;
- Schema validation、空库和升级库幂等合同通过;
- 只选择性提交可解释 diff,并记录生成来源与证据。