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 关闭玩家并且释放其资源 diff --git a/utils/storage/global_data.go b/utils/storage/global_data.go new file mode 100644 index 0000000..63eb32c --- /dev/null +++ b/utils/storage/global_data.go @@ -0,0 +1,51 @@ +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] // 全局数据存储器 + 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() { + slf.data = slf.storage.Load(slf.GetName()) +} + +// SaveData 保存数据 +func (slf *GlobalData[T]) SaveData() error { + return slf.storage.Save(slf.GetName(), slf.GetData()) +} + +// 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..872c368 --- /dev/null +++ b/utils/storage/global_data_storage.go @@ -0,0 +1,10 @@ +package storage + +// GlobalDataStorage 全局数据存储器接口 +type GlobalDataStorage[T any] interface { + // Load 加载全局数据 + // - 当全局数据不存在时,应当返回新的全局数据实例 + Load(name string) T + // Save 保存全局数据 + Save(name string, data T) error +} diff --git a/utils/storage/index_data.go b/utils/storage/index_data.go new file mode 100644 index 0000000..71ac801 --- /dev/null +++ b/utils/storage/index_data.go @@ -0,0 +1,92 @@ +package storage + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "time" +) + +// NewIndexData 创建索引数据 +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: storage.LoadAll(name), + } + return data +} + +// IndexData 全局数据 +type IndexData[I generic.Ordered, T IndexDataItem[I]] struct { + storage IndexDataStorage[I, 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 { + data, exist := slf.data[index] + if !exist { + slf.LoadData(index) + data = slf.data[index] + } + return data +} + +// GetAllData 获取所有数据 +func (slf *IndexData[I, T]) GetAllData() map[I]T { + return slf.data +} + +// LoadData 加载数据 +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(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 处理数据 +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_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 new file mode 100644 index 0000000..4ef0d74 --- /dev/null +++ b/utils/storage/index_data_storage.go @@ -0,0 +1,23 @@ +package storage + +import ( + "github.com/kercylan98/minotaur/utils/generic" + "time" +) + +// IndexDataStorage 全局数据存储器接口 +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) error + // SaveAll 保存所有数据 + 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) +} 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/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.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_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_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/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.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_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_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 + } +} 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) + }) +} 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] +} 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 +}