feat: storage 添加内置实现的文件存储器,可以通过 storages 包进行使用
This commit is contained in:
parent
f59354db3f
commit
c447c8afb3
|
@ -3,15 +3,17 @@ package storage
|
|||
// NewGlobalData 创建全局数据
|
||||
func NewGlobalData[T any](name string, storage GlobalDataStorage[T]) *GlobalData[T] {
|
||||
data := &GlobalData[T]{
|
||||
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 处理数据
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -2,20 +2,22 @@ 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]{
|
||||
storage: storage,
|
||||
name: name,
|
||||
data: map[I]T{},
|
||||
data: storage.LoadAll(name),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// IndexData 全局数据
|
||||
type IndexData[I generic.Ordered, T any] struct {
|
||||
storage GlobalDataStorage[T] // 存储器
|
||||
type IndexData[I generic.Ordered, T IndexDataItem[I]] struct {
|
||||
storage IndexDataStorage[I, T] // 存储器
|
||||
name string // 数据组名称
|
||||
data map[I]T // 数据
|
||||
}
|
||||
|
@ -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 处理数据
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package storage
|
||||
|
||||
import "github.com/kercylan98/minotaur/utils/generic"
|
||||
|
||||
type IndexDataItem[I generic.Ordered] interface {
|
||||
GetIndex() I
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue