From 28e6e397188fe25895c70d09b26ce4fab7cbb96b Mon Sep 17 00:00:00 2001 From: spiritlhl <103393591+spiritLHLS@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:19:13 +0800 Subject: [PATCH] fix: restore classic route labels --- bk/route_render.go | 99 ++++++++++++++++++++++++++++++++++++----- bk/route_report_test.go | 52 +++++++++++++++++++++- model/model.go | 2 +- 3 files changed, 140 insertions(+), 13 deletions(-) diff --git a/bk/route_render.go b/bk/route_render.go index d73bc2a..a2762e8 100644 --- a/bk/route_render.go +++ b/bk/route_render.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/oneclickvirt/backtrace/model" . "github.com/oneclickvirt/defaultset" ) @@ -13,22 +14,12 @@ import ( func RenderRouteReport(report RouteReport) string { var builder strings.Builder for _, target := range report.Targets { - label := target.Classification.Label - if label == "" { - label = "线路证据不足" - } var rendered string switch { case target.Status != RouteProbeAvailable: rendered = Red("检测不到回程路由节点的IP地址") - case target.Classification.Confidence == routeConfidenceInconclusive: - rendered = Red(label) - case target.Classification.Rank >= 4: - rendered = DarkGreen(label) - case target.Classification.Rank == 3: - rendered = Green(label) default: - rendered = White(label) + rendered = renderLegacyRouteLabels(target) } addressWidth := 15 if target.Target.IPVersion == "v6" { @@ -38,3 +29,89 @@ func RenderRouteReport(report RouteReport) string { } return strings.TrimSuffix(builder.String(), "\n") } + +// renderLegacyRouteLabels keeps the original terminal contract: every known +// carrier ASN observed in the trace remains visible. The conservative +// Classification is retained for JSON consumers, but its evidence-oriented +// fallback labels must not replace the classic human-readable route result. +func renderLegacyRouteLabels(target RouteTargetReport) string { + asns := uniqueStrings(target.ObservedASNs) + hasAS4134 := containsString(asns, "AS4134") + hasAS4809 := containsString(asns, "AS4809") + + ordered := make([]string, 0, len(asns)+1) + if hasAS4809 { + if hasAS4134 { + ordered = append(ordered, "AS4809b") + } else { + ordered = append(ordered, "AS4809a") + } + } + ordered = append(ordered, asns...) + + seenLabels := make(map[string]struct{}) + labels := make([]string, 0, len(ordered)) + for _, asn := range ordered { + if asn == "AS4809" { + continue + } + label := model.M[asn] + if label == "" { + continue + } + if _, exists := seenLabels[label]; exists { + continue + } + seenLabels[label] = struct{}{} + switch asn { + case "AS9929", "AS4809a", "AS23764": + labels = append(labels, DarkGreen(label)) + case "AS4809b", "AS58807": + labels = append(labels, Green(label)) + default: + labels = append(labels, White(label)) + } + } + if len(labels) > 0 { + return strings.Join(labels, " ") + } + + // Reports produced by older API clients may contain only Classification. + if target.Classification.Confidence != routeConfidenceInconclusive && target.Classification.Label != "" { + switch { + case target.Classification.Rank >= 4: + return DarkGreen(target.Classification.Label) + case target.Classification.Rank == 3: + return Green(target.Classification.Label) + default: + return White(target.Classification.Label) + } + } + return Red("检测不到已知线路的ASN") +} + +func uniqueStrings(values []string) []string { + seen := make(map[string]struct{}, len(values)) + result := make([]string, 0, len(values)) + for _, value := range values { + value = strings.ToUpper(strings.TrimSpace(value)) + if value == "" { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} + +func containsString(values []string, target string) bool { + for _, value := range values { + if value == target { + return true + } + } + return false +} diff --git a/bk/route_report_test.go b/bk/route_report_test.go index fc00d65..c2b8968 100644 --- a/bk/route_report_test.go +++ b/bk/route_report_test.go @@ -39,7 +39,8 @@ func TestRunRouteReportOfflineFixture(t *testing.T) { if target.Classification.Code != "ct_cn2_gia" || target.Latency.Samples != 6 || target.Latency.P95MS != 30 { t.Fatalf("unexpected route classification or latency: %+v", target) } - if strings.Contains(RenderRouteReport(report), "P95") || !strings.Contains(RenderRouteReport(report), "电信CN2GIA") { + rendered := RenderRouteReport(report) + if strings.Contains(rendered, "P95") || !strings.Contains(rendered, "电信CN2GT") || !strings.Contains(rendered, "电信163") { t.Fatalf("legacy rendering is not compact: %q", RenderRouteReport(report)) } } @@ -88,6 +89,55 @@ func TestRenderRouteReportPreservesLegacyAddressWidths(t *testing.T) { } } +func TestRenderRouteReportUsesClassicASNLabels(t *testing.T) { + report := RouteReport{Targets: []RouteTargetReport{ + { + Target: RouteTarget{Name: "北京电信v4", Address: "219.141.140.10", IPVersion: "v4", Carrier: "CT"}, + Status: RouteProbeAvailable, + ObservedASNs: []string{"AS6453", "AS4134"}, + Classification: inconclusiveClassification("ct_destination_only", "仅见电信目的网", "only one AS4134 hop"), + }, + { + Target: RouteTarget{Name: "上海电信v4", Address: "202.96.209.133", IPVersion: "v4", Carrier: "CT"}, + Status: RouteProbeAvailable, + ObservedASNs: []string{"AS4809", "AS4134"}, + Classification: RouteClassification{Code: "ct_cn2_mixed", Label: "电信CN2混合 [优质线路]", Confidence: routeConfidenceMixed, Rank: 3}, + }, + { + Target: RouteTarget{Name: "广州联通v4", Address: "210.21.196.6", IPVersion: "v4", Carrier: "CU"}, + Status: RouteProbeAvailable, + ObservedASNs: []string{"AS4837"}, + Classification: inconclusiveClassification("cu_destination_only", "仅见联通目的网", "only one AS4837 hop"), + }, + }} + rendered := regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(RenderRouteReport(report), "") + for _, want := range []string{ + "北京电信v4 219.141.140.10 电信163 [普通线路]", + "上海电信v4 202.96.209.133 电信CN2GT [优质线路] 电信163 [普通线路]", + "广州联通v4 210.21.196.6 联通4837 [普通线路]", + } { + if !strings.Contains(rendered, want) { + t.Fatalf("classic route label missing %q from %q", want, rendered) + } + } + if strings.Contains(rendered, "仅见") || strings.Contains(rendered, "未见") { + t.Fatalf("structured fallback label leaked into classic output: %q", rendered) + } +} + +func TestRenderRouteReportUsesClassicUnknownMessage(t *testing.T) { + report := RouteReport{Targets: []RouteTargetReport{{ + Target: RouteTarget{Name: "北京电信v4", Address: "219.141.140.10", IPVersion: "v4", Carrier: "CT"}, + Status: RouteProbeAvailable, + ObservedASNs: []string{"AS6453"}, + Classification: inconclusiveClassification("ct_unknown", "未见电信骨干", "known carrier ASNs are absent"), + }}} + rendered := regexp.MustCompile(`\x1b\[[0-9;]*m`).ReplaceAllString(RenderRouteReport(report), "") + if !strings.Contains(rendered, "检测不到已知线路的ASN") || strings.Contains(rendered, "未见电信骨干") { + t.Fatalf("unexpected classic unknown route output: %q", rendered) + } +} + func TestRunRouteReportHonorsContext(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() diff --git a/model/model.go b/model/model.go index 5c41d76..8170f7b 100644 --- a/model/model.go +++ b/model/model.go @@ -2,7 +2,7 @@ package model import "time" -const BackTraceVersion = "v0.0.19" +const BackTraceVersion = "v0.0.20" var EnableLoger = false