From b6f28dd7431ca0d59292b9c3f993ae23320db63b Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Thu, 20 Jul 2023 18:10:22 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20storage=20=E5=8C=85=E9=87=8D?= =?UTF-8?q?=E6=96=B0=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/storage/data.go | 20 ++++++++ utils/storage/data_test.go | 45 +++++++++++++++++ utils/storage/set.go | 97 ++++++++++++++++++++++++++++++++++++ utils/storage/set_options.go | 65 ++++++++++++++++++++++++ utils/storage/storage.go | 11 ++++ 5 files changed, 238 insertions(+) create mode 100644 utils/storage/data.go create mode 100644 utils/storage/data_test.go create mode 100644 utils/storage/set.go create mode 100644 utils/storage/set_options.go create mode 100644 utils/storage/storage.go diff --git a/utils/storage/data.go b/utils/storage/data.go new file mode 100644 index 0000000..62bdf31 --- /dev/null +++ b/utils/storage/data.go @@ -0,0 +1,20 @@ +package storage + +// newData 创建一个数据存储 +func newData[Body any](body Body) *Data[Body] { + data := &Data[Body]{ + body: body, + } + return data +} + +// Data 数据存储 +// - 数据存储默认拥有一个 Body 字段 +type Data[Body any] struct { + body Body +} + +// Handle 处理数据 +func (slf *Data[Body]) Handle(handler func(data Body)) { + handler(slf.body) +} diff --git a/utils/storage/data_test.go b/utils/storage/data_test.go new file mode 100644 index 0000000..a5b3d11 --- /dev/null +++ b/utils/storage/data_test.go @@ -0,0 +1,45 @@ +package storage_test + +import ( + "encoding/json" + "fmt" + "github.com/kercylan98/minotaur/utils/storage" + "testing" +) + +type Player struct { + ID string `json:"id"` + Name string `json:"name"` + Power int64 `json:"power"` +} + +func TestData_Struct(t *testing.T) { + player := storage.NewSet[string, *Player](new(Player), + func(data *Player) string { + return data.ID + }, storage.WithIndex[string, string, *Player]("id", func(data *Player) string { + return data.ID + }), storage.WithIndex[string, string, *Player]("name", func(data *Player) string { + return data.Name + }), + ) + + p := player.New() + + p.Handle(func(data *Player) { + data.ID = "1" + data.Name = "kercylan" + data.Power = 100 + }) + + str, err := player.Struct(p) + if err != nil { + panic(err) + } + bytes, err := json.Marshal(str) + if err != nil { + panic(err) + } + + fmt.Println(string(bytes)) +} diff --git a/utils/storage/set.go b/utils/storage/set.go new file mode 100644 index 0000000..71f07fe --- /dev/null +++ b/utils/storage/set.go @@ -0,0 +1,97 @@ +package storage + +import ( + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/str" + "reflect" +) + +var json = jsonIter.ConfigCompatibleWithStandardLibrary + +func NewSet[PrimaryKey generic.Ordered, Body any](zero Body, getIndex func(data Body) PrimaryKey, options ...SetOption[PrimaryKey, Body]) *Set[PrimaryKey, Body] { + set := &Set[PrimaryKey, Body]{ + zero: zero, + tf: reflect.Indirect(reflect.ValueOf(zero)).Type(), + getIndex: getIndex, + bodyField: reflect.StructField{ + Name: DefaultBodyFieldName, + Type: reflect.TypeOf(str.None), + }, + items: make(map[PrimaryKey]*Data[Body]), + } + for _, option := range options { + option(set) + } + return set +} + +// Set 数据集合 +type Set[PrimaryKey generic.Ordered, Body any] struct { + storage Storage[PrimaryKey, Body] + zero Body + tf reflect.Type + getIndex func(data Body) PrimaryKey + bodyField reflect.StructField + indexes []reflect.StructField + getIndexValue map[string]func(data Body) any + items map[PrimaryKey]*Data[Body] +} + +// New 创建一份新数据 +// - 这份数据不会被存储 +func (slf *Set[PrimaryKey, Body]) New() *Data[Body] { + var data = reflect.New(slf.tf).Interface().(Body) + return newData(data) +} + +// Get 通过主键获取数据 +// - 优先从内存中加载,如果数据不存在,则尝试从存储中加载 +func (slf *Set[PrimaryKey, Body]) Get(index PrimaryKey) (*Data[Body], error) { + if data, exist := slf.items[index]; exist { + return data, nil + } + body, err := slf.storage.Load(index) + if err != nil { + return nil, err + } + data := newData(body) + slf.items[index] = data + return data, nil +} + +// Set 设置数据 +// - 该方法会将数据存储到内存中 +func (slf *Set[PrimaryKey, Body]) Set(data *Data[Body]) { + slf.items[slf.getIndex(data.body)] = data +} + +// Save 保存数据 +// - 该方法会将数据存储到存储器中 +func (slf *Set[PrimaryKey, Body]) Save(data *Data[Body]) error { + return slf.storage.Save(slf, slf.getIndex(data.body), data.body) +} + +// Struct 将数据存储转换为结构体 +func (slf *Set[PrimaryKey, Body]) Struct(data *Data[Body]) (any, error) { + var fields = make([]reflect.StructField, 0, len(slf.indexes)+1) + for _, field := range append(slf.indexes, slf.bodyField) { + fields = append(fields, field) + } + instance := reflect.New(reflect.StructOf(fields)) + value := instance.Elem() + for _, field := range slf.indexes { + get, exist := slf.getIndexValue[field.Name] + if !exist { + continue + } + value.FieldByName(field.Name).Set(reflect.ValueOf(get(data.body))) + } + bytes, err := json.Marshal(data.body) + if err != nil { + return nil, err + } + + value.FieldByName(slf.bodyField.Name).Set(reflect.ValueOf(string(bytes))) + return value.Interface(), nil +} diff --git a/utils/storage/set_options.go b/utils/storage/set_options.go new file mode 100644 index 0000000..51166d8 --- /dev/null +++ b/utils/storage/set_options.go @@ -0,0 +1,65 @@ +package storage + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/str" + "reflect" + "strings" +) + +const ( + DefaultBodyFieldName = "Data" +) + +type SetOption[PrimaryKey generic.Ordered, Body any] func(set *Set[PrimaryKey, Body]) + +// WithIndex 添加一个索引 +// - 索引将会在数据结构体中创建一个字段,这个字段必须可以在 Body 内部找到,用于对查找功能的拓展 +func WithIndex[PrimaryKey generic.Ordered, Index generic.Ordered, Body any](name string, getValue func(data Body) Index) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + WithTagIndex[PrimaryKey, Index, Body](name, nil, getValue)(set) + } +} + +// WithTagIndex 添加一个带 tag 的索引 +// - 同 WithIndex,但是可以自定义索引的 tag +func WithTagIndex[PrimaryKey generic.Ordered, Index generic.Ordered, Body any](name string, tags []string, getValue func(data Body) Index) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + value := getValue(set.zero) + upperName := str.FirstUpper(name) + if set.getIndexValue == nil { + set.getIndexValue = map[string]func(data Body) any{} + } + set.getIndexValue[upperName] = func(data Body) any { + return getValue(data) + } + var tag string + if len(tags) > 0 { + tag = strings.Join(tags, " ") + } + set.indexes = append(set.indexes, reflect.StructField{ + Name: upperName, + Type: reflect.TypeOf(value), + Tag: reflect.StructTag(tag), + }) + } +} + +// WithBodyName 设置 Body 字段名称 +// - 默认字段名称为 DefaultBodyFieldName +func WithBodyName[PrimaryKey generic.Ordered, Body any](name string) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + if len(name) == 0 { + return + } + set.bodyField.Name = str.FirstUpper(name) + } +} + +// WithBodyTag 设置 Body 字段标签 +// - 如果有多个标签,将会以空格分隔,例如:`json:"data" yaml:"data"` +func WithBodyTag[PrimaryKey generic.Ordered, Body any](tags ...string) SetOption[PrimaryKey, Body] { + return func(set *Set[PrimaryKey, Body]) { + set.bodyField.Tag = reflect.StructTag(strings.Join(tags, " ")) + } +} diff --git a/utils/storage/storage.go b/utils/storage/storage.go new file mode 100644 index 0000000..4491839 --- /dev/null +++ b/utils/storage/storage.go @@ -0,0 +1,11 @@ +package storage + +import "github.com/kercylan98/minotaur/utils/generic" + +type Storage[PrimaryKey generic.Ordered, Body any] interface { + // Load 加载数据 + Load(index PrimaryKey) (Body, error) + + // Save 保存数据 + Save(set *Set[PrimaryKey, Body], index PrimaryKey, data any) error +}