| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- // Command ac15fixture 是 AC-15 阶段 2 前置件的 CLI 入口。
- //
- // 两批能力:
- //
- // 第一批(资金域 4 表:t_user_expense / _school / _log + t_user / t_legacy_mapping)
- // ddl 用 core-model-ddl.sql 补齐目标库结构(只 CREATE,不 DROP)
- // map --user 53691 抽样搬运
- // verify --user 53691 逐列核对(退出码 0=PASS,1=FAIL)
- // run --user 53691 map + verify
- // clean --user 53691 删除 fixture 数据并复核回 0 行
- // count 打印相关表当前行数
- //
- // 第二批(t_user_divide_into / _log / t_user_balance_request / t_expenditure_log
- // + 字典 t_expenditure,biz_no 口径首次落地)
- // map2 抽样搬运 4 表
- // verify2 逐列核对 + biz_no 四项 + D1 id 一致性
- // idem2 幂等实测(同一批跑两次,比对行数与 id)
- // run2 map2 + verify2 + idem2
- // clean2 清理并复核 7 张表回 0 行
- // count2 打印第二批涉及表当前行数
- //
- // 环境变量(口令不落盘、不打印):
- //
- // AC15_DB_HOST / AC15_DB_PORT / AC15_DB_USER / AC15_DB_PASS / AC15_TARGET_DB
- // AC15_LEGACY_USER / AC15_LEGACY_PASS / AC15_LEGACY_DB
- // AC15_CORE_DDL core DDL 路径(默认取仓库外部的 02-data-model/core-model-ddl.sql)
- //
- // 退出码:0 = 成功/PASS;1 = 核对 FAIL;2 = 参数错误;3 = 环境或守卫失败。
- package main
- import (
- "context"
- "flag"
- "fmt"
- "os"
- "yuansiwei/unified-backend/internal/academic/dualrun/fixture"
- )
- func main() {
- if len(os.Args) < 2 {
- usage()
- os.Exit(2)
- }
- cmd := os.Args[1]
- args := os.Args[2:]
- switch cmd {
- case "ddl":
- os.Exit(runDDL(args))
- case "map":
- os.Exit(runMap(args))
- case "verify":
- os.Exit(runVerify(args))
- case "run":
- os.Exit(runAll(args))
- case "clean":
- os.Exit(runClean(args))
- case "count":
- os.Exit(runCount(args))
- case "map2":
- os.Exit(runMap2(args))
- case "verify2":
- os.Exit(runVerify2(args))
- case "idem2":
- os.Exit(runIdem2(args))
- case "run2":
- os.Exit(runRun2(args))
- case "clean2":
- os.Exit(runClean2(args))
- case "count2":
- os.Exit(runCount2(args))
- case "-h", "--help", "help":
- usage()
- os.Exit(0)
- default:
- fmt.Fprintf(os.Stderr, "未知子命令:%s\n\n", cmd)
- usage()
- os.Exit(2)
- }
- }
- func usage() {
- fmt.Fprint(os.Stderr, `ac15fixture —— AC-15 阶段 2 前置件(资金域映射 + 逐列核对)
- 第一批(D1 口径为主):
- ddl 补齐 core-model-ddl.sql 的 13 张表(只 CREATE TABLE IF NOT EXISTS,禁止 DROP)
- map 抽样搬运一个 legacy 用户(账户 + 校区账户 + 流水)
- verify 逐列核对 4 张表 + AUTO_INCREMENT + 空值语义 + float 陷阱
- run map + verify
- clean 删除 fixture 数据并复核 4 张表回 0 行
- count 打印相关表当前行数
- 第二批(biz_no 口径首次落地):
- map2 搬运 t_user_divide_into / _log / t_user_balance_request / t_expenditure_log(+字典)
- verify2 逐列核对 + biz_no 四项 + D1 id 一致性
- idem2 幂等实测(同一批跑两次,比行数与 id)
- run2 map2 + verify2 + idem2
- clean2 清理并复核 7 张表回 0 行
- count2 打印第二批涉及表当前行数
- 第一批参数:--user <legacy userid>(默认 53691), --ddl <path>
- 第二批参数:--root <分账树根 userid,默认 395>, --buser <充值工单用户,默认 31348>,
- --euser <收支记录创建人,默认 395>, --dict <是否迁移字典,默认 true>
- `)
- }
- // bootstrap 解析第一批公共参数、加载配置并跑守卫。
- func bootstrap(args []string, withUser bool) (fixture.Config, int64, string, int) {
- fs := flag.NewFlagSet("ac15fixture", flag.ContinueOnError)
- fs.SetOutput(os.Stderr)
- user := fs.Int64("user", fixture.DefaultLegacyUserID, "legacy userid")
- ddl := fs.String("ddl", "", "core DDL 路径(默认 AC15_CORE_DDL 或仓库外部默认路径)")
- if err := fs.Parse(args); err != nil {
- return fixture.Config{}, 0, "", 2
- }
- if !withUser {
- *user = fixture.DefaultLegacyUserID
- }
- cfg, code := loadConfig()
- if code != 0 {
- return fixture.Config{}, 0, "", code
- }
- return cfg, *user, *ddl, 0
- }
- // loadConfig 取工作目录 → 读配置 → 守卫 → 打印脱敏后的连接串。
- func loadConfig() (fixture.Config, int) {
- repoRoot, err := os.Getwd()
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: 取工作目录失败:%v\n", err)
- return fixture.Config{}, 3
- }
- cfg, err := fixture.LoadConfig(repoRoot)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return fixture.Config{}, 3
- }
- if err := cfg.Guard(); err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return fixture.Config{}, 3
- }
- fmt.Printf("target=%s legacy=%s\n\n", cfg.TargetMasked(), cfg.LegacyMasked())
- return cfg, 0
- }
- // bootstrap2 解析第二批参数。
- func bootstrap2(args []string) (fixture.Config, fixture.Batch2Options, int) {
- def := fixture.DefaultBatch2Options()
- fs := flag.NewFlagSet("ac15fixture-b2", flag.ContinueOnError)
- fs.SetOutput(os.Stderr)
- root := fs.Int64("root", def.DivideRootUserID, "分账树根 userid(取整棵子树)")
- buser := fs.Int64("buser", def.BalanceRequestUserID, "充值工单所属 userid")
- euser := fs.Int64("euser", def.ExpenditureLogUserID, "收支记录创建人 userid")
- dict := fs.Bool("dict", def.WithExpenditureDict, "是否同时 D1 迁移 t_expenditure 字典")
- if err := fs.Parse(args); err != nil {
- return fixture.Config{}, fixture.Batch2Options{}, 2
- }
- cfg, code := loadConfig()
- if code != 0 {
- return fixture.Config{}, fixture.Batch2Options{}, code
- }
- return cfg, fixture.Batch2Options{
- DivideRootUserID: *root,
- BalanceRequestUserID: *buser,
- ExpenditureLogUserID: *euser,
- WithExpenditureDict: *dict,
- }, 0
- }
- func runDDL(args []string) int {
- cfg, _, ddlPath, code := bootstrap(args, false)
- if code != 0 {
- return code
- }
- rep, err := fixture.ApplyCoreDDL(context.Background(), cfg, ddlPath)
- if rep != nil {
- fixture.PrintDDLReport(os.Stdout, rep)
- }
- if err != nil {
- fmt.Fprintf(os.Stderr, "\nFATAL: %v\n", err)
- return 3
- }
- return 0
- }
- func runMap(args []string) int {
- cfg, user, _, code := bootstrap(args, true)
- if code != 0 {
- return code
- }
- rep, err := fixture.PrepareAndLoad(context.Background(), cfg, user)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintMapReport(os.Stdout, rep)
- return 0
- }
- func runVerify(args []string) int {
- cfg, user, _, code := bootstrap(args, true)
- if code != 0 {
- return code
- }
- rep, err := fixture.Verify(context.Background(), cfg, user)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintVerifyReport(os.Stdout, rep)
- if !rep.OK() {
- return 1
- }
- return 0
- }
- func runAll(args []string) int {
- if code := runMap(args); code != 0 {
- return code
- }
- fmt.Println()
- return runVerify(args)
- }
- func runClean(args []string) int {
- cfg, user, _, code := bootstrap(args, true)
- if code != 0 {
- return code
- }
- rep, err := fixture.Cleanup(context.Background(), cfg, user)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintCleanReport(os.Stdout, rep)
- return 0
- }
- func runCount(args []string) int {
- cfg, _, _, code := bootstrap(args, false)
- if code != 0 {
- return code
- }
- counts, err := fixture.CountTargetTables(context.Background(), cfg)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintCounts(os.Stdout, "第一批目标表行数", counts,
- []string{"t_user", "t_legacy_mapping", "t_user_expense", "t_user_expense_school", "t_user_expense_log"})
- return 0
- }
- // ---------- 第二批 ----------
- func runMap2(args []string) int {
- cfg, opts, code := bootstrap2(args)
- if code != 0 {
- return code
- }
- rep, err := fixture.PrepareBatch2(context.Background(), cfg, opts)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintBatch2Report(os.Stdout, rep)
- return 0
- }
- func runVerify2(args []string) int {
- cfg, opts, code := bootstrap2(args)
- if code != 0 {
- return code
- }
- rep, err := fixture.VerifyBatch2(context.Background(), cfg, opts)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintBatch2Verify(os.Stdout, rep)
- if !rep.OK() {
- return 1
- }
- return 0
- }
- func runIdem2(args []string) int {
- cfg, opts, code := bootstrap2(args)
- if code != 0 {
- return code
- }
- rep, err := fixture.IdempotencyCheck(context.Background(), cfg, opts)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintIdempotency(os.Stdout, rep)
- if !rep.AllOK() {
- return 1
- }
- return 0
- }
- func runRun2(args []string) int {
- if code := runMap2(args); code != 0 {
- return code
- }
- fmt.Println()
- if code := runVerify2(args); code != 0 {
- return code
- }
- fmt.Println()
- return runIdem2(args)
- }
- func runClean2(args []string) int {
- cfg, opts, code := bootstrap2(args)
- if code != 0 {
- return code
- }
- rep, err := fixture.CleanupBatch2(context.Background(), cfg, opts)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintBatch2Clean(os.Stdout, rep)
- return 0
- }
- func runCount2(args []string) int {
- cfg, _, code := bootstrap2(args)
- if code != 0 {
- return code
- }
- counts, err := fixture.CountBatch2Tables(context.Background(), cfg)
- if err != nil {
- fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
- return 3
- }
- fixture.PrintCounts(os.Stdout, "第二批目标表行数", counts, fixture.Batch2Tables())
- return 0
- }
|