refactor: storage 包重新实现
This commit is contained in:
parent
3e956b64cf
commit
b6f28dd743
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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, " "))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue