diff --git a/common/cool/global.go b/common/cool/global.go index 929027b10..95affc3ae 100644 --- a/common/cool/global.go +++ b/common/cool/global.go @@ -22,8 +22,14 @@ var ctx = context.TODO() type Cmd struct { Func reflect.Value //方法函数 Req reflect.Type //请求体 + // HeaderFieldIndex 是请求结构体中 TomeeHeader 字段的索引路径。 + HeaderFieldIndex []int + // UseConn 标记第二个参数是否为 gnet.Conn。 + UseConn bool // 新增:预缓存的req创建函数(返回结构体指针) NewReqFunc func() interface{} + // NewReqValue 返回请求结构体指针的 reflect.Value,避免重复构造类型信息。 + NewReqValue func() reflect.Value //Res reflect.Value //返回体 } diff --git a/logic/controller/Controller.go b/logic/controller/Controller.go index 2c358bbdb..697686d1f 100644 --- a/logic/controller/Controller.go +++ b/logic/controller/Controller.go @@ -16,6 +16,7 @@ import ( "github.com/gogf/gf/v2/os/glog" "github.com/lunixbochs/struc" + "github.com/panjf2000/gnet/v2" ) // Maincontroller 是控制器层共享变量。 @@ -45,19 +46,14 @@ func ParseCmd[T any](data []byte) T { // Init 初始化控制器,注册所有cmd处理方法 // 参数 isGame: 标识是否为游戏服务器(true)或登录服务器(false) func Init(isGame bool) { - // 获取控制器实例的反射值 controllerValue := reflect.ValueOf(Maincontroller) - - // 获取控制器类型 controllerType := controllerValue.Type() - // 遍历控制器的所有方法 for i := 0; i < controllerType.NumMethod(); i++ { method := controllerType.Method(i) - methodValue := controllerValue.MethodByName(method.Name) + methodValue := controllerValue.Method(i) methodType := methodValue.Type() - // 获取方法第一个参数的类型(请求结构体) if methodType.NumIn() == 0 { continue } @@ -68,43 +64,46 @@ func Init(isGame bool) { continue } reqType := reqArgType.Elem() + binding := getCmdBinding(reqType) - // 解析请求结构体中的cmd标签 - for _, cmd := range getCmd(reqType) { - if cmd == 0 { // 说明不是有效的注册方法 + for _, cmd := range binding.cmds { + if cmd == 0 { glog.Warning(context.Background(), "方法参数必须包含CMD参数", method.Name, "跳过注册") continue } - // 根据服务器类型过滤cmd - // 登录服务器只处理小于1000的cmd + if methodType.NumIn() != 2 { + glog.Warning(context.Background(), "方法参数数量必须为2", method.Name, "跳过注册") + continue + } + if !isGame && cmd > 1000 { continue } - // 游戏服务器只处理大于等于1000的cmd if isGame && cmd < 1000 { continue } - // 注册命令处理函数 if cool.Config.ServerInfo.IsDebug != 0 { fmt.Println("注册方法", cmd, method.Name) } - cmdInfo := cool.Cmd{ - Func: methodValue, - Req: reqType, - - // Res: , // TODO 待实现对不同用户初始化方法以取消全局cmdcache - } - // 预编译创建req实例的函数:返回结构体指针 reqTypeForNew := reqType - cmdInfo.NewReqFunc = func() interface{} { - return reflect.New(reqTypeForNew).Interface() + cmdInfo := cool.Cmd{ + Func: methodValue, + Req: reqType, + HeaderFieldIndex: append([]int(nil), binding.headerFieldIndex...), + UseConn: methodType.In(1) == connType, + NewReqFunc: func() interface{} { + return reflect.New(reqTypeForNew).Interface() + }, + NewReqValue: func() reflect.Value { + return reflect.New(reqTypeForNew) + }, } - if _, exists := cool.CmdCache[cmd]; exists { // 方法已存在 + if _, exists := cool.CmdCache[cmd]; exists { panic(fmt.Sprintf("命令处理方法已存在,跳过注册 %d %s", cmd, method.Name)) } cool.CmdCache[cmd] = cmdInfo @@ -112,12 +111,20 @@ func Init(isGame bool) { } } -var targetType = reflect.TypeOf(common.TomeeHeader{}) -var cmdTypeCache sync.Map +var ( + targetType = reflect.TypeOf(common.TomeeHeader{}) + connType = reflect.TypeOf((*gnet.Conn)(nil)).Elem() + cmdTypeCache sync.Map +) // 默认返回值(无匹配字段/解析失败时) const defaultCmdValue = 0 +type cmdBinding struct { + cmds []uint32 + headerFieldIndex []int +} + func normalizeStructType(typ reflect.Type) reflect.Type { for typ.Kind() == reflect.Ptr { typ = typ.Elem() @@ -125,92 +132,93 @@ func normalizeStructType(typ reflect.Type) reflect.Type { return typ } -// getCmd 从结构体类型中提取绑定的cmd指令(递归查找嵌套结构体,支持值/指针类型的TomeeHeader) -// 参数 typ: 待解析的结构体类型(支持多层指针) -// 返回值: 解析到的cmd切片,无匹配/解析失败时返回[defaultCmdValue] -func getCmd(typ reflect.Type) []uint32 { +// getCmdBinding 从结构体类型中提取绑定的cmd指令和头字段位置。 +func getCmdBinding(typ reflect.Type) cmdBinding { typ = normalizeStructType(typ) if cached, ok := cmdTypeCache.Load(typ); ok { - return cached.([]uint32) + return cached.(cmdBinding) } - // 非结构体类型直接返回默认值 if typ.Kind() != reflect.Struct { - return []uint32{defaultCmdValue} + binding := cmdBinding{cmds: []uint32{defaultCmdValue}} + cmdTypeCache.Store(typ, binding) + return binding } - if cmd, ok := findCmd(typ, make(map[reflect.Type]struct{})); ok { - cmdTypeCache.Store(typ, cmd) - return cmd + if binding, ok := findCmdBinding(typ, make(map[reflect.Type]struct{})); ok { + cmdTypeCache.Store(typ, binding) + return binding } - // 未找到目标字段/所有解析失败,返回默认值 - defaultCmd := []uint32{defaultCmdValue} - cmdTypeCache.Store(typ, defaultCmd) - return defaultCmd + binding := cmdBinding{cmds: []uint32{defaultCmdValue}} + cmdTypeCache.Store(typ, binding) + return binding } -func findCmd(typ reflect.Type, visiting map[reflect.Type]struct{}) ([]uint32, bool) { +func findCmdBinding(typ reflect.Type, visiting map[reflect.Type]struct{}) (cmdBinding, bool) { typ = normalizeStructType(typ) if typ.Kind() != reflect.Struct { - return nil, false + return cmdBinding{}, false } if _, seen := visiting[typ]; seen { - return nil, false + return cmdBinding{}, false } visiting[typ] = struct{}{} defer delete(visiting, typ) - // 遍历结构体字段,查找TomeeHeader字段并解析cmd for i := 0; i < typ.NumField(); i++ { field := typ.Field(i) - // 尝试解析当前字段的cmd标签 cmdSlice, isHeader, err := parseCmdTagWithStructField(field) - if isHeader && err == nil { // 解析成功,直接返回结果 - return cmdSlice, true + if isHeader && err == nil { + return cmdBinding{ + cmds: cmdSlice, + headerFieldIndex: append([]int(nil), field.Index...), + }, true } - // 递归处理嵌套结构体(值/指针类型) nestedTyp := normalizeStructType(field.Type) - if nestedTyp.Kind() == reflect.Struct { - // 递归查找,找到有效cmd则立即返回 - if nestedCmd, ok := findCmd(nestedTyp, visiting); ok { - return nestedCmd, true - } + if nestedTyp.Kind() != reflect.Struct { + continue } + + nestedBinding, ok := findCmdBinding(nestedTyp, visiting) + if !ok { + continue + } + + fieldIndex := make([]int, 0, len(field.Index)+len(nestedBinding.headerFieldIndex)) + fieldIndex = append(fieldIndex, field.Index...) + fieldIndex = append(fieldIndex, nestedBinding.headerFieldIndex...) + nestedBinding.headerFieldIndex = fieldIndex + return nestedBinding, true } - return nil, false + return cmdBinding{}, false } // parseCmdTagWithStructField 校验字段是否为TomeeHeader(值/指针)并解析cmd标签 // 参数 field: 结构体字段元信息 // 返回值: 解析后的cmd切片,是否为目标类型,解析失败错误 func parseCmdTagWithStructField(field reflect.StructField) ([]uint32, bool, error) { - // 判断字段类型是否为 TomeeHeader 或 *TomeeHeader 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, true, fmt.Errorf("field %s cmd tag is empty", field.Name) } - // 高性能解析标签为uint32切片(替代gconv,减少第三方依赖且可控) result := make([]uint32, 0, strings.Count(cmdStr, "|")+1) remain := cmdStr for idx := 0; ; idx++ { part, next, found := strings.Cut(remain, "|") - // 去除空白字符(兼容标签中意外的空格) s := strings.TrimSpace(part) if s == "" { 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, true, fmt.Errorf("field %s cmd tag part %d parse error: %v (value: %s)", diff --git a/logic/service/player/pack.go b/logic/service/player/pack.go index 6ccc96070..661cddeec 100644 --- a/logic/service/player/pack.go +++ b/logic/service/player/pack.go @@ -53,6 +53,44 @@ func getUnderlyingValue(val reflect.Value) (reflect.Value, error) { } } +func setFieldByIndex(root reflect.Value, index []int, value reflect.Value) bool { + current := root + for pos, idx := range index { + if current.Kind() == reflect.Ptr { + if current.IsNil() { + current.Set(reflect.New(current.Type().Elem())) + } + current = current.Elem() + } + + if current.Kind() != reflect.Struct || idx < 0 || idx >= current.NumField() { + return false + } + + field := current.Field(idx) + if pos == len(index)-1 { + if !field.CanSet() { + return false + } + if value.Type().AssignableTo(field.Type()) { + field.Set(value) + return true + } + if field.Kind() == reflect.Ptr && value.Type().AssignableTo(field.Type().Elem()) { + ptr := reflect.New(field.Type().Elem()) + ptr.Elem().Set(value) + field.Set(ptr) + return true + } + return false + } + + current = field + } + + return false +} + // XORDecryptU 原地执行异或解密,避免额外分配和拷贝。 func XORDecryptU(encryptedData []byte, key uint32) []byte { if len(encryptedData) == 0 { @@ -193,45 +231,38 @@ func (h *ClientData) OnEvent(data common.TomeeHeader) { return //TODO 待实现cmd未注册 } - params := []reflect.Value{} + var ptrValue reflect.Value + if cmdlister.NewReqValue != nil { + ptrValue = cmdlister.NewReqValue() + } else { + ptrValue = reflect.New(cmdlister.Req) + } - //funct := cmdlister.Type().NumIn() - - // 如果需要可设置的变量(用于修改值),创建指针并解引用 - ptrValue := reflect.New(cmdlister.Req) - - // fmt.Println(tt1) if data.Res != nil { - tt1 := ptrValue.Elem().Addr().Interface() - err := struc.Unpack(bytes.NewBuffer(data.Res), tt1) + err := struc.Unpack(bytes.NewBuffer(data.Res), ptrValue.Interface()) if err != nil { - cool.Logger.Error(context.Background(), data.UserID, data.CMD, "解包失败,", err, hex.EncodeToString(data.Res)) - //fmt.Println(data.UserID, data.CMD, "解包失败,", hex.EncodeToString(data.Data)) data.Result = uint32(errorcode.ErrorCodes.ErrSystemProcessingError) h.SendPack(data.Pack(nil)) return } } - ptrValue1 := ptrValue.Elem().Addr() - // 设置 Name 字段 - nameField := ptrValue.Elem().Field(0) //首个为header - nameField.Set(reflect.ValueOf(data)) - - if data.CMD > 1001 { //if cmdlister.Type().In(1) == reflect.TypeOf(&Player{}) { - //t := GetPlayer(c, data.UserID) - - // fmt.Println(data.CMD, "接收 变量的地址 ", &t.Info, t.Info.UserID) - - params = append(params, ptrValue1, reflect.ValueOf(h.Player)) - } else { - - params = append(params, ptrValue1, reflect.ValueOf(h.Conn)) + if !setFieldByIndex(ptrValue.Elem(), cmdlister.HeaderFieldIndex, reflect.ValueOf(data)) { + cool.Logger.Warning(context.Background(), data.UserID, data.CMD, "设置请求头失败") + return } - ret := cmdlister.Func.Call(params) + var params [2]reflect.Value + params[0] = ptrValue + if cmdlister.UseConn { + params[1] = reflect.ValueOf(h.Conn) + } else { + params[1] = reflect.ValueOf(h.Player) + } + + ret := cmdlister.Func.Call(params[:]) if len(ret) <= 0 { //如果判断没有参数,那就说明这个包没有返回参数 return