From 165efd717f646eea67f94936ced85ef97d8f2d82 Mon Sep 17 00:00:00 2001 From: liuxiaobo Date: Wed, 4 Jun 2025 13:08:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BA=8F=E5=88=97=E5=8C=96=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/model/user/user.go | 6 +- common/model/user/userAccount.go | 6 +- common/model/user/userResources.go | 6 +- common/serialization/serialization.go | 227 ++++++++++++--------- common/serialization/serialization_test.go | 5 +- go.mod | 1 + go.sum | 2 + 工作.txt | 15 +- 8 files changed, 154 insertions(+), 114 deletions(-) diff --git a/common/model/user/user.go b/common/model/user/user.go index a0800aa..136b366 100644 --- a/common/model/user/user.go +++ b/common/model/user/user.go @@ -1,12 +1,8 @@ package user -import ( - "gorm.io/gorm" -) - // 玩家账户表 type User struct { - gorm.Model `json:"-"` + ID uint `gorm:"primarykey;autoIncrement" json:"id"` accountId uint `gorm:"type:bigint;uniqueIndex;not null"` // 帐号id Nickname string `gorm:"type:varchar(32);uniqueIndex;not null"` // 昵称 AvatarUrl string `gorm:"type:varchar(255)"` // 头像 diff --git a/common/model/user/userAccount.go b/common/model/user/userAccount.go index e94ea08..ffc071b 100644 --- a/common/model/user/userAccount.go +++ b/common/model/user/userAccount.go @@ -5,14 +5,14 @@ import ( ) const ( - //AccountNormal = 1 // 正常 + // AccountNormal = 1 // 正常 AccountFrozen = 2 // 冻结 AccountBanned = 3 // 封禁 ) // 玩家账户表 type UserAccount struct { - ID uint `gorm:"primarykey" json:"id"` + ID uint `gorm:"primarykey;autoIncrement" json:"id"` Username string `gorm:"type:varchar(32);uniqueIndex;not null" json:"username"` // 用户名 Password string `gorm:"type:varchar(255);not null" json:"password"` // 密码哈希 Email string `gorm:"type:varchar(100)" json:"email"` // 邮箱(可选) @@ -35,7 +35,7 @@ func (u UserAccount) TableName() string { // 玩家登录记录表 type UserLoginLog struct { - ID uint `gorm:"primarykey" json:"id"` + ID uint `gorm:"primarykey;autoIncrement" json:"id"` UID uint `gorm:"index" json:"uid"` // 关联玩家ID LoginIP string `gorm:"type:varchar(45);not null" json:"login_ip"` // 登录IP LoginTime time.Time `gorm:"type:TIMESTAMP;default:CURRENT_TIMESTAMP" json:"login_time"` // 登录时间 diff --git a/common/model/user/userResources.go b/common/model/user/userResources.go index 77a7ecb..fbc86f0 100644 --- a/common/model/user/userResources.go +++ b/common/model/user/userResources.go @@ -1,12 +1,8 @@ package user -import ( - "gorm.io/gorm" -) - // 玩家账户表 type UserResources struct { - gorm.Model `json:"-"` + ID uint `gorm:"primarykey;autoIncrement" json:"id"` accountId uint `gorm:"type:bigint;uniqueIndex;not null"` // 帐号id Nickname string `gorm:"type:varchar(32);uniqueIndex;not null"` // 昵称 AvatarUrl string `gorm:"type:varchar(255)"` // 头像 diff --git a/common/serialization/serialization.go b/common/serialization/serialization.go index 0c3a1dc..06e4875 100644 --- a/common/serialization/serialization.go +++ b/common/serialization/serialization.go @@ -1,106 +1,149 @@ package serialization import ( - "encoding/json" - "fmt" + "github.com/fox/fox/log" + "github.com/mitchellh/mapstructure" "reflect" - "strconv" + "time" ) -func StructToMap(obj interface{}) map[string]interface{} { - out := make(map[string]interface{}) - v := reflect.ValueOf(obj) - if v.Kind() == reflect.Ptr { - v = v.Elem() +func StructToMap(_struct interface{}) map[string]interface{} { + var result map[string]interface{} + err := mapstructure.Decode(_struct, &result) + if err != nil { + log.ErrorF("struct:%v to map error:%v", _struct, err) + return make(map[string]interface{}) } - t := v.Type() - for i := 0; i < v.NumField(); i++ { - field := t.Field(i) - key := field.Tag.Get("json") // 使用 json tag 作为字段名 - if key == "" { - key = field.Name - } - out[key] = v.Field(i).Interface() - } - return out -} - -func keyValueToField(key, value string, fieldValue *reflect.Value) error { - switch fieldValue.Kind() { - case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: - if v, err := strconv.ParseInt(value, 10, 0); err == nil { - fieldValue.SetInt(v) - } else { - return err - } - case reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if v, err := strconv.ParseUint(value, 10, 0); err == nil { - fieldValue.SetUint(v) - } else { - return err - } - case reflect.Float32, reflect.Float64: - if v, err := strconv.ParseFloat(value, 0); err == nil { - fieldValue.SetFloat(v) - } else { - return err - } - case reflect.String: - fieldValue.SetString(value) - case reflect.Bool: - if v, err := strconv.ParseBool(value); err == nil { - fieldValue.SetBool(v) - } else { - return err - } - default: - nv := reflect.New(fieldValue.Type()) - if err := json.Unmarshal([]byte(value), nv.Interface()); err == nil { - if fieldValue.Kind() == nv.Elem().Kind() { - fieldValue.Set(nv.Elem()) - } else { - return fmt.Errorf("field:%v should have same type.original type:%v, reflect type:%v", key, fieldValue.Kind().String(), nv.Elem().Kind().String()) - } - } else { - return err - } - } - return nil -} - -func deserializeMapString(kvs map[string]string, structObj interface{}) error { - val := reflect.ValueOf(structObj).Elem() - typ := val.Type() - - for i := 0; i < typ.NumField(); i++ { - field := typ.Field(i) - // 检查字段是否可设置(即导出) - if !field.IsExported() { - continue - } - fieldName := field.Tag.Get("json") - if fieldName == "-" { - continue - } - if value, ok := kvs[fieldName]; ok { - // 获取字段的反射值 - fieldValue := val.FieldByName(field.Name) - if err := keyValueToField(field.Tag.Get("json"), value, &fieldValue); err != nil { - return err - } - } else { - return fmt.Errorf("redis:%v not has field:%v ", typ.Name(), field.Tag.Get("json")) - } - } - return nil + return result } type resultT[T any] struct { ret T } -func MapStringToStruct[T any](maps map[string]string) (*T, error) { - result := &resultT[T]{} - err := deserializeMapString(maps, &result.ret) - return &result.ret, err +func stringToTimeHook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { + if t == reflect.TypeOf(time.Time{}) && f == reflect.TypeOf("") { + return time.Parse(time.RFC3339, data.(string)) + } + return data, nil } + +func MapToStruct[T any](maps map[string]string) (*T, error) { + result := &resultT[T]{} + config := &mapstructure.DecoderConfig{ + WeaklyTypedInput: true, // 允许弱类型转换(如 "1" → 1) + DecodeHook: stringToTimeHook, // 自定义钩子 + Result: &result.ret, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + log.ErrorF("map:%v to struct error:%v", maps, err) + return nil, err + } + + err = decoder.Decode(maps) + if err != nil { + log.ErrorF("map:%v to struct error:%v", maps, err) + return nil, err + } + return &result.ret, nil +} + +// func StructToMap(obj interface{}) map[string]interface{} { +// out := make(map[string]interface{}) +// v := reflect.ValueOf(obj) +// if v.Kind() == reflect.Ptr { +// v = v.Elem() +// } +// t := v.Type() +// for i := 0; i < v.NumField(); i++ { +// field := t.Field(i) +// key := field.Tag.Get("json") // 使用 json tag 作为字段名 +// if key == "" { +// key = field.Name +// } +// out[key] = v.Field(i).Interface() +// } +// return out +// } + +// func keyValueToField(key, value string, fieldValue *reflect.Value) error { +// switch fieldValue.Kind() { +// case reflect.Int8, reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: +// if v, err := strconv.ParseInt(value, 10, 0); err == nil { +// fieldValue.SetInt(v) +// } else { +// return err +// } +// case reflect.Uint8, reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: +// if v, err := strconv.ParseUint(value, 10, 0); err == nil { +// fieldValue.SetUint(v) +// } else { +// return err +// } +// case reflect.Float32, reflect.Float64: +// if v, err := strconv.ParseFloat(value, 0); err == nil { +// fieldValue.SetFloat(v) +// } else { +// return err +// } +// case reflect.String: +// fieldValue.SetString(value) +// case reflect.Bool: +// if v, err := strconv.ParseBool(value); err == nil { +// fieldValue.SetBool(v) +// } else { +// return err +// } +// default: +// nv := reflect.New(fieldValue.Type()) +// if err := json.Unmarshal([]byte(value), nv.Interface()); err == nil { +// if fieldValue.Kind() == nv.Elem().Kind() { +// fieldValue.Set(nv.Elem()) +// } else { +// return fmt.Errorf("field:%v should have same type.original type:%v, reflect type:%v", key, fieldValue.Kind().String(), nv.Elem().Kind().String()) +// } +// } else { +// return err +// } +// } +// return nil +// } +// +// func deserializeMapString(kvs map[string]string, structObj interface{}) error { +// val := reflect.ValueOf(structObj).Elem() +// typ := val.Type() +// +// for i := 0; i < typ.NumField(); i++ { +// field := typ.Field(i) +// // 检查字段是否可设置(即导出) +// if !field.IsExported() { +// continue +// } +// fieldName := field.Tag.Get("json") +// if fieldName == "-" { +// continue +// } +// if value, ok := kvs[fieldName]; ok { +// // 获取字段的反射值 +// fieldValue := val.FieldByName(field.Name) +// if err := keyValueToField(field.Tag.Get("json"), value, &fieldValue); err != nil { +// return err +// } +// } else { +// return fmt.Errorf("redis:%v not has field:%v ", typ.Name(), field.Tag.Get("json")) +// } +// } +// return nil +// } +// +// type resultT[T any] struct { +// ret T +// } +// +// func MapStringToStruct[T any](maps map[string]string) (*T, error) { +// result := &resultT[T]{} +// err := deserializeMapString(maps, &result.ret) +// return &result.ret, err +// } diff --git a/common/serialization/serialization_test.go b/common/serialization/serialization_test.go index 09aedd0..7d1a962 100644 --- a/common/serialization/serialization_test.go +++ b/common/serialization/serialization_test.go @@ -12,8 +12,9 @@ func Test_zz(t *testing.T) { _ = json.Unmarshal([]byte(zz), us) var err error - us, err = MapStringToStruct[user.UserAccount](map[string]string{ - "id": "17", + us, err = MapToStruct[user.UserAccount](map[string]string{ + "id": "17", + "last_login_time": "2025-06-04T02:01:49.72898394+08:00", }) t.Log(err) t.Log(us) diff --git a/go.mod b/go.mod index f746c16..59c1960 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( require ( github.com/golang/protobuf v1.5.4 + github.com/mitchellh/mapstructure v1.5.0 github.com/nats-io/nats.go v1.42.0 golang.org/x/crypto v0.38.0 google.golang.org/protobuf v1.33.0 diff --git a/go.sum b/go.sum index 766598d..da8a5d2 100644 --- a/go.sum +++ b/go.sum @@ -57,6 +57,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= diff --git a/工作.txt b/工作.txt index 3573453..ffb11d4 100644 --- a/工作.txt +++ b/工作.txt @@ -5,13 +5,14 @@ 1.4 客户端stop关闭连接,触发服务端连接崩溃。(已修复) 2.编写db服 - 2.1 login服向db服请求数据及向log db服写入日志。测试rpc机制。(已验证) - 2.2 db创建表时,主键不会自增,而是随机增加。 - 2.3 首次创建帐号,redis没有写入帐号数据。 (已修复) - 2.4 第二次登陆,login还会走创建帐号逻辑,导致db服返回重复建号失败。 (已修复) - 2.5 redis写入数据的字段名有误。(已修复) - 2.5 清理登陆相关调试日志。 - 2.6 login在创建帐号时,还需要创建user。 + 2.01 login服向db服请求数据及向log db服写入日志。测试rpc机制。(已验证) + 2.02 db创建表时,主键不会自增,而是随机增加。(已修复) + 2.03 首次创建帐号,redis没有写入帐号数据。 (已修复) + 2.04 第二次登陆,login还会走创建帐号逻辑,导致db服返回重复建号失败。 (已修复) + 2.05 redis写入数据的字段名有误。(已修复) + 2.06 struct序列化到redis中时需要处理时间格式。 + 2.07 清理登陆相关调试日志。 + 2.08 login在创建帐号时,还需要创建user。 3.编写color game玩法 3.1 服务端玩法