优化结构
This commit is contained in:
@@ -235,6 +235,62 @@ var routes = map[string]routeValue{
|
||||
"PUT /user/setUserInfo": {group: "系统用户", description: "设置用户信息", audit: true},
|
||||
}
|
||||
|
||||
// compiled is one catalog entry with its path pre-split into segments so that
|
||||
// per-request matching neither parses the map key nor allocates. wildcard marks
|
||||
// a Gin catch-all tail ("/*any"); that segment is dropped from parts because it
|
||||
// absorbs every remaining segment instead of matching one.
|
||||
type compiled struct {
|
||||
parts []string
|
||||
wildcard bool
|
||||
descriptor Descriptor
|
||||
}
|
||||
|
||||
// exact answers a canonical "METHOD /path" in a single map hit. byMethod holds
|
||||
// the same entries pre-split and bucketed by method, so the fallback needed for
|
||||
// path parameters and router prefixes scans one method's routes rather than the
|
||||
// whole catalog, and never re-splits a pattern.
|
||||
var (
|
||||
exact = make(map[string]Descriptor, len(routes))
|
||||
byMethod = make(map[string][]compiled)
|
||||
)
|
||||
|
||||
func init() {
|
||||
for key, value := range routes {
|
||||
descriptor := descriptorFrom(key, value)
|
||||
exact[descriptorKey(descriptor.Method, descriptor.Path)] = descriptor
|
||||
entry := compiled{parts: splitPath(descriptor.Path), descriptor: descriptor}
|
||||
if last := len(entry.parts) - 1; last >= 0 && strings.HasPrefix(entry.parts[last], "*") {
|
||||
entry.parts, entry.wildcard = entry.parts[:last], true
|
||||
}
|
||||
byMethod[descriptor.Method] = append(byMethod[descriptor.Method], entry)
|
||||
}
|
||||
// Suffix matching lets two patterns answer the same URL. Ordering the
|
||||
// buckets most-specific-first makes the winner deterministic instead of
|
||||
// dependent on map iteration order.
|
||||
for method := range byMethod {
|
||||
entries := byMethod[method]
|
||||
sort.SliceStable(entries, func(i, j int) bool {
|
||||
if len(entries[i].parts) != len(entries[j].parts) {
|
||||
return len(entries[i].parts) > len(entries[j].parts)
|
||||
}
|
||||
if entries[i].wildcard != entries[j].wildcard {
|
||||
return entries[j].wildcard
|
||||
}
|
||||
return literalCount(entries[i].parts) > literalCount(entries[j].parts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func literalCount(parts []string) int {
|
||||
count := 0
|
||||
for _, part := range parts {
|
||||
if !strings.HasPrefix(part, ":") {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func splitKey(key string) (string, string) {
|
||||
parts := strings.SplitN(key, " ", 2)
|
||||
if len(parts) != 2 {
|
||||
@@ -267,20 +323,52 @@ func descriptorFrom(key string, value routeValue) Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
func pathMatches(pattern, path string) bool {
|
||||
patternParts := strings.Split(strings.Trim(normalizePath(pattern), "/"), "/")
|
||||
pathParts := strings.Split(strings.Trim(normalizePath(path), "/"), "/")
|
||||
if len(patternParts) == 1 && patternParts[0] == "" {
|
||||
return len(pathParts) == 1 && pathParts[0] == ""
|
||||
func splitPath(path string) []string {
|
||||
return strings.Split(strings.Trim(normalizePath(path), "/"), "/")
|
||||
}
|
||||
|
||||
// Lookup accepts either a canonical Gin route template or an actual URL. A
|
||||
// configured router prefix is tolerated by matching canonical paths by suffix.
|
||||
// Every middleware resolves the descriptor on each request, so the pattern set
|
||||
// is pre-split at init: a miss on the exact key costs one path split and a scan
|
||||
// of the entries declared for that method, not a full catalog walk.
|
||||
func Lookup(method, path string) (Descriptor, bool) {
|
||||
if descriptor, ok := exact[descriptorKey(method, path)]; ok {
|
||||
return descriptor, true
|
||||
}
|
||||
if len(pathParts) < len(patternParts) {
|
||||
parts := splitPath(path)
|
||||
for _, entry := range byMethod[strings.ToUpper(strings.TrimSpace(method))] {
|
||||
if entry.matches(parts) {
|
||||
return entry.descriptor, true
|
||||
}
|
||||
}
|
||||
return Descriptor{}, false
|
||||
}
|
||||
|
||||
// matches compares the pre-split pattern against a pre-split request path.
|
||||
// Canonical patterns match by suffix so a router prefix is tolerated; a
|
||||
// wildcard tail absorbs any number of trailing segments, so its fixed head is
|
||||
// searched at every offset that leaves the wildcard something to consume.
|
||||
func (c compiled) matches(pathParts []string) bool {
|
||||
if len(c.parts) == 0 {
|
||||
return !c.wildcard && len(pathParts) == 1 && pathParts[0] == ""
|
||||
}
|
||||
if len(pathParts) < len(c.parts) {
|
||||
return false
|
||||
}
|
||||
offset := len(pathParts) - len(patternParts)
|
||||
for index, patternPart := range patternParts {
|
||||
if strings.HasPrefix(patternPart, "*") {
|
||||
return true
|
||||
if c.wildcard {
|
||||
for offset := 0; offset+len(c.parts) < len(pathParts); offset++ {
|
||||
if matchAt(c.parts, pathParts, offset) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return matchAt(c.parts, pathParts, len(pathParts)-len(c.parts))
|
||||
}
|
||||
|
||||
func matchAt(patternParts, pathParts []string, offset int) bool {
|
||||
for index, patternPart := range patternParts {
|
||||
actual := pathParts[offset+index]
|
||||
if strings.HasPrefix(patternPart, ":") {
|
||||
if actual == "" {
|
||||
@@ -295,22 +383,6 @@ func pathMatches(pattern, path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Lookup accepts either a canonical Gin route template or an actual URL. A
|
||||
// configured router prefix is tolerated by matching canonical paths by suffix.
|
||||
func Lookup(method, path string) (Descriptor, bool) {
|
||||
key := descriptorKey(method, path)
|
||||
if value, ok := routes[key]; ok {
|
||||
return descriptorFrom(key, value), true
|
||||
}
|
||||
for key, value := range routes {
|
||||
descriptor := descriptorFrom(key, value)
|
||||
if strings.EqualFold(descriptor.Method, method) && pathMatches(descriptor.Path, path) {
|
||||
return descriptor, true
|
||||
}
|
||||
}
|
||||
return Descriptor{}, false
|
||||
}
|
||||
|
||||
func Describe(method, path string) Descriptor {
|
||||
if descriptor, ok := Lookup(method, path); ok {
|
||||
return descriptor
|
||||
|
||||
@@ -28,6 +28,15 @@ func TestRoutePoliciesShareOneDescriptor(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookupMatchesWildcardTail(t *testing.T) {
|
||||
if descriptor, ok := Lookup("GET", "/api/swagger/index.html"); !ok || !descriptor.Public || descriptor.Path != "/swagger/*any" {
|
||||
t.Fatalf("wildcard descriptor = %#v, ok=%v", descriptor, ok)
|
||||
}
|
||||
if _, ok := Lookup("GET", "/api/swagger"); ok {
|
||||
t.Fatal("wildcard route matched without a tail")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEveryRouteHasAPIMetadata(t *testing.T) {
|
||||
for _, descriptor := range Descriptors() {
|
||||
if descriptor.Group == "" || descriptor.Description == "" {
|
||||
|
||||
Reference in New Issue
Block a user