main.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Command ac15fixture 是 AC-15 阶段 2 前置件的 CLI 入口。
  2. //
  3. // 用法(在仓库根目录执行):
  4. //
  5. // go run ./cmd/ac15fixture ddl # 用 core-model-ddl.sql 补齐目标库结构(只 CREATE,不 DROP)
  6. // go run ./cmd/ac15fixture count # 打印 5 张相关表当前行数
  7. // go run ./cmd/ac15fixture map --user 53691 # 抽样搬运(不改任何已有 target 业务代码)
  8. // go run ./cmd/ac15fixture verify --user 53691 # 逐列核对(退出码 0=PASS,1=FAIL)
  9. // go run ./cmd/ac15fixture run --user 53691 # map + verify
  10. // go run ./cmd/ac15fixture clean --user 53691 # 删除 fixture 数据并复核回 0 行
  11. //
  12. // 环境变量(口令不落盘、不打印):
  13. //
  14. // AC15_DB_HOST / AC15_DB_PORT / AC15_DB_USER / AC15_DB_PASS / AC15_TARGET_DB
  15. // AC15_LEGACY_USER / AC15_LEGACY_PASS / AC15_LEGACY_DB
  16. // AC15_CORE_DDL core DDL 路径(默认取仓库外部的 02-data-model/core-model-ddl.sql)
  17. //
  18. // 退出码:0 = 成功/PASS;1 = 核对 FAIL;2 = 参数错误;3 = 环境或守卫失败。
  19. package main
  20. import (
  21. "context"
  22. "flag"
  23. "fmt"
  24. "os"
  25. "yuansiwei/unified-backend/internal/academic/dualrun/fixture"
  26. )
  27. func main() {
  28. if len(os.Args) < 2 {
  29. usage()
  30. os.Exit(2)
  31. }
  32. cmd := os.Args[1]
  33. args := os.Args[2:]
  34. switch cmd {
  35. case "ddl":
  36. os.Exit(runDDL(args))
  37. case "map":
  38. os.Exit(runMap(args))
  39. case "verify":
  40. os.Exit(runVerify(args))
  41. case "run":
  42. os.Exit(runAll(args))
  43. case "clean":
  44. os.Exit(runClean(args))
  45. case "count":
  46. os.Exit(runCount(args))
  47. case "-h", "--help", "help":
  48. usage()
  49. os.Exit(0)
  50. default:
  51. fmt.Fprintf(os.Stderr, "未知子命令:%s\n\n", cmd)
  52. usage()
  53. os.Exit(2)
  54. }
  55. }
  56. func usage() {
  57. fmt.Fprint(os.Stderr, `ac15fixture —— AC-15 阶段 2 前置件(资金域 4 表映射 + 逐列核对)
  58. 子命令:
  59. ddl 补齐 core-model-ddl.sql 的 13 张表(只 CREATE TABLE IF NOT EXISTS,禁止 DROP)
  60. map 抽样搬运一个 legacy 用户(账户 + 校区账户 + 流水)
  61. verify 逐列核对(4 张表 + AUTO_INCREMENT + 空值语义 + float 陷阱)
  62. run map + verify
  63. clean 删除 fixture 数据并复核 4 张表回 0 行
  64. count 打印相关表当前行数
  65. 通用参数:--user <legacy userid>(默认 53691), --ddl <path>
  66. `)
  67. }
  68. // bootstrap 解析公共参数、加载配置并跑守卫。
  69. func bootstrap(args []string, withUser bool) (fixture.Config, int64, string, int) {
  70. fs := flag.NewFlagSet("ac15fixture", flag.ContinueOnError)
  71. fs.SetOutput(os.Stderr)
  72. user := fs.Int64("user", fixture.DefaultLegacyUserID, "legacy userid")
  73. ddl := fs.String("ddl", "", "core DDL 路径(默认 AC15_CORE_DDL 或仓库外部默认路径)")
  74. if err := fs.Parse(args); err != nil {
  75. return fixture.Config{}, 0, "", 2
  76. }
  77. if !withUser {
  78. *user = fixture.DefaultLegacyUserID
  79. }
  80. repoRoot, err := os.Getwd()
  81. if err != nil {
  82. fmt.Fprintf(os.Stderr, "FATAL: 取工作目录失败:%v\n", err)
  83. return fixture.Config{}, 0, "", 3
  84. }
  85. cfg, err := fixture.LoadConfig(repoRoot)
  86. if err != nil {
  87. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  88. return fixture.Config{}, 0, "", 3
  89. }
  90. if err := cfg.Guard(); err != nil {
  91. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  92. return fixture.Config{}, 0, "", 3
  93. }
  94. fmt.Printf("target=%s legacy=%s\n\n", cfg.TargetMasked(), cfg.LegacyMasked())
  95. return cfg, *user, *ddl, 0
  96. }
  97. func runDDL(args []string) int {
  98. cfg, _, ddlPath, code := bootstrap(args, false)
  99. if code != 0 {
  100. return code
  101. }
  102. rep, err := fixture.ApplyCoreDDL(context.Background(), cfg, ddlPath)
  103. if rep != nil {
  104. fixture.PrintDDLReport(os.Stdout, rep)
  105. }
  106. if err != nil {
  107. fmt.Fprintf(os.Stderr, "\nFATAL: %v\n", err)
  108. return 3
  109. }
  110. return 0
  111. }
  112. func runMap(args []string) int {
  113. cfg, user, _, code := bootstrap(args, true)
  114. if code != 0 {
  115. return code
  116. }
  117. rep, err := fixture.PrepareAndLoad(context.Background(), cfg, user)
  118. if err != nil {
  119. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  120. return 3
  121. }
  122. fixture.PrintMapReport(os.Stdout, rep)
  123. return 0
  124. }
  125. func runVerify(args []string) int {
  126. cfg, user, _, code := bootstrap(args, true)
  127. if code != 0 {
  128. return code
  129. }
  130. rep, err := fixture.Verify(context.Background(), cfg, user)
  131. if err != nil {
  132. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  133. return 3
  134. }
  135. fixture.PrintVerifyReport(os.Stdout, rep)
  136. if !rep.OK() {
  137. return 1
  138. }
  139. return 0
  140. }
  141. func runAll(args []string) int {
  142. if code := runMap(args); code != 0 {
  143. return code
  144. }
  145. fmt.Println()
  146. return runVerify(args)
  147. }
  148. func runClean(args []string) int {
  149. cfg, user, _, code := bootstrap(args, true)
  150. if code != 0 {
  151. return code
  152. }
  153. rep, err := fixture.Cleanup(context.Background(), cfg, user)
  154. if err != nil {
  155. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  156. return 3
  157. }
  158. fixture.PrintCleanReport(os.Stdout, rep)
  159. return 0
  160. }
  161. func runCount(args []string) int {
  162. cfg, _, _, code := bootstrap(args, false)
  163. if code != 0 {
  164. return code
  165. }
  166. counts, err := fixture.CountTargetTables(context.Background(), cfg)
  167. if err != nil {
  168. fmt.Fprintf(os.Stderr, "FATAL: %v\n", err)
  169. return 3
  170. }
  171. fmt.Println("== 目标表行数 ==")
  172. for _, t := range []string{"t_user", "t_legacy_mapping", "t_user_expense", "t_user_expense_school", "t_user_expense_log"} {
  173. fmt.Printf(" %-24s %d\n", t, counts[t])
  174. }
  175. return 0
  176. }