refactor(matcher): 提取 LocationType 常量并优化结构体字段布局

- 添加 LocationType 常量定义替代硬编码字符串
- 优化 MatchResult、ExactMatcher、NamedMatcher 结构体字段顺序
- RadixTree.Insert 添加 locationType 参数用于调试追踪
- 更新测试代码适配新接口

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-17 10:11:45 +08:00
parent c418baeb90
commit ca03c121d3
11 changed files with 96 additions and 72 deletions

View File

@ -19,7 +19,7 @@ func BenchmarkRadixTree_Insert(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
for _, p := range paths { for _, p := range paths {
tree.Insert(p, handler, i) tree.Insert(p, handler, i, "prefix")
} }
} }
} }
@ -30,7 +30,7 @@ func BenchmarkRadixTree_Find(b *testing.B) {
paths := []string{"/", "/api", "/api/v1", "/api/v2/users/123"} paths := []string{"/", "/api", "/api/v1", "/api/v2/users/123"}
for i, p := range paths { for i, p := range paths {
tree.Insert(p, handler, i+1) tree.Insert(p, handler, i+1, "prefix")
} }
tree.MarkInitialized() tree.MarkInitialized()

View File

@ -6,8 +6,8 @@ import (
// ExactMatcher Hash Map 精确匹配 // ExactMatcher Hash Map 精确匹配
type ExactMatcher struct { type ExactMatcher struct {
path string
handler fasthttp.RequestHandler handler fasthttp.RequestHandler
path string
priority int priority int
} }
@ -31,6 +31,6 @@ func (m *ExactMatcher) Result() *MatchResult {
Handler: m.handler, Handler: m.handler,
Path: m.path, Path: m.path,
Priority: m.priority, Priority: m.priority,
LocationType: "exact", LocationType: LocationTypeExact,
} }
} }

View File

@ -26,7 +26,7 @@ func TestLocationEngine_NginxPriority(t *testing.T) {
// 测试 ^~ 阻止正则 // 测试 ^~ 阻止正则
result = engine.Match("/api/test.php") result = engine.Match("/api/test.php")
if result.LocationType != "prefix" { if result.LocationType != "prefix_priority" {
t.Errorf("^~ should block regex, got %s", result.LocationType) t.Errorf("^~ should block regex, got %s", result.LocationType)
} }
} }

View File

@ -10,24 +10,13 @@ import (
// LocationEngine 统一匹配引擎 // LocationEngine 统一匹配引擎
type LocationEngine struct { type LocationEngine struct {
// 精确匹配 - Hash Map (O(1))
exactMatchers map[string]*ExactMatcher
// 前缀匹配 - Radix Tree (O(log n))
prefixPriorityTree *RadixTree // ^~ 类型(优先级 2 prefixPriorityTree *RadixTree // ^~ 类型(优先级 2
prefixTree *RadixTree // 普通前缀(优先级 4 prefixTree *RadixTree // 普通前缀(优先级 4
exactMatchers map[string]*ExactMatcher
// 正则匹配 - Linear Scan按配置顺序
regexMatchers []*RegexMatcher
// 命名 location - Hash Map
namedMatchers map[string]*NamedMatcher namedMatchers map[string]*NamedMatcher
// 初始化标记
initialized bool
// 冲突检测
registeredPaths map[string]string registeredPaths map[string]string
regexMatchers []*RegexMatcher
initialized bool
} }
// NewLocationEngine 创建新引擎 // NewLocationEngine 创建新引擎
@ -67,7 +56,7 @@ func (e *LocationEngine) AddPrefixPriority(path string, handler fasthttp.Request
return err return err
} }
return e.prefixPriorityTree.Insert(path, handler, 2) return e.prefixPriorityTree.Insert(path, handler, 2, "prefix_priority")
} }
// AddRegex 添加正则匹配 location // AddRegex 添加正则匹配 location
@ -95,7 +84,7 @@ func (e *LocationEngine) AddPrefix(path string, handler fasthttp.RequestHandler)
return err return err
} }
return e.prefixTree.Insert(path, handler, 4) return e.prefixTree.Insert(path, handler, 4, "prefix")
} }
// AddNamed 添加命名 location // AddNamed 添加命名 location

View File

@ -2,15 +2,23 @@ package matcher
import "github.com/valyala/fasthttp" import "github.com/valyala/fasthttp"
// LocationType 常量定义
const (
LocationTypeExact = "exact"
LocationTypePrefix = "prefix"
LocationTypePrefixPriority = "prefix_priority"
LocationTypeRegex = "regex"
LocationTypeRegexCaseless = "regex_caseless"
LocationTypeNamed = "named"
)
// MatchResult 匹配结果 // MatchResult 匹配结果
type MatchResult struct { type MatchResult struct {
Captures map[string]string // 正则捕获组
Handler fasthttp.RequestHandler Handler fasthttp.RequestHandler
LocationType string
Path string Path string
Priority int Priority int
LocationType string
// 正则捕获组
Captures map[string]string
} }
// Matcher 接口 // Matcher 接口

View File

@ -4,8 +4,8 @@ import "github.com/valyala/fasthttp"
// NamedMatcher @命名 location // NamedMatcher @命名 location
type NamedMatcher struct { type NamedMatcher struct {
name string
handler fasthttp.RequestHandler handler fasthttp.RequestHandler
name string
} }
// NewNamedMatcher 创建命名匹配器 // NewNamedMatcher 创建命名匹配器
@ -17,7 +17,7 @@ func NewNamedMatcher(name string, handler fasthttp.RequestHandler) *NamedMatcher
} }
// Match 检查命名是否匹配(命名 location 不使用 path 匹配) // Match 检查命名是否匹配(命名 location 不使用 path 匹配)
func (m *NamedMatcher) Match(path string) bool { func (m *NamedMatcher) Match(_ string) bool {
return false return false
} }
@ -27,7 +27,7 @@ func (m *NamedMatcher) Result() *MatchResult {
Handler: m.handler, Handler: m.handler,
Path: "@" + m.name, Path: "@" + m.name,
Priority: 0, Priority: 0,
LocationType: "named", LocationType: LocationTypeNamed,
} }
} }

View File

@ -18,7 +18,7 @@ func NewPrefixMatcher() *PrefixMatcher {
// AddPath 添加路径 // AddPath 添加路径
func (pm *PrefixMatcher) AddPath(path string, handler fasthttp.RequestHandler) error { func (pm *PrefixMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
return pm.tree.Insert(path, handler, pm.priority) return pm.tree.Insert(path, handler, pm.priority, "prefix")
} }
// Match 前缀匹配,返回最长前缀匹配结果 // Match 前缀匹配,返回最长前缀匹配结果

View File

@ -18,7 +18,7 @@ func NewPrefixPriorityMatcher() *PrefixPriorityMatcher {
// AddPath 添加路径 // AddPath 添加路径
func (ppm *PrefixPriorityMatcher) AddPath(path string, handler fasthttp.RequestHandler) error { func (ppm *PrefixPriorityMatcher) AddPath(path string, handler fasthttp.RequestHandler) error {
return ppm.tree.Insert(path, handler, ppm.priority) return ppm.tree.Insert(path, handler, ppm.priority, "prefix_priority")
} }
// Match 前缀优先匹配,返回最长前缀匹配结果 // Match 前缀优先匹配,返回最长前缀匹配结果

View File

@ -9,11 +9,12 @@ import (
// RadixNode Radix Tree 节点 // RadixNode Radix Tree 节点
type RadixNode struct { type RadixNode struct {
prefix string
handler fasthttp.RequestHandler
children []*RadixNode children []*RadixNode
isLeaf bool handler fasthttp.RequestHandler
priority int priority int
isLeaf bool
prefix string
locationType string // exact/prefix/prefix_priority
} }
// RadixTree 前缀匹配 Radix Tree // RadixTree 前缀匹配 Radix Tree
@ -30,21 +31,22 @@ func NewRadixTree() *RadixTree {
} }
// Insert 插入路径到 Radix Treestartup-only // Insert 插入路径到 Radix Treestartup-only
func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int) error { func (t *RadixTree) Insert(path string, handler fasthttp.RequestHandler, priority int, locationType string) error {
if t.initialized { if t.initialized {
return errors.New("RadixTree already initialized") return errors.New("RadixTree already initialized")
} }
return t.insertNode(nil, t.root, path, handler, priority) return t.insertNode(nil, t.root, path, handler, priority, locationType)
} }
// insertNode 完整路径分割插入算法 // insertNode 完整路径分割插入算法
func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string, handler fasthttp.RequestHandler, priority int) error { func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string, handler fasthttp.RequestHandler, priority int, locationType string) error {
// Case 1: 空节点(根节点),直接设置 // Case 1: 空节点(根节点),直接设置
if node.prefix == "" && len(node.children) == 0 && node.handler == nil { if node.prefix == "" && len(node.children) == 0 && node.handler == nil {
if path == "" { if path == "" {
node.handler = handler node.handler = handler
node.priority = priority node.priority = priority
node.isLeaf = true node.isLeaf = true
node.locationType = locationType
return nil return nil
} }
// 创建新子节点 // 创建新子节点
@ -53,6 +55,7 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
handler: handler, handler: handler,
isLeaf: true, isLeaf: true,
priority: priority, priority: priority,
locationType: locationType,
} }
node.children = append(node.children, newNode) node.children = append(node.children, newNode)
return nil return nil
@ -77,13 +80,14 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
node.handler = handler node.handler = handler
node.priority = priority node.priority = priority
node.isLeaf = true node.isLeaf = true
node.locationType = locationType
return nil return nil
} }
// 搜索匹配剩余路径的子节点 // 搜索匹配剩余路径的子节点
for _, child := range node.children { for _, child := range node.children {
if strings.HasPrefix(remaining, child.prefix) { if strings.HasPrefix(remaining, child.prefix) {
return t.insertNode(node, child, remaining, handler, priority) return t.insertNode(node, child, remaining, handler, priority, locationType)
} }
} }
@ -93,6 +97,7 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
handler: handler, handler: handler,
isLeaf: true, isLeaf: true,
priority: priority, priority: priority,
locationType: locationType,
} }
node.children = append(node.children, newNode) node.children = append(node.children, newNode)
return nil return nil
@ -114,6 +119,7 @@ func (t *RadixTree) insertNode(parent *RadixNode, node *RadixNode, path string,
handler: handler, handler: handler,
isLeaf: true, isLeaf: true,
priority: priority, priority: priority,
locationType: locationType,
} }
// 将原节点和新节点作为 splitNode 的子节点 // 将原节点和新节点作为 splitNode 的子节点
@ -159,7 +165,7 @@ func (t *RadixTree) searchLongest(node *RadixNode, path string, bestMatch *Match
Handler: node.handler, Handler: node.handler,
Path: node.prefix, Path: node.prefix,
Priority: node.priority, Priority: node.priority,
LocationType: "prefix", LocationType: node.locationType,
} }
// nil-safe 优先级比较 + 长度比较 // nil-safe 优先级比较 + 长度比较

View File

@ -11,7 +11,7 @@ func TestRadixTree_Insert_EmptyNode(t *testing.T) {
tree := NewRadixTree() tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) {} handler := func(ctx *fasthttp.RequestCtx) {}
err := tree.Insert("/api", handler, 1) err := tree.Insert("/api", handler, 1, "prefix")
if err != nil { if err != nil {
t.Fatalf("insert failed: %v", err) t.Fatalf("insert failed: %v", err)
} }
@ -31,8 +31,8 @@ func TestRadixTree_Insert_CommonPrefix(t *testing.T) {
handler1 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("1") } handler1 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("1") }
handler2 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("2") } handler2 := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("2") }
tree.Insert("/api", handler1, 1) tree.Insert("/api", handler1, 1, "prefix")
tree.Insert("/api/users", handler2, 2) tree.Insert("/api/users", handler2, 2, "prefix")
result := tree.FindLongestPrefix("/api/users") result := tree.FindLongestPrefix("/api/users")
if result == nil { if result == nil {
@ -53,8 +53,8 @@ func TestRadixTree_Insert_NodeSplit(t *testing.T) {
handler1 := func(ctx *fasthttp.RequestCtx) {} handler1 := func(ctx *fasthttp.RequestCtx) {}
handler2 := func(ctx *fasthttp.RequestCtx) {} handler2 := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/abc", handler1, 1) tree.Insert("/abc", handler1, 1, "prefix")
tree.Insert("/abx", handler2, 2) tree.Insert("/abx", handler2, 2, "prefix")
// 应该正确分割 /ab 公共前缀 // 应该正确分割 /ab 公共前缀
result := tree.FindLongestPrefix("/abc") result := tree.FindLongestPrefix("/abc")
@ -67,9 +67,9 @@ func TestRadixTree_FindLongestPrefix(t *testing.T) {
tree := NewRadixTree() tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) {} handler := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/", handler, 1) tree.Insert("/", handler, 1, "prefix")
tree.Insert("/api", handler, 2) tree.Insert("/api", handler, 2, "prefix")
tree.Insert("/api/v1", handler, 3) tree.Insert("/api/v1", handler, 3, "prefix")
// "/" has priority 1 (wins), "/api" has 2, "/api/v1" has 3 // "/" has priority 1 (wins), "/api" has 2, "/api/v1" has 3
// Lower number = higher priority // Lower number = higher priority
@ -86,10 +86,10 @@ func TestRadixTree_Insert_AfterInitialized(t *testing.T) {
tree := NewRadixTree() tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) {} handler := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/api", handler, 1) tree.Insert("/api", handler, 1, "prefix")
tree.MarkInitialized() tree.MarkInitialized()
err := tree.Insert("/api/v2", handler, 2) err := tree.Insert("/api/v2", handler, 2, "prefix")
if err == nil { if err == nil {
t.Error("should fail when inserting after initialized") t.Error("should fail when inserting after initialized")
} }
@ -99,8 +99,8 @@ func TestRadixTree_Insert_DuplicatePath(t *testing.T) {
tree := NewRadixTree() tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) {} handler := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/api", handler, 1) tree.Insert("/api", handler, 1, "prefix")
err := tree.Insert("/api", handler, 2) err := tree.Insert("/api", handler, 2, "prefix")
if err == nil { if err == nil {
t.Error("should fail on duplicate path") t.Error("should fail on duplicate path")
} }
@ -110,7 +110,7 @@ func TestRadixTree_FindLongestPrefix_NoMatch(t *testing.T) {
tree := NewRadixTree() tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) {} handler := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/api", handler, 1) tree.Insert("/api", handler, 1, "prefix")
result := tree.FindLongestPrefix("/other") result := tree.FindLongestPrefix("/other")
if result != nil { if result != nil {
@ -123,8 +123,8 @@ func TestRadixTree_PriorityComparison(t *testing.T) {
h1 := func(ctx *fasthttp.RequestCtx) {} h1 := func(ctx *fasthttp.RequestCtx) {}
h2 := func(ctx *fasthttp.RequestCtx) {} h2 := func(ctx *fasthttp.RequestCtx) {}
tree.Insert("/api", h1, 5) tree.Insert("/api", h1, 5, "prefix")
tree.Insert("/api/users", h2, 2) tree.Insert("/api/users", h2, 2, "prefix")
// Lower priority number wins // Lower priority number wins
result := tree.FindLongestPrefix("/api/users") result := tree.FindLongestPrefix("/api/users")
@ -135,3 +135,24 @@ func TestRadixTree_PriorityComparison(t *testing.T) {
t.Errorf("expected priority 2, got %d", result.Priority) t.Errorf("expected priority 2, got %d", result.Priority)
} }
} }
func TestRadixTree_Insert_ExactMatch(t *testing.T) {
// Case 3: path 完全匹配节点前缀,设置 handler
tree := NewRadixTree()
handler := func(ctx *fasthttp.RequestCtx) { ctx.SetBodyString("exact") }
// 先插入父路径
tree.Insert("/api", handler, 1, "prefix")
// 再次插入相同路径(应该报错重复)
err := tree.Insert("/api", handler, 2, "prefix")
if err == nil {
t.Error("should return error for duplicate path")
}
// 验证原 handler 未被覆盖
result := tree.FindLongestPrefix("/api")
if result == nil || result.Priority != 1 {
t.Errorf("original handler should not be overwritten, got priority %d", result.Priority)
}
}

View File

@ -10,9 +10,9 @@ import (
type RegexMatcher struct { type RegexMatcher struct {
pattern *regexp.Regexp pattern *regexp.Regexp
handler fasthttp.RequestHandler handler fasthttp.RequestHandler
captures map[string]string
priority int priority int
caseInsensitive bool caseInsensitive bool
captures map[string]string
} }
// NewRegexMatcher 创建正则匹配器 // NewRegexMatcher 创建正则匹配器
@ -47,9 +47,9 @@ func (m *RegexMatcher) Match(path string) bool {
// Result 返回匹配结果 // Result 返回匹配结果
func (m *RegexMatcher) Result() *MatchResult { func (m *RegexMatcher) Result() *MatchResult {
locType := "regex" locType := LocationTypeRegex
if m.caseInsensitive { if m.caseInsensitive {
locType = "regex_caseless" locType = LocationTypeRegexCaseless
} }
return &MatchResult{ return &MatchResult{
Handler: m.handler, Handler: m.handler,