diff --git a/utils/storage/global_data.go b/utils/storage/global_data.go index ed432e0..63eb32c 100644 --- a/utils/storage/global_data.go +++ b/utils/storage/global_data.go @@ -3,15 +3,17 @@ package storage // NewGlobalData 创建全局数据 func NewGlobalData[T any](name string, storage GlobalDataStorage[T]) *GlobalData[T] { data := &GlobalData[T]{ - name: name, - data: storage.Load(name), + storage: storage, + name: name, + data: storage.Load(name), } + globalDataSaveHandles = append(globalDataSaveHandles, data.SaveData) return data } // GlobalData 全局数据 type GlobalData[T any] struct { - storage GlobalDataStorage[T] // 存储器 + storage GlobalDataStorage[T] // 全局数据存储器 name string // 全局数据名称 data T // 数据 } @@ -27,13 +29,13 @@ func (slf *GlobalData[T]) GetData() T { } // LoadData 加载数据 -func (slf *GlobalData[T]) LoadData(storage GlobalDataStorage[T]) *GlobalData[T] { - return LoadGlobalData(slf, storage) +func (slf *GlobalData[T]) LoadData() { + slf.data = slf.storage.Load(slf.GetName()) } // SaveData 保存数据 -func (slf *GlobalData[T]) SaveData(storage GlobalDataStorage[T]) *GlobalData[T] { - return SaveGlobalData(slf, storage) +func (slf *GlobalData[T]) SaveData() error { + return slf.storage.Save(slf.GetName(), slf.GetData()) } // Handle 处理数据 diff --git a/utils/storage/global_data_storage.go b/utils/storage/global_data_storage.go index 2c0e50e..872c368 100644 --- a/utils/storage/global_data_storage.go +++ b/utils/storage/global_data_storage.go @@ -6,17 +6,5 @@ type GlobalDataStorage[T any] interface { // - 当全局数据不存在时,应当返回新的全局数据实例 Load(name string) T // Save 保存全局数据 - Save(name string, data T) -} - -// LoadGlobalData 加载全局数据 -func LoadGlobalData[T any](globalData *GlobalData[T], storage GlobalDataStorage[T]) *GlobalData[T] { - globalData.data = storage.Load(globalData.GetName()) - return globalData -} - -// SaveGlobalData 保存全局数据 -func SaveGlobalData[T any](globalData *GlobalData[T], storage GlobalDataStorage[T]) *GlobalData[T] { - storage.Save(globalData.GetName(), globalData.GetData()) - return globalData + Save(name string, data T) error } diff --git a/utils/storage/index_data.go b/utils/storage/index_data.go index a41a8c4..71ac801 100644 --- a/utils/storage/index_data.go +++ b/utils/storage/index_data.go @@ -2,22 +2,24 @@ package storage import ( "github.com/kercylan98/minotaur/utils/generic" + "time" ) // NewIndexData 创建索引数据 -func NewIndexData[I generic.Ordered, T any](name string, storage IndexDataStorage[I, T]) *IndexData[I, T] { +func NewIndexData[I generic.Ordered, T IndexDataItem[I]](name string, storage IndexDataStorage[I, T]) *IndexData[I, T] { data := &IndexData[I, T]{ - name: name, - data: map[I]T{}, + storage: storage, + name: name, + data: storage.LoadAll(name), } return data } // IndexData 全局数据 -type IndexData[I generic.Ordered, T any] struct { - storage GlobalDataStorage[T] // 存储器 - name string // 数据组名称 - data map[I]T // 数据 +type IndexData[I generic.Ordered, T IndexDataItem[I]] struct { + storage IndexDataStorage[I, T] // 存储器 + name string // 数据组名称 + data map[I]T // 数据 } // GetName 获取名称 @@ -27,7 +29,12 @@ func (slf *IndexData[I, T]) GetName() string { // GetData 获取数据 func (slf *IndexData[I, T]) GetData(index I) T { - return slf.data[index] + data, exist := slf.data[index] + if !exist { + slf.LoadData(index) + data = slf.data[index] + } + return data } // GetAllData 获取所有数据 @@ -36,13 +43,40 @@ func (slf *IndexData[I, T]) GetAllData() map[I]T { } // LoadData 加载数据 -func (slf *IndexData[I, T]) LoadData(index I, storage IndexDataStorage[I, T]) *IndexData[I, T] { - return LoadIndexData(slf, index, storage) +func (slf *IndexData[I, T]) LoadData(index I) { + slf.data[index] = slf.storage.Load(slf.GetName(), index) +} + +// LoadAllData 加载所有数据 +func (slf *IndexData[I, T]) LoadAllData() { + slf.data = slf.storage.LoadAll(slf.GetName()) } // SaveData 保存数据 -func (slf *IndexData[I, T]) SaveData(storage IndexDataStorage[I, T], index I) *IndexData[I, T] { - return SaveIndexData(slf, index, storage) +func (slf *IndexData[I, T]) SaveData(index I) error { + return slf.storage.Save(slf.GetName(), index, slf.GetData(index)) +} + +// SaveAllData 保存所有数据 +// - errHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个 +func (slf *IndexData[I, T]) SaveAllData(errHandle func(err error) bool, retryInterval time.Duration) { + slf.storage.SaveAll(slf.GetName(), slf.GetAllData(), errHandle, retryInterval) +} + +// DeleteData 删除数据 +func (slf *IndexData[I, T]) DeleteData(index I) *IndexData[I, T] { + slf.storage.Delete(slf.GetName(), index) + delete(slf.data, index) + return slf +} + +// DeleteAllData 删除所有数据 +func (slf *IndexData[I, T]) DeleteAllData() *IndexData[I, T] { + slf.storage.DeleteAll(slf.GetName()) + for k := range slf.data { + delete(slf.data, k) + } + return slf } // Handle 处理数据 diff --git a/utils/storage/index_data_item.go b/utils/storage/index_data_item.go new file mode 100644 index 0000000..86da5b6 --- /dev/null +++ b/utils/storage/index_data_item.go @@ -0,0 +1,7 @@ +package storage + +import "github.com/kercylan98/minotaur/utils/generic" + +type IndexDataItem[I generic.Ordered] interface { + GetIndex() I +} diff --git a/utils/storage/index_data_storage.go b/utils/storage/index_data_storage.go index e1238c4..4ef0d74 100644 --- a/utils/storage/index_data_storage.go +++ b/utils/storage/index_data_storage.go @@ -1,60 +1,23 @@ package storage -import "github.com/kercylan98/minotaur/utils/generic" +import ( + "github.com/kercylan98/minotaur/utils/generic" + "time" +) // IndexDataStorage 全局数据存储器接口 -type IndexDataStorage[I generic.Ordered, T any] interface { +type IndexDataStorage[I generic.Ordered, T IndexDataItem[I]] interface { // Load 加载特定索引数据 // - 通常情况下当数据不存在时,应当返回空指针 Load(name string, index I) T // LoadAll 加载所有数据 LoadAll(name string) map[I]T // Save 保存特定索引数据 - Save(name string, index I, data T) + Save(name string, index I, data T) error // SaveAll 保存所有数据 - SaveAll(name string, data map[I]T) + SaveAll(name string, data map[I]T, errHandle func(err error) bool, retryInterval time.Duration) // Delete 删除特定索引数据 Delete(name string, index I) // DeleteAll 删除所有数据 DeleteAll(name string) } - -// LoadIndexData 加载索引数据 -func LoadIndexData[I generic.Ordered, T any](indexData *IndexData[I, T], index I, storage IndexDataStorage[I, T]) *IndexData[I, T] { - indexData.data[index] = storage.Load(indexData.GetName(), index) - return indexData -} - -// LoadIndexDataAll 加载所有索引数据 -func LoadIndexDataAll[I generic.Ordered, T any](indexData *IndexData[I, T], storage IndexDataStorage[I, T]) *IndexData[I, T] { - indexData.data = storage.LoadAll(indexData.GetName()) - return indexData -} - -// SaveIndexData 保存索引数据 -func SaveIndexData[I generic.Ordered, T any](indexData *IndexData[I, T], index I, storage IndexDataStorage[I, T]) *IndexData[I, T] { - storage.Save(indexData.GetName(), index, indexData.GetData(index)) - return indexData -} - -// SaveIndexDataALl 保存所有所索引数据 -func SaveIndexDataALl[I generic.Ordered, T any](indexData *IndexData[I, T], storage IndexDataStorage[I, T]) *IndexData[I, T] { - storage.SaveAll(indexData.GetName(), indexData.GetAllData()) - return indexData -} - -// DeleteIndexData 删除索引数据 -func DeleteIndexData[I generic.Ordered, T any](indexData *IndexData[I, T], index I, storage IndexDataStorage[I, T]) *IndexData[I, T] { - storage.Delete(indexData.GetName(), index) - delete(indexData.data, index) - return indexData -} - -// DeleteIndexDataAll 删除所有索引数据 -func DeleteIndexDataAll[I generic.Ordered, T any](indexData *IndexData[I, T], storage IndexDataStorage[I, T]) *IndexData[I, T] { - storage.DeleteAll(indexData.GetName()) - for k := range indexData.data { - delete(indexData.data, k) - } - return indexData -} diff --git a/utils/storage/storage.go b/utils/storage/storage.go new file mode 100644 index 0000000..608b959 --- /dev/null +++ b/utils/storage/storage.go @@ -0,0 +1,26 @@ +package storage + +import "time" + +var ( + // globalDataSaveHandles 全局数据保存句柄 + globalDataSaveHandles []func() error +) + +// SaveAll 保存所有数据 +// - errorHandle 错误处理中如果返回 false 将重试,否则跳过当前保存下一个 +func SaveAll(errorHandle func(err error) bool, retryInterval time.Duration) { + var err error + for _, handle := range globalDataSaveHandles { + for { + if err = handle(); err != nil { + if !errorHandle(err) { + time.Sleep(retryInterval) + continue + } + break + } + break + } + } +} diff --git a/utils/storage/storages/encode.go b/utils/storage/storages/encode.go new file mode 100644 index 0000000..f34958d --- /dev/null +++ b/utils/storage/storages/encode.go @@ -0,0 +1,23 @@ +package storages + +import "encoding/json" + +// FileStorageEncoder 全局数据文件存储编码器 +type FileStorageEncoder[T any] func(data T) ([]byte, error) + +// FileStorageDecoder 全局数据文件存储解码器 +type FileStorageDecoder[T any] func(bytes []byte, data T) error + +// FileStorageJSONEncoder JSON 编码器 +func FileStorageJSONEncoder[T any]() FileStorageEncoder[T] { + return func(data T) ([]byte, error) { + return json.Marshal(data) + } +} + +// FileStorageJSONDecoder JSON 解码器 +func FileStorageJSONDecoder[T any]() FileStorageDecoder[T] { + return func(bytes []byte, data T) error { + return json.Unmarshal(bytes, data) + } +} diff --git a/utils/storage/storages/global_data_file.go b/utils/storage/storages/global_data_file.go new file mode 100644 index 0000000..8d1e713 --- /dev/null +++ b/utils/storage/storages/global_data_file.go @@ -0,0 +1,63 @@ +package storages + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/file" + "path/filepath" +) + +const ( + // GlobalDataFileDefaultSuffix 是 GlobalDataFileStorage 的文件默认后缀 + GlobalDataFileDefaultSuffix = "stock" +) + +// NewGlobalDataFileStorage 创建一个 GlobalDataFileStorage,dir 是文件存储目录,generate 是生成数据的函数,options 是可选参数 +// - 生成的文件名为 ${name}.${suffix},可以通过 WithGlobalDataFileStorageSuffix 修改后缀 +// - 默认使用 JSON 格式存储,可以通过 WithGlobalDataFileStorageEncoder 和 WithGlobalDataFileStorageDecoder 修改编码和解码函数 +// - 内置了 JSON 编码和解码函数,可以通过 FileStorageJSONEncoder 和 FileStorageJSONDecoder 获取 +func NewGlobalDataFileStorage[T any](dir string, generate func(name string) T, options ...GlobalDataFileStorageOption[T]) *GlobalDataFileStorage[T] { + abs, err := filepath.Abs(dir) + if err != nil { + panic(err) + } + storage := &GlobalDataFileStorage[T]{ + dir: abs, + suffix: GlobalDataFileDefaultSuffix, + generate: generate, + encoder: FileStorageJSONEncoder[T](), + decoder: FileStorageJSONDecoder[T](), + } + for _, option := range options { + option(storage) + } + return storage +} + +// GlobalDataFileStorage 用于存储全局数据的文件存储器 +type GlobalDataFileStorage[T any] struct { + dir string + suffix string + generate func(name string) T + encoder FileStorageEncoder[T] + decoder FileStorageDecoder[T] +} + +// Load 从文件中加载数据,如果文件不存在则使用 generate 生成数据 +func (slf *GlobalDataFileStorage[T]) Load(name string) T { + bytes, err := file.ReadOnce(filepath.Join(slf.dir, fmt.Sprintf("%s.%s", name, slf.suffix))) + if err != nil { + return slf.generate(name) + } + var data = slf.generate(name) + _ = slf.decoder(bytes, data) + return data +} + +// Save 将数据保存到文件中 +func (slf *GlobalDataFileStorage[T]) Save(name string, data T) error { + bytes, err := slf.encoder(data) + if err != nil { + return err + } + return file.WriterFile(filepath.Join(slf.dir, fmt.Sprintf("%s.%s", name, slf.suffix)), bytes) +} diff --git a/utils/storage/storages/global_data_file_options.go b/utils/storage/storages/global_data_file_options.go new file mode 100644 index 0000000..1f05719 --- /dev/null +++ b/utils/storage/storages/global_data_file_options.go @@ -0,0 +1,28 @@ +package storages + +// GlobalDataFileStorageOption 全局数据文件存储选项 +type GlobalDataFileStorageOption[T any] func(storage *GlobalDataFileStorage[T]) + +// WithGlobalDataFileStorageEncoder 设置编码器 +// - 默认为 FileStorageJSONEncoder 编码器 +func WithGlobalDataFileStorageEncoder[T any](encoder FileStorageEncoder[T]) GlobalDataFileStorageOption[T] { + return func(storage *GlobalDataFileStorage[T]) { + storage.encoder = encoder + } +} + +// WithGlobalDataFileStorageDecoder 设置解码器 +// - 默认为 FileStorageJSONDecoder 解码器 +func WithGlobalDataFileStorageDecoder[T any](decoder FileStorageDecoder[T]) GlobalDataFileStorageOption[T] { + return func(storage *GlobalDataFileStorage[T]) { + storage.decoder = decoder + } +} + +// WithGlobalDataFileStorageSuffix 设置文件后缀 +// - 默认为 GlobalDataFileDefaultSuffix +func WithGlobalDataFileStorageSuffix[T any](suffix string) GlobalDataFileStorageOption[T] { + return func(storage *GlobalDataFileStorage[T]) { + storage.suffix = suffix + } +} diff --git a/utils/storage/storages/index_data_file.go b/utils/storage/storages/index_data_file.go new file mode 100644 index 0000000..4459dae --- /dev/null +++ b/utils/storage/storages/index_data_file.go @@ -0,0 +1,117 @@ +package storages + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/file" + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/storage" + "os" + "path/filepath" + "strings" + "time" +) + +const ( + // IndexDataFileDefaultSuffix 是 IndexDataFileStorage 的文件默认后缀 + IndexDataFileDefaultSuffix = "stock" + + indexNameFormat = "%s.%v.%s" +) + +// NewIndexDataFileStorage 创建索引数据文件存储器 +func NewIndexDataFileStorage[I generic.Ordered, T storage.IndexDataItem[I]](dir string, generate func(name string, index I) T, generateZero func(name string) T, options ...IndexDataFileStorageOption[I, T]) *IndexDataFileStorage[I, T] { + s := &IndexDataFileStorage[I, T]{ + dir: dir, + suffix: IndexDataFileDefaultSuffix, + generate: generate, + generateZero: generateZero, + encoder: FileStorageJSONEncoder[T](), + decoder: FileStorageJSONDecoder[T](), + } + for _, option := range options { + option(s) + } + return s +} + +// IndexDataFileStorage 索引数据文件存储器 +type IndexDataFileStorage[I generic.Ordered, T storage.IndexDataItem[I]] struct { + dir string + suffix string + generate func(name string, index I) T + generateZero func(name string) T + encoder FileStorageEncoder[T] + decoder FileStorageDecoder[T] +} + +func (slf *IndexDataFileStorage[I, T]) Load(name string, index I) T { + bytes, err := file.ReadOnce(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix))) + if err != nil { + return slf.generate(name, index) + } + var data = slf.generate(name, index) + _ = slf.decoder(bytes, data) + return data +} + +func (slf *IndexDataFileStorage[I, T]) LoadAll(name string) map[I]T { + var result = make(map[I]T) + files, err := os.ReadDir(slf.dir) + if err != nil { + return result + } + for _, entry := range files { + if entry.IsDir() || !strings.HasPrefix(entry.Name(), name) || !strings.HasSuffix(entry.Name(), slf.suffix) { + continue + } + bytes, err := file.ReadOnce(filepath.Join(slf.dir, entry.Name())) + if err != nil { + continue + } + data := slf.generateZero(name) + if err := slf.decoder(bytes, data); err == nil { + result[data.GetIndex()] = data + } + } + return result +} + +func (slf *IndexDataFileStorage[I, T]) Save(name string, index I, data T) error { + bytes, err := slf.encoder(data) + if err != nil { + return err + } + return file.WriterFile(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix)), bytes) +} + +func (slf *IndexDataFileStorage[I, T]) SaveAll(name string, data map[I]T, errHandle func(err error) bool, retryInterval time.Duration) { + for index, data := range data { + for { + if err := slf.Save(name, index, data); err != nil { + if !errHandle(err) { + time.Sleep(retryInterval) + continue + } + break + } + break + } + } +} + +func (slf *IndexDataFileStorage[I, T]) Delete(name string, index I) { + _ = os.Remove(filepath.Join(slf.dir, fmt.Sprintf(indexNameFormat, name, index, slf.suffix))) +} + +func (slf *IndexDataFileStorage[I, T]) DeleteAll(name string) { + files, err := os.ReadDir(slf.dir) + if err != nil { + return + } + for _, entry := range files { + if entry.IsDir() || !strings.HasPrefix(entry.Name(), name) || !strings.HasSuffix(entry.Name(), slf.suffix) { + continue + } + _ = os.Remove(filepath.Join(slf.dir, entry.Name())) + } +} diff --git a/utils/storage/storages/index_data_file_options.go b/utils/storage/storages/index_data_file_options.go new file mode 100644 index 0000000..9d8bec1 --- /dev/null +++ b/utils/storage/storages/index_data_file_options.go @@ -0,0 +1,33 @@ +package storages + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "github.com/kercylan98/minotaur/utils/storage" +) + +// IndexDataFileStorageOption 索引数据文件存储器选项 +type IndexDataFileStorageOption[I generic.Ordered, T storage.IndexDataItem[I]] func(storage *IndexDataFileStorage[I, T]) + +// WithIndexDataFileStorageEncoder 设置编码器 +// - 默认为 FileStorageJSONEncoder 编码器 +func WithIndexDataFileStorageEncoder[I generic.Ordered, T storage.IndexDataItem[I]](encoder FileStorageEncoder[T]) IndexDataFileStorageOption[I, T] { + return func(storage *IndexDataFileStorage[I, T]) { + storage.encoder = encoder + } +} + +// WithIndexDataFileStorageDecoder 设置解码器 +// - 默认为 FileStorageJSONDecoder 解码器 +func WithIndexDataFileStorageDecoder[I generic.Ordered, T storage.IndexDataItem[I]](decoder FileStorageDecoder[T]) IndexDataFileStorageOption[I, T] { + return func(storage *IndexDataFileStorage[I, T]) { + storage.decoder = decoder + } +} + +// WithIndexDataFileStorageSuffix 设置文件后缀 +// - 默认为 IndexDataFileDefaultSuffix +func WithIndexDataFileStorageSuffix[I generic.Ordered, T storage.IndexDataItem[I]](suffix string) IndexDataFileStorageOption[I, T] { + return func(storage *IndexDataFileStorage[I, T]) { + storage.suffix = suffix + } +}