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