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