DropsBrowse Pastes
Login with GitHub

神奇代码

November 1st, 2025Views: 2(1 unique)Go
package httpserver

import (
	"net/http"
	"sort"
	"zqzd-safe/modules/gateway/pkg/ast"

	iradix "github.com/hashicorp/go-immutable-radix/v2"
)

// RouteIndexer 路由索引器,用于快速查找路由
type RouteIndexer struct {
	// Host索引: Host -> PathIndexer
	hostIndex map[string]*PathIndexer

	// 默认路径索引(没有Host限制的路由)
	defaultPathIndex *PathIndexer

	// 全量路由列表(fallback用)
	allRoutes []*RouteEntry
}

// PathIndexer 路径索引器(使用基数树)
type PathIndexer struct {
	radixTree *iradix.Tree[*[]*RouteEntry]
}

// NewRouteIndexer 创建路由索引器
func NewRouteIndexer() *RouteIndexer {
	return &RouteIndexer{
		hostIndex:        make(map[string]*PathIndexer),
		defaultPathIndex: &PathIndexer{radixTree: iradix.New[*[]*RouteEntry]()},
		allRoutes:        make([]*RouteEntry, 0),
	}
}

// NewPathIndexer 创建路径索引器
func NewPathIndexer() *PathIndexer {
	return &PathIndexer{radixTree: iradix.New[*[]*RouteEntry]()}
}

// IndexRoute 索引一个路由
func (idx *RouteIndexer) IndexRoute(entry *RouteEntry) {
	// 保存到全量列表
	idx.allRoutes = append(idx.allRoutes, entry)

	// 对全量列表按优先级排序
	sortRoutesByPriority(idx.allRoutes)

	// 分析AST,提取可索引的信息
	hosts, pathPrefixes := analyzeAST(entry.AST)

	// 如果有Host限制,添加到Host索引
	if len(hosts) > 0 {
		for _, host := range hosts {
			pathIndexer, exists := idx.hostIndex[host]
			if !exists {
				pathIndexer = NewPathIndexer()
				idx.hostIndex[host] = pathIndexer
			}
			pathIndexer.addRoute(pathPrefixes, entry)
		}
	} else {
		// 没有Host限制,添加到默认索引
		idx.defaultPathIndex.addRoute(pathPrefixes, entry)
	}
}

// addRoute 将路由添加到路径索引中
func (pi *PathIndexer) addRoute(pathPrefixes []string, entry *RouteEntry) {
	// 如果没有路径前缀,使用"/"作为key
	if len(pathPrefixes) == 0 {
		pathPrefixes = []string{"/"}
	}

	for _, prefix := range pathPrefixes {
		key := []byte(prefix)
		existing, _ := pi.radixTree.Get(key)

		var routes []*RouteEntry
		if existing != nil {
			routes = *existing
		}

		routes = append(routes, entry)

		// 按优先级排序(优先级高的在前)
		sortRoutesByPriority(routes)

		tree, _, _ := pi.radixTree.Insert(key, &routes)
		pi.radixTree = tree
	}
}

// FindRoute 查找匹配的路由(优先级优先 + 索引优化)
func (idx *RouteIndexer) FindRoute(r *http.Request) *RouteEntry {
	// 策略:
	// 1. 先用索引找到候选路由中的最高优先级
	// 2. 然后遍历全量列表,只检查优先级 >= 候选最高优先级的路由
	// 3. 这样既保证了优先级正确性,又利用了索引优化

	var maxCandidatePriority int = -1
	var candidateMatch *RouteEntry

	// 第一步:根据Host选择PathIndexer
	var pathIndexer *PathIndexer
	if pi, exists := idx.hostIndex[r.Host]; exists {
		pathIndexer = pi
	} else {
		pathIndexer = idx.defaultPathIndex
	}

	// 第二步:使用基数树查找路径前缀匹配的候选路由
	candidates := pathIndexer.findCandidates(r.URL.Path)

	// 第三步:在候选路由中进行AST求值,找到最高优先级的匹配
	for _, route := range candidates {
		if route.AST.Eval(r) {
			if candidateMatch == nil || route.Config.Priority > maxCandidatePriority {
				candidateMatch = route
				maxCandidatePriority = route.Config.Priority
			}
			// 找到第一个匹配的候选路由后,记录其优先级
			// 候选列表已按优先级排序,所以第一个匹配的就是最高优先级
			break
		}
	}

	// 第四步:遍历全量列表,检查是否有更高优先级的路由
	// 只检查优先级 > 候选最高优先级的路由
	for _, route := range idx.allRoutes {
		// 如果当前路由优先级不大于已找到的候选路由,后续也不会有更高的了
		if candidateMatch != nil && route.Config.Priority <= maxCandidatePriority {
			break
		}

		if route.AST.Eval(r) {
			return route
		}
	}

	// 返回候选路由中找到的匹配(如果有)
	return candidateMatch
}

// findCandidates 查找路径前缀匹配的候选路由
func (pi *PathIndexer) findCandidates(path string) []*RouteEntry {
	// 使用基数树的LongestPrefix查找
	_, value, found := pi.radixTree.Root().LongestPrefix([]byte(path))
	if !found {
		return nil
	}

	return *value
}

// analyzeAST 分析AST,提取可索引的Host和PathPrefix
func analyzeAST(node ast.Node) (hosts []string, pathPrefixes []string) {
	switch n := node.(type) {
	case *ast.FuncCall:
		if n.Name == "Host" && len(n.Args) == 1 {
			hosts = append(hosts, n.Args[0])
		} else if n.Name == "PathPrefix" && len(n.Args) == 1 {
			pathPrefixes = append(pathPrefixes, n.Args[0])
		} else if n.Name == "Path" && len(n.Args) == 1 {
			pathPrefixes = append(pathPrefixes, n.Args[0])
		}

	case *ast.BinaryOp:
		// AND: 取两边的交集(都要满足)
		if n.Op == "&&" {
			leftHosts, leftPaths := analyzeAST(n.Left)
			rightHosts, rightPaths := analyzeAST(n.Right)

			hosts = append(hosts, leftHosts...)
			hosts = append(hosts, rightHosts...)
			pathPrefixes = append(pathPrefixes, leftPaths...)
			pathPrefixes = append(pathPrefixes, rightPaths...)
		}

		// OR: 取两边的并集(满足任一)
		if n.Op == "||" {
			leftHosts, leftPaths := analyzeAST(n.Left)
			rightHosts, rightPaths := analyzeAST(n.Right)

			hosts = append(hosts, leftHosts...)
			hosts = append(hosts, rightHosts...)
			pathPrefixes = append(pathPrefixes, leftPaths...)
			pathPrefixes = append(pathPrefixes, rightPaths...)
		}

	case *ast.UnaryOp:
		// NOT: 无法提取索引信息
		return nil, nil
	}

	return hosts, pathPrefixes
}

// sortRoutesByPriority 按优先级对路由进行排序(优先级高的在前,相同优先级保持添加顺序)
func sortRoutesByPriority(routes []*RouteEntry) {
	sort.SliceStable(routes, func(i, j int) bool {
		// 优先级高的在前(降序)
		return routes[i].Config.Priority > routes[j].Config.Priority
	})
}