diff --git a/logic/controller/Controller.go b/logic/controller/Controller.go index d04d385c..9703f379 100644 --- a/logic/controller/Controller.go +++ b/logic/controller/Controller.go @@ -6,14 +6,13 @@ package controller import ( "blazing/cool" "blazing/logic/service/common" - "fmt" - "strconv" - - "strings" - "bytes" "context" + "fmt" "reflect" + "strconv" + "strings" + "sync" "github.com/gogf/gf/v2/os/glog" "github.com/lunixbochs/struc" @@ -38,7 +37,7 @@ type Controller struct { func ParseCmd[T any](data []byte) T { var result T // 使用struc.Unpack将字节数据解包到result变量中 - struc.Unpack(bytes.NewBuffer(data), &result) + struc.Unpack(bytes.NewReader(data), &result) return result } @@ -55,14 +54,22 @@ func Init(isGame bool) { for i := 0; i < controllerType.NumMethod(); i++ { method := controllerType.Method(i) methodValue := controllerValue.MethodByName(method.Name) + methodType := methodValue.Type() // 获取方法第一个参数的类型(请求结构体) - if methodValue.Type().NumIn() == 0 { + if methodType.NumIn() == 0 { continue } + reqArgType := methodType.In(0) + if reqArgType.Kind() != reflect.Ptr || reqArgType.Elem().Kind() != reflect.Struct { + glog.Warning(context.Background(), "方法首参必须为结构体指针", method.Name, "跳过注册") + continue + } + reqType := reqArgType.Elem() + // 解析请求结构体中的cmd标签 - for _, cmd := range getCmd(methodValue.Type().In(0)) { + for _, cmd := range getCmd(reqType) { if cmd == 0 { // 说明不是有效的注册方法 glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册") continue @@ -80,48 +87,50 @@ func Init(isGame bool) { } // 注册命令处理函数 - if cool.Config.ServerInfo.IsDebug != 0 { fmt.Println("注册方法", cmd, method.Name) - } cmdInfo := cool.Cmd{ Func: methodValue, - Req: methodValue.Type().In(0).Elem(), + Req: reqType, // Res: , // TODO 待实现对不同用户初始化方法以取消全局cmdcache } - // 获取req的实际类型(如ReqLogin) - reqType := reflect.TypeOf(cmdInfo.Req).Elem() // 预编译创建req实例的函数:返回结构体指针 + reqTypeForNew := reqType cmdInfo.NewReqFunc = func() interface{} { - return reflect.New(reqType).Interface() + return reflect.New(reqTypeForNew).Interface() } - _, exists := cool.CmdCache[cmd] - if exists { // 方法已存在 + if _, exists := cool.CmdCache[cmd]; exists { // 方法已存在 panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name)) - - } else { - cool.CmdCache[cmd] = cmdInfo } + cool.CmdCache[cmd] = cmdInfo } } } var targetType = reflect.TypeOf(common.TomeeHeader{}) +var cmdTypeCache sync.Map // 默认返回值(无匹配字段/解析失败时) const defaultCmdValue = 0 +func normalizeStructType(typ reflect.Type) reflect.Type { + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + return typ +} + // getCmd 从结构体类型中提取绑定的cmd指令(递归查找嵌套结构体,支持值/指针类型的TomeeHeader) // 参数 typ: 待解析的结构体类型(支持多层指针) // 返回值: 解析到的cmd切片,无匹配/解析失败时返回[defaultCmdValue] func getCmd(typ reflect.Type) []uint32 { - // 递归解引用所有指针类型(处理 *struct、**struct 等场景) - for typ.Kind() == reflect.Ptr { - typ = typ.Elem() + typ = normalizeStructType(typ) + if cached, ok := cmdTypeCache.Load(typ); ok { + return cached.([]uint32) } // 非结构体类型直接返回默认值 @@ -129,78 +138,90 @@ func getCmd(typ reflect.Type) []uint32 { return []uint32{defaultCmdValue} } + if cmd, ok := findCmd(typ, make(map[reflect.Type]struct{})); ok { + cmdTypeCache.Store(typ, cmd) + return cmd + } + + // 未找到目标字段/所有解析失败,返回默认值 + defaultCmd := []uint32{defaultCmdValue} + cmdTypeCache.Store(typ, defaultCmd) + return defaultCmd +} + +func findCmd(typ reflect.Type, visiting map[reflect.Type]struct{}) ([]uint32, bool) { + typ = normalizeStructType(typ) + if typ.Kind() != reflect.Struct { + return nil, false + } + if _, seen := visiting[typ]; seen { + return nil, false + } + visiting[typ] = struct{}{} + defer delete(visiting, typ) + // 遍历结构体字段,查找TomeeHeader字段并解析cmd for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) // 尝试解析当前字段的cmd标签 - cmdSlice, err := parseCmdTagWithStructField(field) - if err == nil { // 解析成功,直接返回结果 - return cmdSlice + cmdSlice, isHeader, err := parseCmdTagWithStructField(field) + if isHeader && err == nil { // 解析成功,直接返回结果 + return cmdSlice, true } // 递归处理嵌套结构体(值/指针类型) - nestedTyp := field.Type - if nestedTyp.Kind() == reflect.Ptr { - nestedTyp = nestedTyp.Elem() - } + nestedTyp := normalizeStructType(field.Type) if nestedTyp.Kind() == reflect.Struct { // 递归查找,找到有效cmd则立即返回 - if nestedCmd := getCmd(nestedTyp); len(nestedCmd) > 0 && nestedCmd[0] != defaultCmdValue { - return nestedCmd + if nestedCmd, ok := findCmd(nestedTyp, visiting); ok { + return nestedCmd, true } } } - // 未找到目标字段/所有解析失败,返回默认值 - return []uint32{defaultCmdValue} + return nil, false } // parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签 // 参数 field: 结构体字段元信息 -// 返回值: 解析后的cmd切片,非目标类型/解析失败返回错误 -func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, error) { +// 返回值: 解析后的cmd切片,是否为目标类型,解析失败错误 +func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) { // 判断字段类型是否为 TomeeHeader 或 *TomeeHeader - var isTomeeHeader bool - switch { - case field.Type == targetType: // 值类型 - isTomeeHeader = true - case field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType: // 指针类型 - isTomeeHeader = true - default: - isTomeeHeader = false - } - - // 非目标类型返回错误 - if !isTomeeHeader { - return nil, fmt.Errorf("field %s (type: %v) is not common.TomeeHeader or *common.TomeeHeader", - field.Name, field.Type) + if field.Type != targetType && !(field.Type.Kind() == reflect.Ptr && field.Type.Elem() == targetType) { + return nil, false, nil } // 提取cmd标签 cmdStr := field.Tag.Get("cmd") if cmdStr == "" { - return nil, fmt.Errorf("field %s cmd tag is empty", field.Name) + return nil, true, fmt.Errorf("field %s cmd tag is empty", field.Name) } // 高性能解析标签为uint32切片(替代gconv,减少第三方依赖且可控) - parts := strings.Split(cmdStr, "|") - result := make([]uint32, 0, len(parts)) - for idx, s := range parts { + result := make([]uint32, 0, strings.Count(cmdStr, "|")+1) + remain := cmdStr + for idx := 0; ; idx++ { + part, next, found := strings.Cut(remain, "|") // 去除空白字符(兼容标签中意外的空格) - s = strings.TrimSpace(s) + s := strings.TrimSpace(part) if s == "" { - return nil, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx) + return nil, true, fmt.Errorf("field %s cmd tag part %d is empty", field.Name, idx) } // 手动解析uint32,比gconv更可控,避免隐式转换问题 num, err := strconv.ParseUint(s, 10, 32) if err != nil { - return nil, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)", + return nil, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)", field.Name, idx, err, s) } result = append(result, uint32(num)) + + if !found { + break + } + remain = next } - return result, nil + return result, true, nil }