feat(http): 实现动态路由生成和处理机制

- 添加 HandlerEntry 和 Route 结构体用于描述路由条目和路由规则
- 实现 generateRoutes 函数自动生成路由列表- 支持通过反射获取处理对象的方法并映射为路由
- 根据方法名前缀自动识别 HTTP 方法(GET、POST 等)
- 忽略匿名字段中提升的方法以避免重复路由- 为 GET 请求动态生成带参数的正则表达式路径匹配规则
- 支持路径前缀和动作名称的自动解析与拼接
- 提供 matchesPromotedMethodName 和 getAnonymousFieldMethods 辅助函数优化方法识别逻辑
This commit is contained in:
2025-11-04 22:00:18 +08:00
parent 6cbde9ea83
commit c598c5d0f3
2 changed files with 164 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
package handing
import (
"net/http"
"reflect"
"regexp"
"strings"
)
// HandlerEntry 路由条目
type HandlerEntry struct {
// 路由前缀
Prefix string
// 路由处理对象
Handler interface{}
}
type Route struct {
// HTTP 方法
httpMethod string
// 路由前缀
prefix string
// 路由处理对象名称
handlerName string
actionName string
// 路由的正则表达式
expression regexp.Regexp
// 路由方法
handlerMethod reflect.Method
}
var httpMethods = []string{
http.MethodGet,
http.MethodPost,
http.MethodDelete,
http.MethodPut,
}
func generateRoutes(entries ...HandlerEntry) []Route {
routes := make([]Route, 0, 10)
for _, entry := range entries {
// 获取处理对象的类型
handlerType := reflect.TypeOf(entry.Handler)
// 获取处理对象中字段的所有方法
promotedMethods := getAnonymousFieldMethods(handlerType)
// 遍历处理对象的所有方法
for i := 0; i < handlerType.NumMethod(); i++ {
method := handlerType.Method(i)
methodName := strings.ToUpper(method.Name)
// 遍历 http 方法类型
for _, httpMethod := range httpMethods {
if strings.Index(methodName, httpMethod) == 0 {
// 假如方法为字段的方法,则忽略
if matchesPromotedMethodName(method, promotedMethods) {
continue
}
// 建立路由
route := Route{
// HTTP 方法
httpMethod: httpMethod,
// 路由前缀
prefix: entry.Prefix,
// 路由处理对象名称
handlerName: strings.Split(handlerType.Name(), "Handler")[0],
// 动作名称
actionName: strings.Split(methodName, httpMethod)[1],
// 调用方法
handlerMethod: method,
}
// 生成正则表达式地址匹配
generateRegularExpression(entry.Prefix, &route)
routes = append(routes, route)
}
}
}
}
return routes
}
func matchesPromotedMethodName(method reflect.Method, methods []reflect.Method) bool {
for _, m := range methods {
if m.Name == method.Name {
return true
}
}
return false
}
func getAnonymousFieldMethods(target reflect.Type) []reflect.Method {
var methods []reflect.Method
// 获取处理对象的所有字段
for i := 0; i < target.NumField(); i++ {
field := target.Field(i)
if field.Anonymous && field.IsExported() {
// 获取字段的所有方法
for j := 0; j < field.Type.NumField(); j++ {
method := field.Type.Method(j)
if method.IsExported() {
methods = append(methods, method)
}
}
}
}
return methods
}
func generateRegularExpression(prefix string, route *Route) {
if prefix != "" && !strings.HasPrefix(prefix, "/") {
prefix += "/"
}
pattern := "(?i)" + "/" + prefix + route.actionName
if route.httpMethod == http.MethodGet {
for i := 1; i < route.handlerMethod.Type.NumIn(); i++ {
if route.handlerMethod.Type.In(i).Kind() == reflect.Int {
pattern += "/([0-9]*)"
} else {
pattern += "/([a-zA-Z0-9_]*)"
}
}
}
pattern = "^" + pattern + "[/]?$"
route.expression = *regexp.MustCompile(pattern)
}

View File

@@ -0,0 +1,41 @@
package placeholder
import (
"fmt"
"platform/logging"
)
var names = []string{"Alice", "Bob", "charlie", "Dora"}
type NameHandler struct {
logging.Logger
}
type NewName struct {
Name string
InsertAtStart bool
}
func (n NameHandler) GetName(i int) string {
n.Logger.Debugf("GetName method invoked with argument: %v", i)
if i < len(names) {
return fmt.Sprintf("Name #%v : %v", i, names[i])
} else {
return fmt.Sprintf("Index out of bounds")
}
}
func (n NameHandler) GetNames() string {
n.Logger.Debug("GetNames method invoked")
return fmt.Sprintf("Names: %v", names)
}
func (n NameHandler) PostName(new NewName) string {
n.Logger.Debugf("PostName method invoked with argument: %v", new)
if new.InsertAtStart {
names = append([]string{new.Name}, names...)
} else {
names = append(names, new.Name)
}
return fmt.Sprintf("Names: %v", names)
}