test: add real migration business sampling

This commit is contained in:
Codex
2026-06-30 13:03:38 +08:00
parent d005f65fe7
commit 9fdb6cd28b
6 changed files with 1245 additions and 5 deletions

View File

@@ -441,6 +441,25 @@ dry-run 会检查导出目录、JSON 形态、核心集合缺失、重复/缺失
当前真实 SQLite 基线已经跑通只读导出和干净库全量导入58 个业务 collection、248555 条记录、9 个 storage 原始资源文件;本地 `npx supabase db reset` 后执行 `npm run pb:import:json` 最近用时约 10 分 11 秒,`npm run pb:import:validate` 结果为 0 failures、3 warnings。`user_answer_records``mock_exam_configs``referral_qrcodes``commission_settings` 已进入标准化导入,导入后核心计数包括 3670 用户、74102 题目、85199 条旧答题记录、38205 条错题、636 订单、447 权益、79 个推广码和 1 条租户分佣设置。导入器现在还会从旧 `region_modules/module_nodes/subjects/categories/questions.nodeId` 生成新架构 `content_entries/content_nodes/question_collections/practice_blueprints`,最新导入计数为 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系、3102 个顺序/随机练习蓝图;所有已发布旧题都会写入 `entry_id/content_node_id/primary_collection_id`,供 Taro 前端按新模型直接消费。 当前真实 SQLite 基线已经跑通只读导出和干净库全量导入58 个业务 collection、248555 条记录、9 个 storage 原始资源文件;本地 `npx supabase db reset` 后执行 `npm run pb:import:json` 最近用时约 10 分 11 秒,`npm run pb:import:validate` 结果为 0 failures、3 warnings。`user_answer_records``mock_exam_configs``referral_qrcodes``commission_settings` 已进入标准化导入,导入后核心计数包括 3670 用户、74102 题目、85199 条旧答题记录、38205 条错题、636 订单、447 权益、79 个推广码和 1 条租户分佣设置。导入器现在还会从旧 `region_modules/module_nodes/subjects/categories/questions.nodeId` 生成新架构 `content_entries/content_nodes/question_collections/practice_blueprints`,最新导入计数为 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系、3102 个顺序/随机练习蓝图;所有已发布旧题都会写入 `entry_id/content_node_id/primary_collection_id`,供 Taro 前端按新模型直接消费。
导入结构校验后,还要运行真实业务抽样:
```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
npm run pb:import:sample
```
`pb:import:sample` 是只读脚本,用来证明迁移数据能被新 SaaS 业务模型消费:题库入口、分类节点、题目合集、顺序/随机/全真模拟蓝图、题目当前版本、答题记录、错题、收藏、单词、知识手册、分数线、订单、支付、权益、激活码、资源台账和敏感字段泄露都会被抽样检查。当前真实迁移库最近结果为 `0 failures, 6 warnings, 1 skipped, 39 passed`warning 均为旧数据人工复核项或上线前留档项,详见 runbook。
如需生成本地报告:
```powershell
$env:PB_SAMPLE_WRITE_REPORT="true"
npm run pb:import:sample
Remove-Item Env:\PB_SAMPLE_WRITE_REPORT
```
报告输出到已忽略的 `docs/refactor/migration-reports/`,不要提交真实用户、订单或学习数据样本。
production dry-run 目前仍有 2 个真实数据 blocker30 个订单缺用户、22 个知识手册章节缺所属手册;导入器会把它们隔离到财务复核/迁移待复核手册并写入 `pb_import_issues`,其中最新导入 run 的 critical issue 剩余 29 个7 个已支付订单缺用户、22 个手册章节缺所属手册。正式切换前仍必须人工确认,详见: production dry-run 目前仍有 2 个真实数据 blocker30 个订单缺用户、22 个知识手册章节缺所属手册;导入器会把它们隔离到财务复核/迁移待复核手册并写入 `pb_import_issues`,其中最新导入 run 的 critical issue 剩余 29 个7 个已支付订单缺用户、22 个手册章节缺所属手册。正式切换前仍必须人工确认,详见:
```text ```text
@@ -559,7 +578,7 @@ npm run audit:runtime
git diff --check git diff --check
``` ```
结果:通过。最近一轮真实迁移专项验证已通过 `npx supabase db reset``npm run pb:import:json``npm run pb:import:validate``npm run check:importer`production dry-run 仍按预期返回非 0因为旧数据本身还剩订单缺用户和手册章节缺归属两个 blocker。`npm run test:auth:remote-smoke` 覆盖远程 Auth/JWKS 验收脚本自身。`npm run test:rls` 覆盖 75 条运行时 RLS 断言,包含主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝。`npm run test:api` 覆盖平台细粒度权限、公共题库跨租户同步运营状态、资源访问事件、锁定 CDN 资源拒绝、provider-managed CDN 显式放行、学生短 TTL 下载/预览、访问记录查询、安全扫描门禁、官方账单下载任务权限和脱敏响应、异常订单运营台、人工调整凭证提交/复核/事件/报表、平台账单逾期 dry-run/催缴记录、平台审计告警查询/状态更新/越权拒绝/敏感 details 脱敏、平台审计告警通知渠道/事件查询和密钥不回显、平台催缴通知渠道/事件查询和密钥不回显、租户隔离,以及凭证审批不修改订单/支付/权益。`npm run test:worker:commerce` 覆盖支付/退款补偿、微信/支付宝官方账单下载、账单 hash 校验、导入 `provider_download` 对账批次和密钥不泄露。`npm run test:worker:platform-billing` 覆盖平台 SaaS 订阅自动计费、重复开票保护、账单明细和审计。`npm run test:worker:platform-dunning` 覆盖平台 SaaS 逾期账单标记、内部催缴记录、租户 `past_due` 状态和每日催缴幂等。`npm run test:worker:platform-dunning-notifications` 覆盖平台 SaaS 催缴外部通知入队、generic webhook 发送、幂等、防重复、联系方式掩码、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:platform-audit-alerts` 覆盖平台审计告警生成、规则匹配、幂等、防重复和告警 details 脱敏。`npm run test:worker:platform-audit-notifications` 覆盖平台审计告警外部通知入队、generic webhook 发送、幂等、防重复、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:assets` 覆盖托管资源复检、内置安全扫描、外部 HTTP scanner 通过/失败/不可用 fail-closed、扫描失败/跳过事件和异常资源自动下架。`npm run test:worker:public-banks` 覆盖公共题库自动同步、失败通知和恢复自动关闭。`npm run test:worker:exports` 覆盖导出 worker 生成可信资源并标记 `securityScanStatus=passed``npm run audit:runtime` 当前为 0 vulnerabilitiesExcel 解析已从 `exceljs` 切换为 `read-excel-file`,避免生产运行时携带 `exceljs -> uuid` 的已知中危依赖。 结果:通过。最近一轮真实迁移专项验证已通过 `npx supabase db reset``npm run pb:import:json``npm run pb:import:validate``npm run pb:import:sample``npm run check:importer``pb:import:sample` 当前为 0 failures、6 warnings、1 skipped、39 passedproduction dry-run 仍按预期返回非 0因为旧数据本身还剩订单缺用户和手册章节缺归属两个 blocker。`npm run test:auth:remote-smoke` 覆盖远程 Auth/JWKS 验收脚本自身。`npm run test:rls` 覆盖 75 条运行时 RLS 断言,包含主租户、合作商租户、无租户 claim、平台管理员旁路和跨租户写入拒绝。`npm run test:api` 覆盖平台细粒度权限、公共题库跨租户同步运营状态、资源访问事件、锁定 CDN 资源拒绝、provider-managed CDN 显式放行、学生短 TTL 下载/预览、访问记录查询、安全扫描门禁、官方账单下载任务权限和脱敏响应、异常订单运营台、人工调整凭证提交/复核/事件/报表、平台账单逾期 dry-run/催缴记录、平台审计告警查询/状态更新/越权拒绝/敏感 details 脱敏、平台审计告警通知渠道/事件查询和密钥不回显、平台催缴通知渠道/事件查询和密钥不回显、租户隔离,以及凭证审批不修改订单/支付/权益。`npm run test:worker:commerce` 覆盖支付/退款补偿、微信/支付宝官方账单下载、账单 hash 校验、导入 `provider_download` 对账批次和密钥不泄露。`npm run test:worker:platform-billing` 覆盖平台 SaaS 订阅自动计费、重复开票保护、账单明细和审计。`npm run test:worker:platform-dunning` 覆盖平台 SaaS 逾期账单标记、内部催缴记录、租户 `past_due` 状态和每日催缴幂等。`npm run test:worker:platform-dunning-notifications` 覆盖平台 SaaS 催缴外部通知入队、generic webhook 发送、幂等、防重复、联系方式掩码、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:platform-audit-alerts` 覆盖平台审计告警生成、规则匹配、幂等、防重复和告警 details 脱敏。`npm run test:worker:platform-audit-notifications` 覆盖平台审计告警外部通知入队、generic webhook 发送、幂等、防重复、签名密钥不泄露和请求 payload 脱敏。`npm run test:worker:assets` 覆盖托管资源复检、内置安全扫描、外部 HTTP scanner 通过/失败/不可用 fail-closed、扫描失败/跳过事件和异常资源自动下架。`npm run test:worker:public-banks` 覆盖公共题库自动同步、失败通知和恢复自动关闭。`npm run test:worker:exports` 覆盖导出 worker 生成可信资源并标记 `securityScanStatus=passed``npm run audit:runtime` 当前为 0 vulnerabilitiesExcel 解析已从 `exceljs` 切换为 `read-excel-file`,避免生产运行时携带 `exceljs -> uuid` 的已知中危依赖。
注意:`apps/taro` 是静态构建工程,线上发布 `apps/taro/dist/**`,不发布 `node_modules`。Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 提示,不能用 `npm audit fix --force` 降级到 Taro 3 破坏构建;上线验收时以 `audit:runtime`、构建产物、前端密钥检查和静态服务器配置为准,并持续跟进 Taro 官方修复。 注意:`apps/taro` 是静态构建工程,线上发布 `apps/taro/dist/**`,不发布 `node_modules`。Taro 4.2.0 当前构建工具链仍会触发 `npm run audit:taro:toolchain` 的上游 high/critical 提示,不能用 `npm audit fix --force` 降级到 Taro 3 破坏构建;上线验收时以 `audit:runtime`、构建产物、前端密钥检查和静态服务器配置为准,并持续跟进 Taro 官方修复。

View File

@@ -172,7 +172,7 @@
| --- | --- | --- | | --- | --- | --- |
| PocketBase schema/导出分析 | 可联调 | `scripts/import-pocketbase` 支持 schema summary/risk、`npm run pb:import:dry-run` 导出目录静态迁移报告 | | PocketBase schema/导出分析 | 可联调 | `scripts/import-pocketbase` 支持 schema summary/risk、`npm run pb:import:dry-run` 导出目录静态迁移报告 |
| PocketBase JSON dry-run | 可联调 | 不写数据库检查导出目录、JSON 形态、核心集合、旧 ID、敏感字段、schema relation、未映射集合和关键业务计数`--profile=production` 会额外检查生产迁移必需集合和关键字段覆盖率,正式切换建议配合 `--fail-on-warnings` | | PocketBase JSON dry-run | 可联调 | 不写数据库检查导出目录、JSON 形态、核心集合、旧 ID、敏感字段、schema relation、未映射集合和关键业务计数`--profile=production` 会额外检查生产迁移必需集合和关键字段覆盖率,正式切换建议配合 `--fail-on-warnings` |
| PocketBase SQLite 标准化导入 | 可联调 | 已支持真实 SQLite 只读导出后的核心集合标准化,新增覆盖 `user_answer_records -> answer_records/wrong_questions``mock_exam_configs -> practice_blueprints``referral_qrcodes -> referral_qrcodes/referral_codes``commission_settings -> tenant_commission_settings`;干净本地 Supabase 全量真实导入 248555 条记录最近约 10 分 11 秒,`pb:import:validate` 为 0 failures、3 warnings导入后会生成 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系和 3102 个顺序/随机练习蓝图,并回填全部已发布旧题的 `entry_id/content_node_id/primary_collection_id`;缺用户订单会进入财务复核且不自动开权益,缺归属手册章节会进入“迁移待复核手册”并写 `pb_import_issues`;正式切换前仍需人工处理 7 个已支付缺用户订单和 22 个待复核手册章节 | | PocketBase SQLite 标准化导入 | 可联调 | 已支持真实 SQLite 只读导出后的核心集合标准化,新增覆盖 `user_answer_records -> answer_records/wrong_questions``mock_exam_configs -> practice_blueprints``referral_qrcodes -> referral_qrcodes/referral_codes``commission_settings -> tenant_commission_settings`;干净本地 Supabase 全量真实导入 248555 条记录最近约 10 分 11 秒,`pb:import:validate` 为 0 failures、3 warnings已补 `npm run pb:import:sample` 只读业务抽样,当前真实迁移库为 0 failures、6 warnings、1 skipped、39 passed覆盖题库入口、内容节点、题目合集、练习蓝图、题目版本、学习记录、单词、手册、分数线、订单、支付、权益、激活码、资源和敏感字段泄露导入后会生成 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系和 3102 个顺序/随机练习蓝图,并回填全部已发布旧题的 `entry_id/content_node_id/primary_collection_id`;缺用户订单会进入财务复核且不自动开权益,缺归属手册章节会进入“迁移待复核手册”并写 `pb_import_issues`;正式切换前仍需人工处理 7 个已支付缺用户订单和 22 个待复核手册章节 |
| 题目 JSON preview/import | 可联调 | 后端负责规范化、issue、幂等、审计 | | 题目 JSON preview/import | 可联调 | 后端负责规范化、issue、幂等、审计 |
| 公共题库采纳、手动同步和自动同步 | 可联调 | 平台授权后,租户可采纳公共题库并复制已发布题目快照;同步 API 和 `public-banks` worker 支持新增/更新题目、重新校验授权、跨租户拒绝、审计记录、租户内容通知和租户自改冲突保护;冲突处理 API 已支持单条/批量采纳平台版本和保留租户本地版本worker 失败会写 `public_question_bank_sync_failed` 租户通知并保留稳定错误码,恢复成功会自动关闭失败通知;平台可通过 `/api/platform-admin/question-bank-sync-status` 查看跨租户同步运营状态;已覆盖跨租户、重复采纳、采纳后组卷、同步新增题、通知隔离/已读/自动 resolved、冲突不覆盖、单条/批量冲突处理、worker 自动同步/失败通知/恢复关闭、starter 单地区不可见/不可采纳第二地区题库、pro 全国套餐可见第二地区题库等测试 | | 公共题库采纳、手动同步和自动同步 | 可联调 | 平台授权后,租户可采纳公共题库并复制已发布题目快照;同步 API 和 `public-banks` worker 支持新增/更新题目、重新校验授权、跨租户拒绝、审计记录、租户内容通知和租户自改冲突保护;冲突处理 API 已支持单条/批量采纳平台版本和保留租户本地版本worker 失败会写 `public_question_bank_sync_failed` 租户通知并保留稳定错误码,恢复成功会自动关闭失败通知;平台可通过 `/api/platform-admin/question-bank-sync-status` 查看跨租户同步运营状态;已覆盖跨租户、重复采纳、采纳后组卷、同步新增题、通知隔离/已读/自动 resolved、冲突不覆盖、单条/批量冲突处理、worker 自动同步/失败通知/恢复关闭、starter 单地区不可见/不可采纳第二地区题库、pro 全国套餐可见第二地区题库等测试 |
| 单词 JSON preview/import | 可联调 | 兼容旧模板 | | 单词 JSON preview/import | 可联调 | 兼容旧模板 |
@@ -228,7 +228,8 @@ npx supabase db reset
$env:DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:54322/postgres' $env:DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:54322/postgres'
npm run pb:import:json npm run pb:import:json
npm run pb:import:validate npm run pb:import:validate
npm run pb:import:sample
npm run check:importer npm run check:importer
``` ```
结果:`pb:import:json` 用时约 10 分 11 秒,`pb:import:validate` 为 0 failures、3 warnings。新题库导航计数11 个 `content_entries`、2830 个 `content_nodes`、1597 个 `question_collections`、82106 条 `question_collection_items`、3102 个 `practice_blueprints`,已发布旧题缺入口/节点/合集数为 0。`npm run pb:import:dry-run -- --profile=production --json` 仍按预期返回非 0因为真实旧数据还有 30 个订单缺 `userId` 和 22 个手册章节缺 `subjectId` 两个上线前 blocker。 结果:`pb:import:json` 用时约 10 分 11 秒,`pb:import:validate` 为 0 failures、3 warnings`pb:import:sample` 为 0 failures、6 warnings、1 skipped、39 passed。新题库导航计数11 个 `content_entries`、2830 个 `content_nodes`、1597 个 `question_collections`、82106 条 `question_collection_items`、3102 个 `practice_blueprints`,已发布旧题缺入口/节点/合集数为 0。`npm run pb:import:dry-run -- --profile=production --json` 仍按预期返回非 0因为真实旧数据还有 30 个订单缺 `userId` 和 22 个手册章节缺 `subjectId` 两个上线前 blocker。

View File

@@ -79,8 +79,9 @@
- 当前真实 dry-run 剩余 blocker30 个订单缺 `userId`,其中 7 个 paid22 个知识手册章节缺 `subjectId`。导入器已补隔离策略:缺用户订单进入财务复核且不自动开权益,缺归属手册章节进入“迁移待复核手册”。正式切换前仍必须人工找回/确认这些记录。 - 当前真实 dry-run 剩余 blocker30 个订单缺 `userId`,其中 7 个 paid22 个知识手册章节缺 `subjectId`。导入器已补隔离策略:缺用户订单进入财务复核且不自动开权益,缺归属手册章节进入“迁移待复核手册”。正式切换前仍必须人工找回/确认这些记录。
- 已补真实 mapper gap`user_answer_records` 85442 条标准化到 `answer_records/wrong_questions``mock_exam_configs` 48 条标准化到 `practice_blueprints``referral_qrcodes` 79 条标准化到 `referral_qrcodes/referral_codes``commission_settings` 1 条标准化到 `tenant_commission_settings` - 已补真实 mapper gap`user_answer_records` 85442 条标准化到 `answer_records/wrong_questions``mock_exam_configs` 48 条标准化到 `practice_blueprints``referral_qrcodes` 79 条标准化到 `referral_qrcodes/referral_codes``commission_settings` 1 条标准化到 `tenant_commission_settings`
- 真实 `pb:import:json` 已在干净本地 Supabase 上跑通248555 条真实记录最近导入约 10 分 11 秒;`pb:import:validate` 为 0 failures、3 warnings。核心落库计数3670 用户、74102 题目、85199 旧答题记录、38205 错题、636 订单、447 权益、79 个推广码、1 条租户分佣设置;同时生成 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系、3102 个顺序/随机练习蓝图,已发布旧题缺入口/节点/合集数为 0。 - 真实 `pb:import:json` 已在干净本地 Supabase 上跑通248555 条真实记录最近导入约 10 分 11 秒;`pb:import:validate` 为 0 failures、3 warnings。核心落库计数3670 用户、74102 题目、85199 旧答题记录、38205 错题、636 订单、447 权益、79 个推广码、1 条租户分佣设置;同时生成 11 个题库入口、2830 个内容节点、1597 个题目合集、82106 条合集题目关系、3102 个顺序/随机练习蓝图,已发布旧题缺入口/节点/合集数为 0。
- 已补 `npm run pb:import:sample` 真实业务抽样验收,只读检查题库入口、内容节点、题目合集、练习蓝图、题目当前版本、答题记录、错题、收藏、单词、手册、分数线、订单、支付、权益、激活码、资源台账和敏感字段泄露。当前真实迁移库结果为 0 failures、6 warnings、1 skipped、39 passedwarning 均为旧数据人工复核或上线留档项。
- 最新导入 run 剩余 29 个 critical import issues7 个已支付订单缺用户、22 个手册章节缺所属手册,均为正式切换前人工复核项;旧答题记录空用户或旧用户已删除已降为 warning。 - 最新导入 run 剩余 29 个 critical import issues7 个已支付订单缺用户、22 个手册章节缺所属手册,均为正式切换前人工复核项;旧答题记录空用户或旧用户已删除已降为 warning。
- 处理完 blocker 后再跑 `npm run pb:import:dry-run -- --profile=production --json --fail-on-warnings`,再跑 `pb:import:json``pb:import:validate`业务抽样 - 处理完 blocker 后再跑 `npm run pb:import:dry-run -- --profile=production --json --fail-on-warnings`,再跑 `pb:import:json``pb:import:validate` `pb:import:sample`
- 对题目 JSON、单词、知识手册、分数线、视频走后端 preview/import API 做二次验证。 - 对题目 JSON、单词、知识手册、分数线、视频走后端 preview/import API 做二次验证。
4. 部署配置 4. 部署配置

View File

@@ -329,6 +329,39 @@ Remove-Item Env:\FAIL_ON_WARNINGS
## 阶段 4抽样验收 ## 阶段 4抽样验收
结构校验通过后,先运行只读业务抽样脚本:
```bash
npm run pb:import:sample
```
它和 `pb:import:validate` 的边界不同:
- `pb:import:validate` 证明核心表关系、敏感字段和迁移隔离策略没有断。
- `pb:import:sample` 证明真实迁移数据能被新 SaaS 业务模型消费,覆盖题库入口、内容节点、合集、练习蓝图、题目当前版本、答题记录、错题、收藏、单词、知识手册、分数线、订单、支付、权益、激活码、内容资源、视频和敏感字段泄露。
- 脚本只读数据库不修复数据不生成测试数据FAIL 表示当前业务模型不能安全消费该部分迁移数据WARN 表示旧数据或上线配置需要人工复核。
常用参数:
```powershell
$env:DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres"
$env:PB_SAMPLE_TENANT_SLUG="master"
npm run pb:import:sample
# 需要保存本地报告时开启;报告目录已被 .gitignore 忽略
$env:PB_SAMPLE_WRITE_REPORT="true"
npm run pb:import:sample
Remove-Item Env:\PB_SAMPLE_WRITE_REPORT
```
当前真实迁移库最近一次抽样结果:
```text
npm run pb:import:sample => 0 failures, 6 warnings, 1 skipped, 39 passed
```
其中 6 个 warning 分别对应:迁移待复核题目、部分旧答题记录引用已删除题、已支付缺用户订单、已使用但缺使用人的激活码、已知 critical import issues 人工复核项、最新导入 warning/error issue 留档项;`videos.not_present` 为 SKIP因为当前旧库没有题目视频记录。
每次演练都应至少抽样下面数据: 每次演练都应至少抽样下面数据:
| 范围 | 抽样建议 | 验收点 | | 范围 | 抽样建议 | 验收点 |

View File

@@ -70,6 +70,7 @@
"pb:export:sqlite": "npm --workspace @tiku-saas/import-pocketbase run export:sqlite --", "pb:export:sqlite": "npm --workspace @tiku-saas/import-pocketbase run export:sqlite --",
"pb:import:dry-run": "npm --workspace @tiku-saas/import-pocketbase run import:dry-run --", "pb:import:dry-run": "npm --workspace @tiku-saas/import-pocketbase run import:dry-run --",
"pb:import:json": "npm --workspace @tiku-saas/import-pocketbase run import:json", "pb:import:json": "npm --workspace @tiku-saas/import-pocketbase run import:json",
"pb:import:validate": "npm --workspace @tiku-saas/import-pocketbase run import:validate" "pb:import:validate": "npm --workspace @tiku-saas/import-pocketbase run import:validate",
"pb:import:sample": "node scripts/pb-import-business-sample.js"
} }
} }

File diff suppressed because it is too large Load Diff