batch2_report.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. package fixture
  2. import (
  3. "fmt"
  4. "io"
  5. "strings"
  6. )
  7. // 本文件只做第二批报告的终端排版。
  8. // PrintBatch2Report 打印「搬 4 表」结果。
  9. func PrintBatch2Report(w io.Writer, r *Batch2Report) {
  10. fmt.Fprintf(w, "== 第二批映射与装载 ==\n")
  11. fmt.Fprintf(w, "取样:分账树根=%d,充值工单用户=%d,收支记录创建人=%d,字典=%v\n",
  12. r.Options.DivideRootUserID, r.Options.BalanceRequestUserID,
  13. r.Options.ExpenditureLogUserID, r.Options.WithExpenditureDict)
  14. fmt.Fprintf(w, "legacy 行数:divide_into=%d, divide_into_log=%d, balance_request=%d, expenditure_log=%d, 字典=%d\n",
  15. r.LegacyCounts["t_user_divide_into"], r.LegacyCounts["t_user_divide_into_log"],
  16. r.LegacyCounts["t_user_balance_request"], r.LegacyCounts["t_expenditure_log"],
  17. r.LegacyCounts["t_expenditure"])
  18. fmt.Fprintf(w, "写入:t_expenditure=%d, t_user_divide_into=%d, t_user_divide_into_log=%d, "+
  19. "t_user_balance_request=%d, t_expenditure_log=%d\n",
  20. r.LoadedCounts["t_expenditure"], r.LoadedCounts["t_user_divide_into"],
  21. r.LoadedCounts["t_user_divide_into_log"], r.LoadedCounts["t_user_balance_request"],
  22. r.LoadedCounts["t_expenditure_log"])
  23. fmt.Fprintf(w, "引用人映射:%d 个 legacy uid → ULID(t_user %d 行 / t_legacy_mapping %d 行)\n",
  24. len(r.RefUsers), r.UsersWritten, r.MappingsWritten)
  25. fmt.Fprintf(w, "分账明细 id(D1 原样保留):%v\n", r.DivideLogIDs)
  26. if len(r.BizNoSamples) > 0 {
  27. fmt.Fprintf(w, "biz_no 样本(文本列已脱敏):\n")
  28. for _, s := range r.BizNoSamples {
  29. fmt.Fprintf(w, " %-24s biz=%s type=%-4d money=%s time=%s remark=%s\n",
  30. s.Table, s.BizNo, s.Type, CentsToDecimal(s.MoneyCents), s.CreateTime, s.RemarkMask)
  31. }
  32. }
  33. if len(r.Warnings) > 0 {
  34. fmt.Fprintf(w, "映射告警(%d):\n", len(r.Warnings))
  35. for _, x := range r.Warnings {
  36. fmt.Fprintf(w, " - %s\n", x)
  37. }
  38. } else {
  39. fmt.Fprintf(w, "映射告警:无\n")
  40. }
  41. }
  42. // PrintBatch2Verify 打印第二批核对结果(按「列 × N 行」聚合,避免逐格刷屏)。
  43. func PrintBatch2Verify(w io.Writer, r *Batch2VerifyReport) {
  44. fmt.Fprintf(w, "== 第二批逐列核对(列 × 行 聚合)==\n")
  45. lastTable := ""
  46. for _, c := range r.Cols {
  47. if c.Table != lastTable {
  48. fmt.Fprintf(w, "\n-- %s(target %d 行)--\n", c.Table, r.TargetRows[c.Table])
  49. lastTable = c.Table
  50. }
  51. st := markOK
  52. if !c.OK() {
  53. st = markFail
  54. }
  55. fmt.Fprintf(w, " %s %-24s %d/%d 相等 %s\n", st, c.Column, c.Equal, c.Total, c.Note)
  56. for _, s := range c.Samples {
  57. fmt.Fprintf(w, " ✘ %s\n", s)
  58. }
  59. }
  60. fmt.Fprintf(w, "\n== D1:id 与 legacy 完全相同 ==\n")
  61. for _, d := range r.D1 {
  62. st := markOK
  63. if !d.Pass {
  64. st = markFail
  65. }
  66. fmt.Fprintf(w, " %s %-24s legacy=%v\n target=%v\n", st, d.Table, d.LegacyIDs, d.TargetIDs)
  67. }
  68. fmt.Fprintf(w, "\n== biz_no 四项验证 ==\n")
  69. for _, v := range r.BizNo {
  70. fmt.Fprintf(w, " %s:legacy %d 行 / target %d 行\n", v.Table, v.LegacyRows, v.Rows)
  71. fmt.Fprintf(w, " ① 逐字符相等 %d 行;长度=32 的 %d 行;hex 形态 %d 行\n", v.CharEqual, v.Len32, v.HexOK)
  72. fmt.Fprintf(w, " ② 唯一:DISTINCT biz_no = %d(总 %d)\n", v.UniqueBizNo, v.Rows)
  73. fmt.Fprintf(w, " ③ id 解耦:max(id)=%d, AUTO_INCREMENT=%d;反序镜像 %v → %v\n",
  74. v.MaxID, v.AutoIncrementVal, v.MirrorOK, v.MirrorSample)
  75. fmt.Fprintf(w, " ④ 反查:biz_no JOIN 回 legacy 命中 %d 行\n", v.ReverseJoin)
  76. }
  77. fmt.Fprintf(w, "\n== 语义断言 ==\n")
  78. for _, a := range r.Assertions {
  79. st := markOK
  80. if !a.Pass {
  81. st = markFail
  82. }
  83. fmt.Fprintf(w, " %s %s\n %s\n", st, a.Name, a.Detail)
  84. }
  85. fmt.Fprintf(w, "\n核对结论:%s(不一致列 + 失败断言 = %d)\n", verdict(r.OK()), r.FailCount())
  86. }
  87. // PrintIdempotency 打印「跑两次」的幂等结论。
  88. func PrintIdempotency(w io.Writer, r *IdempotencyResult) {
  89. fmt.Fprintf(w, "== 幂等实测(同一批数据跑两次)==\n")
  90. for _, t := range []string{"t_user_balance_request", "t_expenditure_log"} {
  91. st := markOK
  92. if !r.SameRows[t] || !r.SameIDs[t] || r.NewBizNos[t] != 0 {
  93. st = markFail
  94. }
  95. fmt.Fprintf(w, " %s %s\n", st, t)
  96. fmt.Fprintf(w, " 第一次:%d 行,id=%v\n", len(r.Before[t]), r.IDListFirst[t])
  97. fmt.Fprintf(w, " 第二次:%d 行,id=%v\n", len(r.After[t]), r.IDListAgain[t])
  98. fmt.Fprintf(w, " 行数不变=%v,已存在行 id 不变=%v,新增 biz_no=%d\n",
  99. r.SameRows[t], r.SameIDs[t], r.NewBizNos[t])
  100. }
  101. fmt.Fprintf(w, "\n幂等结论:%s\n", verdict(r.AllOK()))
  102. }
  103. // PrintBatch2Clean 打印清理结果。
  104. func PrintBatch2Clean(w io.Writer, r *Batch2CleanReport) {
  105. fmt.Fprintf(w, "== 第二批清理 ==\n")
  106. fmt.Fprintf(w, "按 biz_no 精确删除 %d 行(balance_request + expenditure_log)\n", r.BizNos)
  107. for _, t := range []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log",
  108. "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"} {
  109. fmt.Fprintf(w, " %-26s 删除 %-4d 行,剩余全表 %d 行\n", t, r.Deleted[t], r.Remaining[t])
  110. }
  111. zero := true
  112. for _, t := range []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log",
  113. "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"} {
  114. if r.Remaining[t] != 0 {
  115. zero = false
  116. }
  117. }
  118. fmt.Fprintf(w, "7 张表回 0 行:%s\n", verdict(zero))
  119. }
  120. // PrintCounts 打印一组表的行数。
  121. func PrintCounts(w io.Writer, title string, counts map[string]int64, tables []string) {
  122. fmt.Fprintf(w, "== %s ==\n", title)
  123. for _, t := range tables {
  124. fmt.Fprintf(w, " %-26s %d\n", t, counts[t])
  125. }
  126. }
  127. // Batch2Tables 是第二批涉及的表名(固定顺序,报告用)。
  128. func Batch2Tables() []string {
  129. return []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log",
  130. "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"}
  131. }
  132. // JoinIDs 把 id 列表拼成一行(报告用,避免超长时刷屏)。
  133. func JoinIDs(ids []int64) string {
  134. if len(ids) == 0 {
  135. return "[]"
  136. }
  137. parts := make([]string, len(ids))
  138. for i, v := range ids {
  139. parts[i] = fmt.Sprint(v)
  140. }
  141. return "[" + strings.Join(parts, " ") + "]"
  142. }