refactor(logic): 重构逻辑模块

- 移除了 player.go 中的 SendPackBytes 方法
- 将 TomeeHeader 中的 CMDID 改为 CMD,类型从 uint32 改为 EnumCommandID
- 删除了 controller 文件夹下的 service.go 和 login.go
- 新增了 login 文件夹下的 PlayerLoginService.go
- 优化了 main.go 中的 Start 函数,使用 controller.Recv 作为回调
This commit is contained in:
2025-06-26 23:20:11 +08:00
parent dce68c7094
commit feabc7153a
15 changed files with 375 additions and 332 deletions

View File

@@ -0,0 +1,167 @@
package controller
import (
"blazing/common/core"
"blazing/common/socket/cmd"
"blazing/common/socket/handler"
"blazing/logic/service"
"bytes"
"context"
"fmt"
"reflect"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/util/gconv"
"github.com/lunixbochs/struc"
"github.com/panjf2000/gnet/v2"
)
var maincontroller = NewController() //注入service
// 分发cmd逻辑实现Controller
type Controller struct {
}
func NewController() *Controller {
return &Controller{}
}
func parseCmd[T any](a T, data []byte) T {
// := info.NewLoginSidInfo()
struc.Unpack(bytes.NewBuffer(data), &a)
return a
//fmt.Println(pinfo)
//login.OnData_1001(pinfo, player)
//fmt.Println(data)
}
func init() { //默认初始化扫描
// 获取对象的反射值和类型
value := reflect.ValueOf(maincontroller)
// 获取类型
typ := value.Type()
for i := 0; i < typ.NumMethod(); i++ {
method := typ.Method(i)
// TODO 接收者 + 2个参数 一个是类型,一个是player
//method.Type.In(1) == reflect.TypeOf([]byte{}
// 获取方法值
methodValue := value.MethodByName(method.Name)
methodValue.Type().NumIn()
var func_cmd uint32
//params := []reflect.Value{}
//fmt.Println(methodValue.Type().NumIn())
for i := 0; i < methodValue.Type().NumIn(); i++ {
ttt := methodValue.Type().In(i)
func_cmd = gconv.Uint32(getcmd(ttt))
}
if !cmd.Isexist(cmd.CommandID, cmd.EnumCommandID(func_cmd)) {
fmt.Println("ID方法存在cmd未注册", func_cmd)
}
// fmt.Println(methodValue.Interface().(func(gnet.Conn, handler.TomeeHeader)))
_, ok := core.Maincmdcache.LoadOrStore(func_cmd, methodValue) //TODO 待实现对不同用户初始化方法以取消全局cmdcache
if ok { //方法已存在init
glog.Error(context.Background(), "方法已存在init,不会初始化后面的方法", func_cmd)
}
}
}
func getcmd(t reflect.Type) cmd.EnumCommandID {
// 处理指针类型
if t.Kind() == reflect.Ptr {
t = t.Elem() // 获取指针指向的类型
}
// 确保是结构体
if t.Kind() != reflect.Struct {
fmt.Println("非结构体类型")
return 0
}
// 遍历结构体字段
// fmt.Printf("结构体 %s 的字段信息:\n", t.Name())
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// fmt.Printf("- 字段名: %s\n", field.Name)
if field.Type == reflect.TypeOf(handler.TomeeHeader{}) {
// fmt.Println(reflect.ValueOf(field))
return cmd.EnumCommandID(gconv.Uint32(field.Tag.Get("cmd")))
}
// fmt.Printf(" 类型: %v\n", field.Type)
// fmt.Printf(" 标签: %q\n", field.Tag)
// 递归处理嵌套结构体
if field.Type.Kind() == reflect.Struct ||
(field.Type.Kind() == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct) {
fmt.Println(" 嵌套结构体字段:")
nestedType := field.Type
if nestedType.Kind() == reflect.Ptr {
nestedType = nestedType.Elem()
}
getcmd(nestedType)
}
// fmt.Println()
}
return 0
}
// 遍历结构体方法并执行RECV_cmd
func Recv(c gnet.Conn, data handler.TomeeHeader) {
tt, ok := core.Maincmdcache.Load(data.CMD)
if !ok {
glog.Error(context.Background(), data.CMD, "cmd未注册")
return //TODO 待实现cmd未注册
}
cmdlister := tt.(reflect.Value)
// fmt.Println(cmdlister)
params := []reflect.Value{}
funct := cmdlister.Type().NumIn()
// 如果需要可设置的变量(用于修改值),创建指针并解引用
ptrValue := reflect.New(cmdlister.Type().In(0))
struc.Unpack(bytes.NewBuffer(data.Data), ptrValue)
// ttt := cmdlister.Type().In(0)
ptrValue1 := ptrValue.Elem()
switch funct {
case 1:
// struc.Unpack(bytes.NewBuffer(data), &a)
// return a
params = append(params, ptrValue1)
case 2:
params = append(params, ptrValue1, reflect.ValueOf(service.GetPlayer(c, data.UserID)))
}
ret := cmdlister.Call(params)
switch ret[0].Interface().(type) {
case []byte: //原始包
c.Write(ret[0].Interface().([]byte)) //这里直接发送原始包,应该是已经拼接过的原始包,通常不同使用
//case uint32: //错误码 实际上这里包含在结构体里了 ,错误码应该构造在返回之前
default:
var data1 bytes.Buffer
struc.Pack(&data1, &data)
c.Write(data1.Bytes())
}
}

View File

@@ -1,16 +1,15 @@
package controller
import (
"blazing/common/data/entity"
"blazing/logic/service/login"
"encoding/hex"
"fmt"
)
// 处理命令: 1001
func (h Controller) Login(data login.LoginSidInfo, player *entity.Player) interface{} {
func (h Controller) Login(data login.LoginSidInfo) interface{} { //这个时候player应该是空的
fmt.Println(login.CheakSession(data, player))
fmt.Println(login.CheakSession(data)) //检查结构体
t1, _ := hex.DecodeString("0000045D37000003E9000186A600000000000186A6683F89CF6E69656F0000000000000000000000000008000F00000000000000000000000000000000000000000000000000000001000001DB0000018B000000000000A8C000000000000000000000000000000000000000080001388000000001000000017FFFFFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF03030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030000000000000000000000000000000000000064000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000001FFFFFFFF000000004E4F4E4F0000000000000000000000000000000000000001000000010000000100000001000000010000000100000001000000000003030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030000000100000064000000000000000000000000000000000000001F000000000000006400000000000093F4000093F4000000D5000000F7000000AD00000088000000920000008C0000009C00000000000000000000000000000000000000000000000000000004000027900000001B00004E6200000014000028380000002800004E3E0000002368493DC60000000000000000000000000000000000000000000100000000000000A937000007D1000186A600000000000186A66E69656F00000000000000000000000000000000000000000000000F0000000000000000000001DB0000018B0000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFF0000000000000001000000000000000000000000000000000000000000000000000000000000000000000000")

View File

@@ -1,24 +0,0 @@
package controller
import (
"bytes"
"github.com/lunixbochs/struc"
)
// 分发cmd逻辑实现Controller
type Controller struct {
}
func NewController() *Controller {
return &Controller{}
}
func parseCmd[T any](a T, data []byte) T {
// := info.NewLoginSidInfo()
struc.Unpack(bytes.NewBuffer(data), &a)
return a
//fmt.Println(pinfo)
//login.OnData_1001(pinfo, player)
//fmt.Println(data)
}

View File

@@ -1,208 +1,26 @@
package main
import (
"bytes"
_ "github.com/gogf/gf/contrib/nosql/redis/v2"
"github.com/lunixbochs/struc"
"github.com/panjf2000/gnet/v2"
"blazing/common/core/info"
"blazing/common/data/entity"
"blazing/common/socket"
"blazing/common/socket/cmd"
"blazing/common/socket/handler"
"blazing/cool"
"blazing/logic/controller"
"fmt"
"reflect"
"strconv"
"sync"
"github.com/gogf/gf/v2/os/gctx"
)
var (
maininfocodec = info.NewInfoCodec() //创建一个InfoCodec实例
maincontroller = controller.NewController() //注入service
mainplayer sync.Map //玩家数据
maincmdcache sync.Map //命令缓存
)
func main() {
if cool.IsRedisMode {
go cool.ListenFunc(gctx.New())
}
Start(cool.Config.PortBL) //注入service
}
func Start(port string) {
head := handler.NewTomeeHandler()
head.Callback = recv
head.Callback = controller.Recv
socket.NewServer(socket.WithPort(port), socket.WithSocketHandler(head)).Start()
}
func recv(c gnet.Conn, data handler.TomeeHeader) {
// 处理接收到的TomeeHeader数据
// fmt.Println("收到数据:", data)
processWithReflection(c, data)
}
func getplayer(c gnet.Conn, userid uint32) *entity.Player { //TODO 这里待优化,可能存在内存泄漏问题
clientdata := c.Context().(*entity.ClientData)
if clientdata != nil && clientdata.Player != nil {
return clientdata.Player
}
var player *entity.Player
if player1, ok := mainplayer.Load((userid)); !ok {
player = entity.NewPlayer(
entity.WithUserID(userid), //注入ID
entity.WithConn(c), //注入conn
)
mainplayer.Store(userid, player)
} else {
player = player1.(*entity.Player) //取成功,否则创建
}
clientdata.Player = player
return player
}
// 遍历结构体方法并执行RECV_cmd
func processWithReflection(c gnet.Conn, pp handler.TomeeHeader) {
data := pp.Data
player := getplayer(c, pp.UserID) //获取player实例
cmdlister, ok := maincmdcache.Load(pp.CMDID) //TODO 待实现对不同用户初始化方法以取消全局cmdcache
if ok {
callhandler(cmdlister.(reflect.Value), data, player, cmd.EnumCommandID(pp.CMDID))
// return
} else {
// 获取对象的反射值和类型
value := reflect.ValueOf(maincontroller)
// 如果传入的是指针,获取其指向的值
if value.Kind() == reflect.Ptr {
if value.IsNil() {
fmt.Println("错误: 传入的是nil指针")
return
}
value = value.Elem()
}
// 获取类型
typ := value.Type()
// 遍历所有方法
fmt.Printf("检查结构体 %s 的方法...\n", typ.Name(), typ.NumMethod())
for i := 0; i < typ.NumMethod(); i++ {
method := typ.Method(i)
if method.Type.NumIn() == 4 { //&& // TODO 接收者 + 2个参数 一个是类型,一个是player
//method.Type.In(1) == reflect.TypeOf([]byte{}
// 获取方法值
methodValue := value.MethodByName(method.Name)
// 准备参数
cmd1 := method.Name[len("Recv_"):len(method.Name)]
cmdint, _ := strconv.Atoi(cmd1)
if cmdint == int(pp.CMDID) {
if !cmd.Isexist(cmd.CommandID, cmd.EnumCommandID(pp.CMDID)) {
fmt.Println("ID方法存在cmd未注册", pp.CMDID, cmd.CommandID)
}
maincmdcache.Store(pp.CMDID, methodValue) //TODO 待实现对不同用户初始化方法以取消全局cmdcache
callhandler(methodValue, data, player, cmd.EnumCommandID(pp.CMDID))
}
//return
}
//}
}
}
}
func callhandler(cmdlister reflect.Value, data []byte, player *entity.Player, cmd cmd.EnumCommandID) {
//TODO 待实现返回参数返回客户端???实现直接返回对象进行序列化 传递chan待返回
//TODO 已实现,待测试结构体序列化
recvchan := make(chan any, 1) //传递自定义chan
go func() { //TODO 待实现ants线程池,以及确认是否存在顺序混乱问题https://github.com/lxzan/gws/issues/3
retv := <-recvchan
switch ttt := retv.(type) {
case []byte:
player.SendPackBytes(cmd, ttt)
default:
var data1 bytes.Buffer
struc.Unpack(&data1, &ttt)
player.SendPackBytes(cmd, data1.Bytes())
}
}()
cmdlister.Call([]reflect.Value{
reflect.ValueOf(data),
reflect.ValueOf(player),
reflect.ValueOf(recvchan),
})
}
func getincodec(cmdid cmd.EnumCommandID, data []byte) reflect.Value {
// 获取对象的反射值和类型
value := reflect.ValueOf(maininfocodec)
// 如果传入的是指针,获取其指向的值
if value.Kind() == reflect.Ptr {
if value.IsNil() {
fmt.Println("错误: 传入的是nil指针")
return reflect.ValueOf(nil)
}
value = value.Elem()
}
// 获取类型
typ := value.Type()
// 遍历所有方法
fmt.Printf("检查结构体 %s 的方法...\n", typ.Name(), typ.NumMethod())
for i := 0; i < typ.NumMethod(); i++ {
method := typ.Method(i)
if method.Type.NumIn() == 2 && // 接收者 + 2个参数
method.Type.In(1) == reflect.TypeOf([]byte{}) {
// 获取方法值
methodValue := value.MethodByName(method.Name)
// 准备参数
cmd1 := method.Name[len("In_"):len(method.Name)]
cmdint, _ := strconv.Atoi(cmd1)
if cmdint == int(cmdid) {
if !cmd.Isexist(cmd.CommandID, cmd.EnumCommandID(cmdid)) {
fmt.Println("ID方法存在cmd未注册", cmdid, cmd.CommandID)
}
//data := pp.Data
// 调用方法
tt := methodValue.Call([]reflect.Value{
// reflect.ValueOf(cmdint),
reflect.ValueOf(data),
})
return tt[0]
}
//return
}
//}
}
return reflect.ValueOf(nil)
}

View File

@@ -1,68 +0,0 @@
package login
import (
"blazing/common/data/entity"
"blazing/cool"
"context"
"encoding/hex"
"fmt"
"strings"
)
// CommendSvrInfo 初始连接请求信息结构体
type CommendSvrInfo struct {
MaxOnlineID uint32 // 最大连接数
IsVip uint32 // 建议为0
ServerInfoLen uint32 `struc:"sizeof=ServerList"` // 服务器信息长度 ServerInfo
ServerList []ServerInfo // 服务器具体信息
Reversed uint32 // 保留字段
}
func NewCommendSvrInfo() *CommendSvrInfo {
return &CommendSvrInfo{
MaxOnlineID: 100,
IsVip: 0,
ServerInfoLen: 0,
ServerList: make([]ServerInfo, 0),
Reversed: 0,
}
}
// ServerInfo 服务器信息结构体
type ServerInfo struct {
// 连接ID, 即服务器序号
OnlineID uint32
// 当前服务器玩家在线数量, 供SWF显示
UserCnt uint32
// 服务器IP, 16字节UTF-8, 不足16补齐到16
IP string `struc:"[16]byte"` // 定长模式16字节
// 端口
Port uint16
// 好友在线的个数
Friends uint32
}
// NewServerInfo 创建新的服务器信息实例
func NewServerInfo() *ServerInfo {
return &ServerInfo{}
}
// LoginSidInfo 登录携带的凭证结构体
type LoginSidInfo struct {
Sid []byte `struc:"[16]byte"` // 登录会话ID固定长度16字节
}
func CheakSession(c LoginSidInfo, p *entity.Player) bool {
// tt, _ := cool.CacheManager.Keys(context.Background())
//g.Dump(tt)
t1 := hex.EncodeToString(c.Sid)
t2 := strings.Trim(t1, " ")
t, err := cool.CacheManager.Get(context.Background(), t2)
fmt.Println("后端获取", string(c.Sid), t, err)
if t.Uint32() == p.UserID {
return true
}
return false
}

34
logic/service/login/in.go Normal file
View File

@@ -0,0 +1,34 @@
package login
import (
"blazing/common/socket/handler"
"blazing/cool"
"context"
"encoding/hex"
"fmt"
"strings"
)
// LoginSidInfo 登录携带的凭证结构体
type LoginSidInfo struct { //这里直接使用组合来实现将传入的原始头部数据和结构体参数序列化
handler.TomeeHeader `cmd:"1001" struc:"[0]pad"`
Sid []byte `struc:"[16]byte"` // 登录会话ID固定长度16字节
// NotLogin uint32 `error="10001"|struc:"[0]pad"` //返回错误码 ,不序列化,仅作为错误码
// ErrorPassWord uint32 `struc:"[0]pad"`
}
func CheakSession(c LoginSidInfo) bool {
// tt, _ := cool.CacheManager.Keys(context.Background())
//g.Dump(tt)
t1 := hex.EncodeToString(c.Sid)
t2 := strings.Trim(t1, " ")
t, err := cool.CacheManager.Get(context.Background(), t2)
fmt.Println("后端获取", string(c.Sid), t, err)
if t.Uint32() == c.UserID {
return true
}
return false
}

View File

@@ -0,0 +1,42 @@
package login
import "blazing/common/socket/handler"
// CommendSvrInfo 初始连接请求信息结构体
type CommendSvrInfo struct {
Handler handler.TomeeHeader ` struc:"[0]pad"` //消息头 ,这里为传入的头部数据,遍历此头部实现解析CommendSvrInfo
MaxOnlineID uint32 // 最大连接数
IsVip uint32 // 建议为0
ServerInfoLen uint32 `struc:"sizeof=ServerList"` // 服务器信息长度 ServerInfo
ServerList []ServerInfo // 服务器具体信息
Reversed uint32 // 保留字段
}
func NewCommendSvrInfo() *CommendSvrInfo {
return &CommendSvrInfo{
MaxOnlineID: 100,
IsVip: 0,
ServerInfoLen: 0,
ServerList: make([]ServerInfo, 0),
Reversed: 0,
}
}
// ServerInfo 服务器信息结构体
type ServerInfo struct {
// 连接ID, 即服务器序号
OnlineID uint32
// 当前服务器玩家在线数量, 供SWF显示
UserCnt uint32
// 服务器IP, 16字节UTF-8, 不足16补齐到16
IP string `struc:"[16]byte"` // 定长模式16字节
// 端口
Port uint16
// 好友在线的个数
Friends uint32
}
// NewServerInfo 创建新的服务器信息实例
func NewServerInfo() *ServerInfo {
return &ServerInfo{}
}

31
logic/service/service.go Normal file
View File

@@ -0,0 +1,31 @@
package service
import (
"blazing/common/core"
"blazing/common/data/entity"
"github.com/panjf2000/gnet/v2"
)
func GetPlayer(c gnet.Conn, userid uint32) *entity.Player { //TODO 这里待优化,可能存在内存泄漏问题
//检查player初始化是否为conn初始后取map防止二次连接后存在两个player
clientdata := c.Context().(*entity.ClientData)
if clientdata != nil && clientdata.Player != nil {
return clientdata.Player
}
var player *entity.Player
if player1, ok := core.Mainplayer.Load((userid)); !ok {
player = entity.NewPlayer(
entity.WithUserID(userid), //注入ID
entity.WithConn(c), //注入conn
)
core.Mainplayer.Store(userid, player)
} else {
player = player1.(*entity.Player) //取成功,否则创建
}
clientdata.Player = player
return player
// return nil
}