package fixture import ( "fmt" "io" "strings" ) // 本文件只做第二批报告的终端排版。 // PrintBatch2Report 打印「搬 4 表」结果。 func PrintBatch2Report(w io.Writer, r *Batch2Report) { fmt.Fprintf(w, "== 第二批映射与装载 ==\n") fmt.Fprintf(w, "取样:分账树根=%d,充值工单用户=%d,收支记录创建人=%d,字典=%v\n", r.Options.DivideRootUserID, r.Options.BalanceRequestUserID, r.Options.ExpenditureLogUserID, r.Options.WithExpenditureDict) fmt.Fprintf(w, "legacy 行数:divide_into=%d, divide_into_log=%d, balance_request=%d, expenditure_log=%d, 字典=%d\n", r.LegacyCounts["t_user_divide_into"], r.LegacyCounts["t_user_divide_into_log"], r.LegacyCounts["t_user_balance_request"], r.LegacyCounts["t_expenditure_log"], r.LegacyCounts["t_expenditure"]) fmt.Fprintf(w, "写入:t_expenditure=%d, t_user_divide_into=%d, t_user_divide_into_log=%d, "+ "t_user_balance_request=%d, t_expenditure_log=%d\n", r.LoadedCounts["t_expenditure"], r.LoadedCounts["t_user_divide_into"], r.LoadedCounts["t_user_divide_into_log"], r.LoadedCounts["t_user_balance_request"], r.LoadedCounts["t_expenditure_log"]) fmt.Fprintf(w, "引用人映射:%d 个 legacy uid → ULID(t_user %d 行 / t_legacy_mapping %d 行)\n", len(r.RefUsers), r.UsersWritten, r.MappingsWritten) fmt.Fprintf(w, "分账明细 id(D1 原样保留):%v\n", r.DivideLogIDs) if len(r.BizNoSamples) > 0 { fmt.Fprintf(w, "biz_no 样本(文本列已脱敏):\n") for _, s := range r.BizNoSamples { fmt.Fprintf(w, " %-24s biz=%s type=%-4d money=%s time=%s remark=%s\n", s.Table, s.BizNo, s.Type, CentsToDecimal(s.MoneyCents), s.CreateTime, s.RemarkMask) } } if len(r.Warnings) > 0 { fmt.Fprintf(w, "映射告警(%d):\n", len(r.Warnings)) for _, x := range r.Warnings { fmt.Fprintf(w, " - %s\n", x) } } else { fmt.Fprintf(w, "映射告警:无\n") } } // PrintBatch2Verify 打印第二批核对结果(按「列 × N 行」聚合,避免逐格刷屏)。 func PrintBatch2Verify(w io.Writer, r *Batch2VerifyReport) { fmt.Fprintf(w, "== 第二批逐列核对(列 × 行 聚合)==\n") lastTable := "" for _, c := range r.Cols { if c.Table != lastTable { fmt.Fprintf(w, "\n-- %s(target %d 行)--\n", c.Table, r.TargetRows[c.Table]) lastTable = c.Table } st := markOK if !c.OK() { st = markFail } fmt.Fprintf(w, " %s %-24s %d/%d 相等 %s\n", st, c.Column, c.Equal, c.Total, c.Note) for _, s := range c.Samples { fmt.Fprintf(w, " ✘ %s\n", s) } } fmt.Fprintf(w, "\n== D1:id 与 legacy 完全相同 ==\n") for _, d := range r.D1 { st := markOK if !d.Pass { st = markFail } fmt.Fprintf(w, " %s %-24s legacy=%v\n target=%v\n", st, d.Table, d.LegacyIDs, d.TargetIDs) } fmt.Fprintf(w, "\n== biz_no 四项验证 ==\n") for _, v := range r.BizNo { fmt.Fprintf(w, " %s:legacy %d 行 / target %d 行\n", v.Table, v.LegacyRows, v.Rows) fmt.Fprintf(w, " ① 逐字符相等 %d 行;长度=32 的 %d 行;hex 形态 %d 行\n", v.CharEqual, v.Len32, v.HexOK) fmt.Fprintf(w, " ② 唯一:DISTINCT biz_no = %d(总 %d)\n", v.UniqueBizNo, v.Rows) fmt.Fprintf(w, " ③ id 解耦:max(id)=%d, AUTO_INCREMENT=%d;反序镜像 %v → %v\n", v.MaxID, v.AutoIncrementVal, v.MirrorOK, v.MirrorSample) fmt.Fprintf(w, " ④ 反查:biz_no JOIN 回 legacy 命中 %d 行\n", v.ReverseJoin) } fmt.Fprintf(w, "\n== 语义断言 ==\n") for _, a := range r.Assertions { st := markOK if !a.Pass { st = markFail } fmt.Fprintf(w, " %s %s\n %s\n", st, a.Name, a.Detail) } fmt.Fprintf(w, "\n核对结论:%s(不一致列 + 失败断言 = %d)\n", verdict(r.OK()), r.FailCount()) } // PrintIdempotency 打印「跑两次」的幂等结论。 func PrintIdempotency(w io.Writer, r *IdempotencyResult) { fmt.Fprintf(w, "== 幂等实测(同一批数据跑两次)==\n") for _, t := range []string{"t_user_balance_request", "t_expenditure_log"} { st := markOK if !r.SameRows[t] || !r.SameIDs[t] || r.NewBizNos[t] != 0 { st = markFail } fmt.Fprintf(w, " %s %s\n", st, t) fmt.Fprintf(w, " 第一次:%d 行,id=%v\n", len(r.Before[t]), r.IDListFirst[t]) fmt.Fprintf(w, " 第二次:%d 行,id=%v\n", len(r.After[t]), r.IDListAgain[t]) fmt.Fprintf(w, " 行数不变=%v,已存在行 id 不变=%v,新增 biz_no=%d\n", r.SameRows[t], r.SameIDs[t], r.NewBizNos[t]) } fmt.Fprintf(w, "\n幂等结论:%s\n", verdict(r.AllOK())) } // PrintBatch2Clean 打印清理结果。 func PrintBatch2Clean(w io.Writer, r *Batch2CleanReport) { fmt.Fprintf(w, "== 第二批清理 ==\n") fmt.Fprintf(w, "按 biz_no 精确删除 %d 行(balance_request + expenditure_log)\n", r.BizNos) for _, t := range []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log", "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"} { fmt.Fprintf(w, " %-26s 删除 %-4d 行,剩余全表 %d 行\n", t, r.Deleted[t], r.Remaining[t]) } zero := true for _, t := range []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log", "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"} { if r.Remaining[t] != 0 { zero = false } } fmt.Fprintf(w, "7 张表回 0 行:%s\n", verdict(zero)) } // PrintCounts 打印一组表的行数。 func PrintCounts(w io.Writer, title string, counts map[string]int64, tables []string) { fmt.Fprintf(w, "== %s ==\n", title) for _, t := range tables { fmt.Fprintf(w, " %-26s %d\n", t, counts[t]) } } // Batch2Tables 是第二批涉及的表名(固定顺序,报告用)。 func Batch2Tables() []string { return []string{"t_expenditure", "t_user_divide_into", "t_user_divide_into_log", "t_user_balance_request", "t_expenditure_log", "t_legacy_mapping", "t_user"} } // JoinIDs 把 id 列表拼成一行(报告用,避免超长时刷屏)。 func JoinIDs(ids []int64) string { if len(ids) == 0 { return "[]" } parts := make([]string, len(ids)) for i, v := range ids { parts[i] = fmt.Sprint(v) } return "[" + strings.Join(parts, " ") + "]" }