序列化使用第三方库

This commit is contained in:
liuxiaobo 2025-06-04 13:08:58 +08:00
parent 62abce7bca
commit 165efd717f
8 changed files with 154 additions and 114 deletions

View File

@ -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)"` // 头像

View File

@ -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"` // 登录时间

View File

@ -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)"` // 头像

View File

@ -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
// }

View File

@ -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)

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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 服务端玩法