From 8835e4a88bd80bb795a93dfe2494445d8acf0d95 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 18 Jul 2023 18:25:21 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20timer.CalcNextTimeWithRefer=20=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E4=B8=8B=E4=B8=80=E4=B8=AA=E6=95=B4=E7=82=B9=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/times/calc.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/utils/times/calc.go b/utils/times/calc.go index 83d6a87..ab693a9 100644 --- a/utils/times/calc.go +++ b/utils/times/calc.go @@ -17,3 +17,23 @@ func CalcNextSecWithTime(t time.Time, sec int) int { next := now + int64(sec) - now%int64(sec) return int(next - now) } + +// CalcNextTimeWithRefer 根据参考时间计算下一个整点时间 +// - 假设当 now 为 14:15:16 , 参考时间为 10 分钟, 则返回 14:20:00 +// - 假设当 now 为 14:15:16 , 参考时间为 20 分钟, 则返回 14:20:00 +// +// 当 refer 小于 1 分钟时,将会返回当前时间 +func CalcNextTimeWithRefer(now time.Time, refer time.Duration) time.Time { + referInSeconds := int(refer.Minutes()) * 60 + if referInSeconds <= 0 { + return now + } + + minutes := now.Minute() + seconds := now.Second() + + remainder := referInSeconds - (minutes*60+seconds)%referInSeconds + nextTime := now.Add(time.Duration(remainder) * time.Second) + nextTime = time.Date(nextTime.Year(), nextTime.Month(), nextTime.Day(), nextTime.Hour(), nextTime.Minute(), 0, 0, nextTime.Location()) + return nextTime +} From 31ad0ee4fbfe8c0fe1d4225c11b250559154d21c Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Tue, 18 Jul 2023 18:25:51 +0800 Subject: [PATCH 2/6] =?UTF-8?q?feat:=20builtin.Player=20=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E9=80=9A=E8=BF=87=20GetConn=20=E5=87=BD=E6=95=B0=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E7=BD=91=E7=BB=9C=E8=BF=9E=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/builtin/player.go | 4 ++++ game/player.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/game/builtin/player.go b/game/builtin/player.go index 3fd686e..265f06a 100644 --- a/game/builtin/player.go +++ b/game/builtin/player.go @@ -19,6 +19,10 @@ func (slf *Player[ID]) GetID() ID { return slf.id } +func (slf *Player[ID]) GetConn() *server.Conn { + return slf.conn +} + func (slf *Player[ID]) UseConn(conn *server.Conn) { if conn == nil { return diff --git a/game/player.go b/game/player.go index 5970467..f322136 100644 --- a/game/player.go +++ b/game/player.go @@ -6,6 +6,8 @@ import "github.com/kercylan98/minotaur/server" type Player[ID comparable] interface { // GetID 获取玩家ID GetID() ID + // GetConn 获取玩家连接 + GetConn() *server.Conn // UseConn 指定连接 UseConn(conn *server.Conn) // Close 关闭玩家并且释放其资源 From 1dcbd0a2203c5b0384969836bf4083f3fedce418 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 19 Jul 2023 11:07:06 +0800 Subject: [PATCH 3/6] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20super.RegError=20=E5=87=BD=E6=95=B0=E4=B8=BA?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=B3=A8=E5=86=8C=E5=85=A8=E5=B1=80=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=A0=81=EF=BC=8C=E4=BD=BF=E7=94=A8=20super.GetErrorC?= =?UTF-8?q?ode=20=E6=A0=B9=E6=8D=AE=E9=94=99=E8=AF=AF=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=85=A8=E5=B1=80=E9=94=99=E8=AF=AF=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/error.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 utils/super/error.go diff --git a/utils/super/error.go b/utils/super/error.go new file mode 100644 index 0000000..e89d276 --- /dev/null +++ b/utils/super/error.go @@ -0,0 +1,22 @@ +package super + +import ( + "errors" +) + +var errorMapper = make(map[error]int) + +// RegError 通过错误码注册错误,返回错误的引用 +func RegError(code int, message string) error { + if code == 0 { + panic("error code can not be 0") + } + err := errors.New(message) + errorMapper[err] = code + return err +} + +// GetErrorCode 通过错误引用获取错误码,如果错误不存在则返回 0 +func GetErrorCode(err error) int { + return errorMapper[err] +} From f59354db3f244e76faf3590f6865088e5ed6e226 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 19 Jul 2023 12:12:30 +0800 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20storage=20?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E6=95=B0=E6=8D=AE=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/storage/global_data.go | 49 +++++++++++++++++++++++ utils/storage/global_data_storage.go | 22 ++++++++++ utils/storage/index_data.go | 58 +++++++++++++++++++++++++++ utils/storage/index_data_storage.go | 60 ++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+) create mode 100644 utils/storage/global_data.go create mode 100644 utils/storage/global_data_storage.go create mode 100644 utils/storage/index_data.go create mode 100644 utils/storage/index_data_storage.go diff --git a/utils/storage/global_data.go b/utils/storage/global_data.go new file mode 100644 index 0000000..ed432e0 --- /dev/null +++ b/utils/storage/global_data.go @@ -0,0 +1,49 @@ +package storage + +// NewGlobalData 创建全局数据 +func NewGlobalData[T any](name string, storage GlobalDataStorage[T]) *GlobalData[T] { + data := &GlobalData[T]{ + name: name, + data: storage.Load(name), + } + return data +} + +// GlobalData 全局数据 +type GlobalData[T any] struct { + storage GlobalDataStorage[T] // 存储器 + name string // 全局数据名称 + data T // 数据 +} + +// GetName 获取名称 +func (slf *GlobalData[T]) GetName() string { + return slf.name +} + +// GetData 获取数据 +func (slf *GlobalData[T]) GetData() T { + return slf.data +} + +// LoadData 加载数据 +func (slf *GlobalData[T]) LoadData(storage GlobalDataStorage[T]) *GlobalData[T] { + return LoadGlobalData(slf, storage) +} + +// SaveData 保存数据 +func (slf *GlobalData[T]) SaveData(storage GlobalDataStorage[T]) *GlobalData[T] { + return SaveGlobalData(slf, storage) +} + +// Handle 处理数据 +func (slf *GlobalData[T]) Handle(handler func(name string, data T)) *GlobalData[T] { + handler(slf.GetName(), slf.GetData()) + return slf +} + +// HandleWithCallback 处理数据 +func (slf *GlobalData[T]) HandleWithCallback(handler func(name string, data T) error, callback func(err error)) *GlobalData[T] { + callback(handler(slf.GetName(), slf.GetData())) + return slf +} diff --git a/utils/storage/global_data_storage.go b/utils/storage/global_data_storage.go new file mode 100644 index 0000000..2c0e50e --- /dev/null +++ b/utils/storage/global_data_storage.go @@ -0,0 +1,22 @@ +package storage + +// GlobalDataStorage 全局数据存储器接口 +type GlobalDataStorage[T any] interface { + // Load 加载全局数据 + // - 当全局数据不存在时,应当返回新的全局数据实例 + 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 +} diff --git a/utils/storage/index_data.go b/utils/storage/index_data.go new file mode 100644 index 0000000..a41a8c4 --- /dev/null +++ b/utils/storage/index_data.go @@ -0,0 +1,58 @@ +package storage + +import ( + "github.com/kercylan98/minotaur/utils/generic" +) + +// NewIndexData 创建索引数据 +func NewIndexData[I generic.Ordered, T any](name string, storage IndexDataStorage[I, T]) *IndexData[I, T] { + data := &IndexData[I, T]{ + name: name, + data: map[I]T{}, + } + return data +} + +// IndexData 全局数据 +type IndexData[I generic.Ordered, T any] struct { + storage GlobalDataStorage[T] // 存储器 + name string // 数据组名称 + data map[I]T // 数据 +} + +// GetName 获取名称 +func (slf *IndexData[I, T]) GetName() string { + return slf.name +} + +// GetData 获取数据 +func (slf *IndexData[I, T]) GetData(index I) T { + return slf.data[index] +} + +// GetAllData 获取所有数据 +func (slf *IndexData[I, T]) GetAllData() map[I]T { + return slf.data +} + +// LoadData 加载数据 +func (slf *IndexData[I, T]) LoadData(index I, storage IndexDataStorage[I, T]) *IndexData[I, T] { + return LoadIndexData(slf, index, storage) +} + +// SaveData 保存数据 +func (slf *IndexData[I, T]) SaveData(storage IndexDataStorage[I, T], index I) *IndexData[I, T] { + return SaveIndexData(slf, index, storage) +} + +// Handle 处理数据 +func (slf *IndexData[I, T]) Handle(index I, handler func(name string, index I, data T)) *IndexData[I, T] { + handler(slf.GetName(), index, slf.GetData(index)) + return slf +} + +// HandleWithCallback 处理数据 +func (slf *IndexData[I, T]) HandleWithCallback(index I, handler func(name string, index I, data T) error, callback func(err error)) *IndexData[I, T] { + callback(handler(slf.GetName(), index, slf.GetData(index))) + return slf +} diff --git a/utils/storage/index_data_storage.go b/utils/storage/index_data_storage.go new file mode 100644 index 0000000..e1238c4 --- /dev/null +++ b/utils/storage/index_data_storage.go @@ -0,0 +1,60 @@ +package storage + +import "github.com/kercylan98/minotaur/utils/generic" + +// IndexDataStorage 全局数据存储器接口 +type IndexDataStorage[I generic.Ordered, T any] interface { + // Load 加载特定索引数据 + // - 通常情况下当数据不存在时,应当返回空指针 + Load(name string, index I) T + // LoadAll 加载所有数据 + LoadAll(name string) map[I]T + // Save 保存特定索引数据 + Save(name string, index I, data T) + // SaveAll 保存所有数据 + SaveAll(name string, data map[I]T) + // 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 +} From c447c8afb395558a2dd85117b3fac8e093a8cfa7 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 19 Jul 2023 14:56:52 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat:=20storage=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=86=85=E7=BD=AE=E5=AE=9E=E7=8E=B0=E7=9A=84=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=99=A8=EF=BC=8C=E5=8F=AF=E4=BB=A5=E9=80=9A?= =?UTF-8?q?=E8=BF=87=20storages=20=E5=8C=85=E8=BF=9B=E8=A1=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/storage/global_data.go | 16 +-- utils/storage/global_data_storage.go | 14 +-- utils/storage/index_data.go | 58 +++++++-- utils/storage/index_data_item.go | 7 ++ utils/storage/index_data_storage.go | 51 ++------ utils/storage/storage.go | 26 ++++ utils/storage/storages/encode.go | 23 ++++ utils/storage/storages/global_data_file.go | 63 ++++++++++ .../storages/global_data_file_options.go | 28 +++++ utils/storage/storages/index_data_file.go | 117 ++++++++++++++++++ .../storages/index_data_file_options.go | 33 +++++ 11 files changed, 360 insertions(+), 76 deletions(-) create mode 100644 utils/storage/index_data_item.go create mode 100644 utils/storage/storage.go create mode 100644 utils/storage/storages/encode.go create mode 100644 utils/storage/storages/global_data_file.go create mode 100644 utils/storage/storages/global_data_file_options.go create mode 100644 utils/storage/storages/index_data_file.go create mode 100644 utils/storage/storages/index_data_file_options.go 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 + } +} From 4378aa0eb79f08052121c7ec6f3648aa2248d3dd Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Wed, 19 Jul 2023 14:57:20 +0800 Subject: [PATCH 6/6] =?UTF-8?q?test:=20=E6=96=B0=E5=A2=9E=20GlobalDataFile?= =?UTF-8?q?Storage=20=E5=92=8C=20IndexDataFileStorage=20=E7=9A=84=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example-data/global_data_file_test.stock | 1 + .../index_data_file_test.INDEX_001.stock | 1 + .../storages/global_data_file_example_test.go | 19 ++++++++ .../storage/storages/global_data_file_test.go | 42 +++++++++++++++++ .../storages/index_data_file_example_test.go | 18 ++++++++ .../storage/storages/index_data_file_test.go | 45 +++++++++++++++++++ 6 files changed, 126 insertions(+) create mode 100644 utils/storage/storages/example-data/global_data_file_test.stock create mode 100644 utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock create mode 100644 utils/storage/storages/global_data_file_example_test.go create mode 100644 utils/storage/storages/global_data_file_test.go create mode 100644 utils/storage/storages/index_data_file_example_test.go create mode 100644 utils/storage/storages/index_data_file_test.go diff --git a/utils/storage/storages/example-data/global_data_file_test.stock b/utils/storage/storages/example-data/global_data_file_test.stock new file mode 100644 index 0000000..7359430 --- /dev/null +++ b/utils/storage/storages/example-data/global_data_file_test.stock @@ -0,0 +1 @@ +{"CreateAt":"2023-07-19T14:49:35.7235348+08:00","TotalCount":10} \ No newline at end of file diff --git a/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock b/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock new file mode 100644 index 0000000..c730b47 --- /dev/null +++ b/utils/storage/storages/example-data/index_data_file_test.INDEX_001.stock @@ -0,0 +1 @@ +{"ID":"INDEX_001","Value":10} \ No newline at end of file diff --git a/utils/storage/storages/global_data_file_example_test.go b/utils/storage/storages/global_data_file_example_test.go new file mode 100644 index 0000000..a9f3186 --- /dev/null +++ b/utils/storage/storages/global_data_file_example_test.go @@ -0,0 +1,19 @@ +package storages_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/storage/storages" + "time" +) + +func ExampleNewGlobalDataFileStorage() { + storage := storages.NewGlobalDataFileStorage[*GlobalData](".", func(name string) *GlobalData { + return &GlobalData{ + CreateAt: time.Now(), + } + }) + + fmt.Println(storage != nil) + // Output: + // true +} diff --git a/utils/storage/storages/global_data_file_test.go b/utils/storage/storages/global_data_file_test.go new file mode 100644 index 0000000..9241893 --- /dev/null +++ b/utils/storage/storages/global_data_file_test.go @@ -0,0 +1,42 @@ +package storages_test + +import ( + "github.com/kercylan98/minotaur/utils/storage" + "github.com/kercylan98/minotaur/utils/storage/storages" + . "github.com/smartystreets/goconvey/convey" + "testing" + "time" +) + +type GlobalData struct { + CreateAt time.Time + TotalCount int +} + +func TestGlobalDataFileStorage_Save(t *testing.T) { + Convey("TestGlobalDataFileStorage_Save", t, func() { + data := storage.NewGlobalData[*GlobalData]("global_data_file_test", storages.NewGlobalDataFileStorage[*GlobalData]("./example-data", func(name string) *GlobalData { + return &GlobalData{ + CreateAt: time.Now(), + } + })) + data.Handle(func(name string, data *GlobalData) { + data.TotalCount = 10 + }) + if err := data.SaveData(); err != nil { + t.Fatal(err) + } + So(data.GetData().TotalCount, ShouldEqual, 10) + }) +} + +func TestGlobalDataFileStorage_Load(t *testing.T) { + Convey("TestGlobalDataFileStorage_Load", t, func() { + data := storage.NewGlobalData[*GlobalData]("global_data_file_test", storages.NewGlobalDataFileStorage[*GlobalData]("./example-data", func(name string) *GlobalData { + return &GlobalData{ + CreateAt: time.Now(), + } + })) + So(data.GetData().TotalCount, ShouldEqual, 10) + }) +} diff --git a/utils/storage/storages/index_data_file_example_test.go b/utils/storage/storages/index_data_file_example_test.go new file mode 100644 index 0000000..f6c865d --- /dev/null +++ b/utils/storage/storages/index_data_file_example_test.go @@ -0,0 +1,18 @@ +package storages_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/storage/storages" +) + +func ExampleNewIndexDataFileStorage() { + storage := storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { + return &IndexData[string]{ID: index} + }, func(name string) *IndexData[string] { + return new(IndexData[string]) + }) + + fmt.Println(storage != nil) + // Output: + // true +} diff --git a/utils/storage/storages/index_data_file_test.go b/utils/storage/storages/index_data_file_test.go new file mode 100644 index 0000000..5e12649 --- /dev/null +++ b/utils/storage/storages/index_data_file_test.go @@ -0,0 +1,45 @@ +package storages_test + +import ( + "github.com/kercylan98/minotaur/utils/storage" + "github.com/kercylan98/minotaur/utils/storage/storages" + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +type IndexData[I string] struct { + ID I + Value int +} + +func (slf *IndexData[I]) GetIndex() I { + return slf.ID +} + +func TestIndexDataFileStorage_Save(t *testing.T) { + Convey("TestIndexDataFileStorage_Save", t, func() { + data := storage.NewIndexData[string, *IndexData[string]]("index_data_file_test", storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { + return &IndexData[string]{ID: index} + }, func(name string) *IndexData[string] { + return new(IndexData[string]) + })) + data.Handle("INDEX_001", func(name string, index string, data *IndexData[string]) { + data.Value = 10 + }) + if err := data.SaveData("INDEX_001"); err != nil { + t.Fatal(err) + } + So(data.GetData("INDEX_001").Value, ShouldEqual, 10) + }) +} + +func TestIndexDataFileStorage_Load(t *testing.T) { + Convey("TestIndexDataFileStorage_Load", t, func() { + data := storage.NewIndexData[string, *IndexData[string]]("index_data_file_test", storages.NewIndexDataFileStorage[string, *IndexData[string]]("./example-data", func(name string, index string) *IndexData[string] { + return &IndexData[string]{ID: index} + }, func(name string) *IndexData[string] { + return new(IndexData[string]) + })) + So(data.GetData("INDEX_001").Value, ShouldEqual, 10) + }) +}