diff --git a/config/config.go b/config/config.go deleted file mode 100644 index a212b34..0000000 --- a/config/config.go +++ /dev/null @@ -1,90 +0,0 @@ -package config - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/timer" - "go.uber.org/zap" - "os" - "path/filepath" - "sync" - "time" -) - -// LoadHandle 配置加载处理函数 -type LoadHandle func(handle func(filename string, config any) error) - -// RefreshHandle 配置刷新处理函数 -type RefreshHandle func() - -const ( - tickerLoadRefresh = "_tickerLoadRefresh" -) - -var ( - cLoadDir string - cTicker *timer.Ticker - cInterval time.Duration - cLoadHandle LoadHandle - cRefreshHandle RefreshHandle - json = jsonIter.ConfigCompatibleWithStandardLibrary - mutex sync.Mutex - isInit = true -) - -// Init 配置初始化 -func Init(loadDir string, loadHandle LoadHandle, refreshHandle RefreshHandle) { - cLoadDir = loadDir - cLoadHandle = loadHandle - cRefreshHandle = refreshHandle - Load() - Refresh() -} - -// Load 加载配置 -// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 -func Load() { - mutex.Lock() - cLoadHandle(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath.Join(cLoadDir, filename)) - if err != nil { - return err - } - if err = json.Unmarshal(bytes, &config); err == nil && isInit { - log.Info("Config", zap.String("Name", filename), zap.Bool("LoadSuccess", true)) - } - return err - }) - isInit = false - mutex.Unlock() -} - -// WithTickerLoad 通过定时器加载配置 -// - 通过定时器加载配置后,会自动刷新线上配置 -// - 调用该函数后将会立即加载并刷新一次配置,随后每隔 interval 时间加载并刷新一次配置 -func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) { - if ticker != cTicker && cTicker != nil { - cTicker.StopTimer(tickerLoadRefresh) - } - cTicker = ticker - cInterval = interval - cTicker.Loop(tickerLoadRefresh, timer.Instantly, cInterval, timer.Forever, func() { - Load() - Refresh() - }) -} - -// StopTickerLoad 停止通过定时器加载配置 -func StopTickerLoad() { - if cTicker != nil { - cTicker.StopTimer(tickerLoadRefresh) - } -} - -// Refresh 刷新配置 -func Refresh() { - mutex.Lock() - cRefreshHandle() - OnConfigRefreshEvent() - mutex.Unlock() -} diff --git a/config/doc.go b/config/doc.go deleted file mode 100644 index 09bd1ab..0000000 --- a/config/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package config 基于配置导表功能实现的配置加载及刷新功能 -package config diff --git a/configuration/config.go b/configuration/config.go new file mode 100644 index 0000000..506a72d --- /dev/null +++ b/configuration/config.go @@ -0,0 +1,73 @@ +package configuration + +import ( + "github.com/kercylan98/minotaur/utils/log" + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +const ( + tickerLoadRefresh = "_tickerLoadRefresh" +) + +var ( + cTicker *timer.Ticker + cInterval time.Duration + cLoader []Loader +) + +// Init 配置初始化 +// - 在初始化后会立即加载配置 +func Init(loader ...Loader) { + cLoader = loader + Load() + Refresh() +} + +// Load 加载配置 +// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 +func Load() { + defer func() { + if err := recover(); err != nil { + log.Error("Config", log.String("Action", "Load"), log.Err(err.(error))) + } + }() + for _, loader := range cLoader { + loader.Load() + } +} + +// Refresh 刷新配置 +func Refresh() { + defer func() { + if err := recover(); err != nil { + log.Error("Config", log.String("Action", "Refresh"), log.Err(err.(error))) + } + OnConfigRefreshEvent() + }() + for _, loader := range cLoader { + loader.Refresh() + } +} + +// WithTickerLoad 通过定时器加载配置 +// - 通过定时器加载配置后,会自动刷新线上配置 +// - 调用该函数后不会立即刷新,而是在 interval 后加载并刷新一次配置,之后每隔 interval 加载并刷新一次配置 +func WithTickerLoad(ticker *timer.Ticker, interval time.Duration) { + if ticker != cTicker && cTicker != nil { + cTicker.StopTimer(tickerLoadRefresh) + } + cTicker = ticker + cInterval = interval + cTicker.Loop(tickerLoadRefresh, cInterval, cInterval, timer.Forever, func() { + Load() + Refresh() + }) +} + +// StopTickerLoad 停止通过定时器加载配置 +func StopTickerLoad() { + if cTicker != nil { + cTicker.StopTimer(tickerLoadRefresh) + } +} diff --git a/configuration/doc.go b/configuration/doc.go new file mode 100644 index 0000000..a1249c9 --- /dev/null +++ b/configuration/doc.go @@ -0,0 +1,2 @@ +// Package configuration 基于配置导表功能实现的配置加载及刷新功能 +package configuration diff --git a/config/event.go b/configuration/event.go similarity index 95% rename from config/event.go rename to configuration/event.go index e4d1776..5a47932 100644 --- a/config/event.go +++ b/configuration/event.go @@ -1,4 +1,4 @@ -package config +package configuration // RefreshEventHandle 配置刷新事件处理函数 type RefreshEventHandle func() diff --git a/configuration/loader.go b/configuration/loader.go new file mode 100644 index 0000000..69723ff --- /dev/null +++ b/configuration/loader.go @@ -0,0 +1,10 @@ +package configuration + +// Loader 配置加载器 +type Loader interface { + // Load 加载配置 + // - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新 + Load() + // Refresh 刷新线上配置 + Refresh() +} diff --git a/game/builtin/attrs.go b/game/builtin/attrs.go index 505bf17..04b2fcd 100644 --- a/game/builtin/attrs.go +++ b/game/builtin/attrs.go @@ -272,7 +272,7 @@ func (slf *Attrs) GetAttrInt64(id int) int64 { case int32: return int64(value) case int64: - return int64(value) + return value } return 0 } diff --git a/game/builtin/room.go b/game/builtin/room.go index 546db04..457d5b0 100644 --- a/game/builtin/room.go +++ b/game/builtin/room.go @@ -5,7 +5,6 @@ import ( "github.com/kercylan98/minotaur/utils/asynchronous" "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" ) // NewRoom 创建一个默认的内置游戏房间 Room @@ -90,7 +89,7 @@ func (slf *Room[PlayerID, Player]) Join(player Player) error { } slf.players.Set(playerId, player) if !exist { - log.Debug("Room.Join", zap.Any("guid", slf.GetGuid()), zap.Any("player", playerId)) + log.Debug("Room.Join", log.Any("guid", slf.GetGuid()), log.Any("player", playerId)) if slf.players.Size() == 1 && !slf.noMaster { slf.owner = playerId } @@ -105,7 +104,7 @@ func (slf *Room[PlayerID, Player]) Leave(id PlayerID) { if !exist { return } - log.Debug("Room.Leave", zap.Any("guid", slf.GetGuid()), zap.Any("player", id)) + log.Debug("Room.Leave", log.Any("guid", slf.GetGuid()), log.Any("player", id)) slf.OnPlayerLeaveRoomEvent(player) slf.players.Delete(id) } diff --git a/game/builtin/world.go b/game/builtin/world.go index 34d69c9..463b76d 100644 --- a/game/builtin/world.go +++ b/game/builtin/world.go @@ -5,7 +5,6 @@ import ( "github.com/kercylan98/minotaur/utils/hash" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/synchronization" - "go.uber.org/zap" "sync/atomic" ) @@ -103,7 +102,7 @@ func (slf *World[PlayerID, Player]) Join(player Player) error { if slf.players.Size() >= slf.playerLimit && slf.playerLimit > 0 { return ErrWorldPlayerLimit } - log.Debug("World.Join", zap.Int64("guid", slf.GetGuid()), zap.Any("player", player.GetID())) + log.Debug("World.Join", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID())) slf.players.Set(player.GetID(), player) if actors := slf.playerActors.Get(player.GetID()); actors == nil { actors = synchronization.NewMap[int64, game.Actor]() @@ -118,7 +117,7 @@ func (slf *World[PlayerID, Player]) Leave(id PlayerID) { if !exist { return } - log.Debug("World.Leave", zap.Int64("guid", slf.GetGuid()), zap.Any("player", player.GetID())) + log.Debug("World.Leave", log.Int64("guid", slf.GetGuid()), log.Any("player", player.GetID())) slf.OnPlayerLeaveWorldEvent(player) slf.playerActors.Get(player.GetID()).Range(func(guid int64, actor game.Actor) { slf.OnActorAnnihilationEvent(actor) @@ -170,7 +169,7 @@ func (slf *World[PlayerID, Player]) RemoveActorOwner(guid int64) { } func (slf *World[PlayerID, Player]) Reset() { - log.Debug("World", zap.Int64("Reset", slf.guid)) + log.Debug("World", log.Int64("Reset", slf.guid)) slf.players.Clear() slf.playerActors.Range(func(id PlayerID, actors hash.Map[int64, game.Actor]) { actors.Clear() @@ -184,7 +183,7 @@ func (slf *World[PlayerID, Player]) Reset() { func (slf *World[PlayerID, Player]) Release() { if !slf.released.Swap(true) { - log.Debug("World", zap.Int64("Release", slf.guid)) + log.Debug("World", log.Int64("Release", slf.guid)) slf.OnWorldReleaseEvent() slf.Reset() slf.players = nil diff --git a/notify/manager.go b/notify/manager.go index a235d98..4c4829e 100644 --- a/notify/manager.go +++ b/notify/manager.go @@ -2,7 +2,6 @@ package notify import ( "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" "reflect" ) @@ -20,19 +19,19 @@ func NewManager(senders ...Sender) *Manager { case <-manager.closeChannel: close(manager.closeChannel) close(manager.notifyChannel) - log.Info("Manager", zap.String("state", "release")) + log.Info("Manager", log.String("state", "release")) return case notify := <-manager.notifyChannel: for _, sender := range manager.senders { if err := sender.Push(notify); err != nil { - log.Error("Manager", zap.String("sender", reflect.TypeOf(sender).String()), zap.Error(err)) + log.Error("Manager", log.String("sender", reflect.TypeOf(sender).String()), log.Err(err)) } } } } }() - log.Info("Manager", zap.String("state", "running")) + log.Info("Manager", log.String("state", "running")) return manager } diff --git a/planner/configexport/configexport.go b/planner/configexport/configexport.go deleted file mode 100644 index 006c283..0000000 --- a/planner/configexport/configexport.go +++ /dev/null @@ -1,212 +0,0 @@ -package configexport - -import ( - "bytes" - "fmt" - "github.com/kercylan98/minotaur/planner/configexport/internal" - "github.com/kercylan98/minotaur/utils/file" - "github.com/kercylan98/minotaur/utils/log" - "github.com/kercylan98/minotaur/utils/str" - "github.com/tealeg/xlsx" - "go.uber.org/zap" - "os/exec" - "path/filepath" - "strings" - "text/template" -) - -// New 创建一个导表配置 -func New(xlsxPath string) *ConfigExport { - ce := &ConfigExport{xlsxPath: xlsxPath, exist: make(map[string]bool)} - xlsxFile, err := xlsx.OpenFile(xlsxPath) - if err != nil { - panic(err) - } - for i := 0; i < len(xlsxFile.Sheets); i++ { - sheet := xlsxFile.Sheets[i] - if config, err := internal.NewConfig(sheet, ce.exist); err != nil { - switch err { - case internal.ErrReadConfigFailedSame: - log.Warn("ConfigExport", - zap.String("File", xlsxPath), - zap.String("Sheet", sheet.Name), - zap.String("Info", "A configuration with the same name exists, skipped"), - ) - case internal.ErrReadConfigFailedIgnore: - log.Info("ConfigExport", - zap.String("File", xlsxPath), - zap.String("Sheet", sheet.Name), - zap.String("Info", "Excluded non-configuration table files"), - ) - default: - log.ErrorHideStack("ConfigExport", - zap.String("File", xlsxPath), - zap.String("Sheet", sheet.Name), - zap.String("Info", "Excluded non-configuration table files"), - ) - } - } else { - if config == nil { - continue - } - ce.configs = append(ce.configs, config) - ce.exist[config.Name] = true - - log.Info("ConfigExport", - zap.String("File", xlsxPath), - zap.String("Sheet", sheet.Name), - zap.String("Info", "Export successfully"), - ) - } - } - return ce -} - -type ConfigExport struct { - xlsxPath string - configs []*internal.Config - exist map[string]bool -} - -// Merge 合并多个导表配置 -func Merge(exports ...*ConfigExport) *ConfigExport { - if len(exports) == 0 { - return nil - } - if len(exports) == 1 { - return exports[0] - } - var export = exports[0] - for i := 1; i < len(exports); i++ { - ce := exports[i] - for _, config := range ce.configs { - if _, ok := export.exist[config.Name]; ok { - log.Warn("ConfigExport", - zap.String("File", ce.xlsxPath), - zap.String("Sheet", config.Name), - zap.String("Info", "A configuration with the same name exists, skipped"), - ) - continue - } - export.configs = append(export.configs, config) - export.exist[config.Name] = true - } - } - return export -} - -func (slf *ConfigExport) ExportClient(prefix, outputDir string) { - for _, config := range slf.configs { - config := config - if len(prefix) > 0 { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - if err := file.WriterFile(filepath.Join(outputDir, fmt.Sprintf("%s%s.json", config.Prefix, config.Name)), config.JsonClient()); err != nil { - panic(err) - } - } -} - -func (slf *ConfigExport) ExportServer(prefix, outputDir string) { - for _, config := range slf.configs { - config := config - if len(prefix) > 0 { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - if err := file.WriterFile(filepath.Join(outputDir, fmt.Sprintf("%s%s.json", config.Prefix, config.Name)), config.JsonServer()); err != nil { - panic(err) - } - } -} - -func (slf *ConfigExport) ExportGo(prefix, outputDir string) { - if len(prefix) > 0 { - for _, config := range slf.configs { - config.Prefix = fmt.Sprintf("%s.", prefix) - } - } - slf.exportGoConfig(outputDir) - slf.exportGoDefine(outputDir) -} - -func (slf *ConfigExport) exportGoConfig(outputDir string) { - var v struct { - Package string - Configs []*internal.Config - } - v.Package = filepath.Base(outputDir) - - for _, config := range slf.configs { - v.Configs = append(v.Configs, config) - } - - tmpl, err := template.New("struct").Parse(internal.GenerateGoConfigTemplate) - if err != nil { - panic(err) - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, &v); err != nil { - panic(err) - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - - filePath := filepath.Join(outputDir, "config.go") - if err := file.WriterFile(filePath, []byte(result)); err != nil { - panic(err) - } - - cmd := exec.Command("gofmt", "-w", filePath) - _ = cmd.Run() -} - -func (slf *ConfigExport) exportGoDefine(outputDir string) { - var v struct { - Package string - Configs []*internal.Config - } - v.Package = filepath.Base(outputDir) - - for _, config := range slf.configs { - v.Configs = append(v.Configs, config) - } - - tmpl, err := template.New("struct").Parse(internal.GenerateGoDefineTemplate) - if err != nil { - panic(err) - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, &v); err != nil { - panic(err) - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - - filePath := filepath.Join(outputDir, "config.define.go") - if err := file.WriterFile(filePath, []byte(result)); err != nil { - panic(err) - } - - cmd := exec.Command("gofmt", "-w", filePath) - _ = cmd.Run() -} diff --git a/planner/configexport/configexport_example_test.go b/planner/configexport/configexport_example_test.go deleted file mode 100644 index d1d9c6b..0000000 --- a/planner/configexport/configexport_example_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package configexport_test - -import ( - "fmt" - "github.com/kercylan98/minotaur/config" - "github.com/kercylan98/minotaur/planner/configexport" - "github.com/kercylan98/minotaur/planner/configexport/example" - "os" - "path/filepath" - "strings" -) - -func ExampleNew() { - var workdir = "./" - files, err := os.ReadDir(workdir) - if err != nil { - panic(err) - } - var ces []*configexport.ConfigExport - for _, file := range files { - if file.IsDir() || !strings.HasSuffix(file.Name(), ".xlsx") || strings.HasPrefix(file.Name(), "~") { - continue - } - - ces = append(ces, configexport.New(filepath.Join(workdir, file.Name()))) - } - - c := configexport.Merge(ces...) - outDir := filepath.Join(workdir, "example") - c.ExportGo("", outDir) - c.ExportServer("", outDir) - c.ExportClient("", outDir) - - // 下方为配置加载代码 - // 使用生成的 LoadConfig 函数加载配置 - config.Init(outDir, example.LoadConfig, example.Refresh) - - fmt.Println("success") - - // Output: - // success -} diff --git a/planner/configexport/doc.go b/planner/configexport/doc.go deleted file mode 100644 index 59faff4..0000000 --- a/planner/configexport/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package configexport 提供了XLSX配置转换为JSON及Go代码的导表工具实现 -package configexport diff --git a/planner/configexport/example/EasyConfig.json b/planner/configexport/example/EasyConfig.json deleted file mode 100644 index 79f1fe2..0000000 --- a/planner/configexport/example/EasyConfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "Id": 1, - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } -} \ No newline at end of file diff --git a/planner/configexport/example/IndexConfig.json b/planner/configexport/example/IndexConfig.json deleted file mode 100644 index 1118e0e..0000000 --- a/planner/configexport/example/IndexConfig.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "0": { - "": { - "Award": null, - "Other": null, - "Id": 0, - "Count": "" - } - }, - "1": { - "b": { - "Id": 1, - "Count": "b", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "name": "刘备", - "id": 2 - } - } - } - }, - "2": { - "c": { - "Id": 2, - "Count": "c", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - }, - "d": { - "Id": 2, - "Count": "d", - "Award": { - "0": "asd", - "1": "12" - }, - "Other": { - "0": { - "id": 1, - "name": "张飞" - }, - "1": { - "id": 2, - "name": "刘备" - } - } - } - } -} \ No newline at end of file diff --git a/planner/configexport/example/config.define.go b/planner/configexport/example/config.define.go deleted file mode 100644 index 30814af..0000000 --- a/planner/configexport/example/config.define.go +++ /dev/null @@ -1,75 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package example - -// IndexConfigDefine 有索引 -type IndexConfigDefine struct { - Id int // 任务ID - Count string // 次数 - Info *IndexConfigInfo // 信息 - Other map[int]*IndexConfigOther // 信息2 -} - -func (slf *IndexConfigDefine) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - - return "{}" -} - -type IndexConfigInfo struct { - Id int - Name string - Info *IndexConfigInfoInfo -} - -type IndexConfigInfoInfo struct { - Lv int - Exp *IndexConfigInfoInfoExp -} - -type IndexConfigInfoInfoExp struct { - Mux int - Count int -} - -type IndexConfigOther struct { - Id int - Name string -} - -// EasyConfigDefine 无索引 -type EasyConfigDefine struct { - Id int // 任务ID - Info *EasyConfigInfo // 信息 - Other map[int]*EasyConfigOther // 信息2 -} - -func (slf *EasyConfigDefine) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - - return "{}" -} - -type EasyConfigInfo struct { - Id int - Name string - Info *EasyConfigInfoInfo -} - -type EasyConfigInfoInfo struct { - Lv int - Exp *EasyConfigInfoInfoExp -} - -type EasyConfigInfoInfoExp struct { - Mux int - Count int -} - -type EasyConfigOther struct { - Id int - Name string -} diff --git a/planner/configexport/example/config.go b/planner/configexport/example/config.go deleted file mode 100644 index 7fedfdc..0000000 --- a/planner/configexport/example/config.go +++ /dev/null @@ -1,63 +0,0 @@ -// Code generated by minotaur-config-export. DO NOT EDIT. -package example - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "os" -) - -var json = jsonIter.ConfigCompatibleWithStandardLibrary -var full map[string]any -var ( - // IndexConfig 有索引 - IndexConfigSign = "IndexConfig" - IndexConfig map[int]map[string]*IndexConfigDefine - _IndexConfig map[int]map[string]*IndexConfigDefine - // EasyConfig 无索引 - EasyConfigSign = "EasyConfig" - EasyConfig *EasyConfigDefine - _EasyConfig *EasyConfigDefine -) - -func LoadConfig(handle func(filename string, config any) error) { - var err error - _IndexConfig = make(map[int]map[string]*IndexConfigDefine) - if err = handle("IndexConfig.json", &_IndexConfig); err != nil { - log.Error("Config", zap.String("Name", "IndexConfig"), zap.Bool("Invalid", true), zap.Error(err)) - } - - _EasyConfig = new(EasyConfigDefine) - if err = handle("EasyConfig.json", _EasyConfig); err != nil { - log.Error("Config", zap.String("Name", "EasyConfig"), zap.Bool("Invalid", true), zap.Error(err)) - } - -} - -// Refresh 将加载后的配置刷新到线上 -func Refresh() { - full = make(map[string]any) - IndexConfig = _IndexConfig - full["IndexConfig"] = IndexConfig - EasyConfig = _EasyConfig - full["EasyConfig"] = EasyConfig -} - -// DefaultLoad 默认提供的配置加载函数 -func DefaultLoad(filepath string) { - LoadConfig(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath) - if err != nil { - return err - } - - return json.Unmarshal(bytes, &config) - }) -} - -// GetFull 获取所有配置的 map 集合 -// - 通常用于前端配置通过后端接口获取的情况 -func GetFull() map[string]any { - return full -} diff --git a/planner/configexport/internal/config.go b/planner/configexport/internal/config.go deleted file mode 100644 index 218b42c..0000000 --- a/planner/configexport/internal/config.go +++ /dev/null @@ -1,423 +0,0 @@ -package internal - -import ( - "bytes" - "fmt" - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/geometry/matrix" - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/str" - "github.com/kercylan98/minotaur/utils/xlsxtool" - "github.com/tealeg/xlsx" - "strconv" - "strings" - "text/template" -) - -// NewConfig 定位为空将读取Sheet名称 -// - 表格中需要严格遵守 描述、名称、类型、导出参数、数据列的顺序 -func NewConfig(sheet *xlsx.Sheet, exist map[string]bool) (*Config, error) { - config := &Config{ - ignore: "#", - excludeFields: map[int]bool{0: true}, - Exist: exist, - } - if strings.HasPrefix(sheet.Name, config.ignore) { - return nil, ErrReadConfigFailedIgnore - } - if err := config.initField(sheet); err != nil { - return nil, err - } - return config, nil -} - -type Config struct { - Exist map[string]bool - Prefix string - DisplayName string - Name string - Describe string - ExportParam string - IndexCount int - Fields []*Field - - matrix *matrix.Matrix[*xlsx.Cell] - excludeFields map[int]bool // 排除的字段 - ignore string - horizontal bool - dataStart int - - dataServer map[any]any - dataClient map[any]any -} - -func (slf *Config) initField(sheet *xlsx.Sheet) error { - slf.matrix = xlsxtool.GetSheetMatrix(sheet) - var displayName *Position - name, indexCount := NewPosition(1, 0), NewPosition(1, 1) - if displayName == nil { - slf.DisplayName = sheet.Name - } else { - if value := slf.matrix.Get(displayName.X, displayName.Y); value == nil { - return ErrReadConfigFailedWithDisplayName - } else { - slf.DisplayName = strings.TrimSpace(value.String()) - } - } - - if name == nil { - slf.Name = sheet.Name - } else { - if value := slf.matrix.Get(name.X, name.Y); value == nil { - return ErrReadConfigFailedWithName - } else { - slf.Name = str.FirstUpper(strings.TrimSpace(value.String())) - } - } - if strings.HasPrefix(sheet.Name, slf.ignore) { - return ErrReadConfigFailedIgnore - } - if hash.Exist(slf.Exist, slf.Name) { - return ErrReadConfigFailedSame - } - - if indexCount == nil { - slf.IndexCount, _ = strconv.Atoi(sheet.Name) - } else { - if value := slf.matrix.Get(indexCount.X, indexCount.Y); value == nil { - return ErrReadConfigFailedWithIndexCount - } else { - indexCount, err := value.Int() - if err != nil { - return err - } - if indexCount < 0 { - return ErrReadConfigFailedWithIndexCountLessThanZero - } - slf.IndexCount = indexCount - } - } - - var ( - describeStart int - horizontal bool - fields = make(map[string]bool) - dx, dy, nx, ny, tx, ty, ex, ey int - ) - horizontal = slf.IndexCount > 0 - slf.horizontal = horizontal - - if horizontal { - describeStart = 3 - dy = describeStart - ny = dy + 1 - ty = ny + 1 - ey = ty + 1 - slf.dataStart = ey + 1 - } else { - delete(slf.excludeFields, 0) - describeStart = 4 - dy = describeStart - ny = dy - ty = dy - ey = dy - nx = dx + 1 - tx = nx + 1 - ex = tx + 1 - slf.dataStart = ey + 1 - } - var index = slf.IndexCount - for { - var ( - describe, fieldName, fieldType, exportParam string - ) - var skip bool - if value, exist := slf.matrix.GetExist(dx, dy); !exist { - skip = true - } else { - describe = value.String() - } - if value, exist := slf.matrix.GetExist(nx, ny); !exist { - skip = true - } else { - fieldName = str.FirstUpper(strings.TrimSpace(value.String())) - } - if value, exist := slf.matrix.GetExist(tx, ty); !exist { - skip = true - } else { - fieldType = strings.TrimSpace(value.String()) - } - if value, exist := slf.matrix.GetExist(ex, ey); !exist { - skip = true - } else { - exportParam = strings.ToLower(strings.TrimSpace(value.String())) - } - if len(strings.TrimSpace(fieldName))+len(strings.TrimSpace(fieldType))+len(strings.TrimSpace(exportParam)) < 3 { - skip = true - } - - var field = NewField(slf.Name, fieldName, fieldType) - field.Describe = strings.ReplaceAll(describe, "\n", ", ") - if strings.HasSuffix(field.Describe, ", ") { - field.Describe = field.Describe[:len(field.Describe)-2] - } - field.ExportParam = exportParam - switch field.ExportParam { - case "s", "sc", "cs": - field.Server = true - } - - if horizontal { - dx++ - nx++ - tx++ - ex++ - } else { - dy++ - ny++ - ty++ - ey++ - } - - if !skip { - - field.Ignore = slf.excludeFields[len(slf.Fields)] - if !field.Ignore { - if strings.HasPrefix(field.Describe, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.Name, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.Type, slf.ignore) { - field.Ignore = true - } else if strings.HasPrefix(field.ExportParam, slf.ignore) { - field.Ignore = true - } - } - if !field.Ignore { - switch exportParam { - case "s", "c", "sc", "cs": - default: - continue - } - } - - if fields[field.Name] && !field.Ignore { - return ErrReadConfigFailedWithNameDuplicate - } - if index > 0 && !field.Ignore { - if _, exist := basicTypeName[field.Type]; !exist { - return ErrReadConfigFailedWithIndexTypeException - } - index-- - } - - fields[field.Name] = true - slf.Fields = append(slf.Fields, field) - } - - if horizontal { - if dx >= slf.matrix.GetWidth() { - break - } - } else { - if dy >= slf.matrix.GetHeight() { - break - } - } - } - - return slf.initData() -} - -func (slf *Config) initData() error { - var x, y int - if slf.horizontal { - y = slf.dataStart - } else { - x = 4 - y = 4 - } - var dataSourceServer = make(map[any]any) - var dataSourceClient = make(map[any]any) - for { - var dataServer = dataSourceServer - var dataClient = dataSourceClient - var currentIndex = 0 - - var lineServer = map[any]any{} - var lineClient = map[any]any{} - var skip bool - var offset int - var stop bool - for i := 0; i < len(slf.Fields); i++ { - if slf.excludeFields[i] { - if c := slf.matrix.Get(x+i, y); c != nil && strings.HasPrefix(c.String(), "#") { - skip = true - break - } - continue - } - - field := slf.Fields[i] - if field.Ignore { - continue - } - var value any - var zero bool - if slf.horizontal { - c := slf.matrix.Get(x+i, y) - if c == nil { - if currentIndex < slf.IndexCount { - stop = true - break - } - value = getValueZero(field.SourceType) - zero = true - } else if currentIndex < slf.IndexCount && len(strings.TrimSpace(c.String())) == 0 { - stop = true - break - } - if !zero { - value = getValueWithType(field.SourceType, c.String()) - } - } else { - c := slf.matrix.Get(x, y+i+offset) - for c == nil { - offset++ - c = slf.matrix.Get(x, y+i+offset) - if y+i+offset >= slf.matrix.GetHeight() { - break - } - } - value = getValueWithType(field.SourceType, c.String()) - } - switch field.ExportParam { - case "s": - lineServer[field.Name] = value - case "c": - lineClient[field.Name] = value - case "sc", "cs": - lineServer[field.Name] = value - lineClient[field.Name] = value - default: - skip = true - break - } - - if currentIndex < slf.IndexCount { - currentIndex++ - m, exist := dataServer[value] - if !exist { - if currentIndex == slf.IndexCount { - dataServer[value] = lineServer - } else { - m = map[any]any{} - dataServer[value] = m - } - } - if currentIndex < slf.IndexCount { - dataServer = m.(map[any]any) - } - - m, exist = dataClient[value] - if !exist { - if currentIndex == slf.IndexCount { - dataClient[value] = lineClient - } else { - m = map[any]any{} - dataClient[value] = m - } - } - if currentIndex < slf.IndexCount { - dataClient = m.(map[any]any) - } - } - } - if stop { - break - } else if slf.horizontal { - y++ - if y >= slf.matrix.GetHeight() { - break - } - } else { - if !skip { - slf.dataServer = lineServer - slf.dataClient = lineClient - } - break - } - } - if slf.horizontal { - slf.dataServer = dataSourceServer - slf.dataClient = dataSourceClient - } - return nil -} - -func (slf *Config) String() string { - tmpl, err := template.New("struct").Parse(generateConfigTemplate) - if err != nil { - return "" - } - - var buf bytes.Buffer - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - var result string - _ = str.RangeLine(buf.String(), func(index int, line string) error { - if len(strings.TrimSpace(line)) == 0 { - return nil - } - result += fmt.Sprintf("%s\n", strings.ReplaceAll(line, "\t\t", "\t")) - if len(strings.TrimSpace(line)) == 1 { - result += "\n" - } - return nil - }) - return result -} - -func (slf *Config) JsonServer() []byte { - buffer := &bytes.Buffer{} - encoder := jsonIter.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - _ = encoder.Encode(slf.dataServer) - return buffer.Bytes() -} - -func (slf *Config) JsonClient() []byte { - buffer := &bytes.Buffer{} - encoder := jsonIter.NewEncoder(buffer) - encoder.SetEscapeHTML(false) - encoder.SetIndent("", " ") - _ = encoder.Encode(slf.dataClient) - return buffer.Bytes() -} - -func (slf *Config) GetVariable() string { - var result string - var count int - if slf.IndexCount > 0 { - for _, field := range slf.Fields { - if field.Ignore { - continue - } - result += fmt.Sprintf("map[%s]", field.Type) - count++ - if count >= slf.IndexCount { - break - } - } - } - return fmt.Sprintf("%s*%sDefine", result, slf.Name) -} - -func (slf *Config) GetVariableGen() string { - if slf.IndexCount == 0 { - return fmt.Sprintf("new(%s)", strings.TrimPrefix(slf.GetVariable(), "*")) - } - return fmt.Sprintf("make(%s)", slf.GetVariable()) -} diff --git a/planner/configexport/internal/errors.go b/planner/configexport/internal/errors.go deleted file mode 100644 index 2e2472f..0000000 --- a/planner/configexport/internal/errors.go +++ /dev/null @@ -1,16 +0,0 @@ -package internal - -import "errors" - -var ( - ErrReadConfigFailedIgnore = errors.New("read config skip ignore") - ErrReadConfigFailedSame = errors.New("read config skip, same name") - ErrReadConfigFailedWithDisplayName = errors.New("read config display name failed, can not found position") - ErrReadConfigFailedWithName = errors.New("read config name failed, can not found position") - ErrReadConfigFailedWithIndexCount = errors.New("read config index count failed, can not found position") - ErrReadConfigFailedWithIndexCountLessThanZero = errors.New("read config index count failed, value less than zero") - ErrReadConfigFailedWithFieldPosition = errors.New("read config index count failed, field position exception") - ErrReadConfigFailedWithNameDuplicate = errors.New("read config index count failed, duplicate field names") - ErrReadConfigFailedWithExportParamException = errors.New("read config index count failed, export param must is s or c or sc or cs") - ErrReadConfigFailedWithIndexTypeException = errors.New("read config index count failed, the index type is only allowed to be the basic type") -) diff --git a/planner/configexport/internal/field.go b/planner/configexport/internal/field.go deleted file mode 100644 index b0f5762..0000000 --- a/planner/configexport/internal/field.go +++ /dev/null @@ -1,146 +0,0 @@ -package internal - -import ( - "bytes" - "fmt" - "github.com/kercylan98/minotaur/utils/str" - "html/template" - "strings" -) - -func NewField(configName, fieldName, fieldType string) *Field { - sourceType := fieldType - var handleStruct = func(configName, fieldType string) []*Field { - var fs []*Field - var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") - var fields []string - var field string - var leftBrackets []int - for i, c := range s { - switch c { - case ',': - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } else { - field += string(c) - } - case '{': - leftBrackets = append(leftBrackets, i) - field += string(c) - case '}': - leftBrackets = leftBrackets[:len(leftBrackets)-1] - field += string(c) - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } - default: - field += string(c) - } - } - if len(field) > 0 { - fields = append(fields, field) - } - - for _, fieldInfo := range fields { - fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") - fs = append(fs, NewField(configName, str.FirstUpper(fieldName), fieldType)) - - } - return fs - } - - var fs []*Field - var sliceField *Field - if t, exist := basicTypeName[fieldType]; exist { - fieldType = t - goto end - } - if strings.HasPrefix(fieldType, "[]") { - fieldType = strings.TrimPrefix(fieldType, "[]") - if t, exist := basicTypeName[fieldType]; exist { - fieldType = fmt.Sprintf("map[int]%s", t) - } else { - sliceField = NewField(configName, fieldName, fieldType) - fieldType = fmt.Sprintf("map[int]*%s", configName+fieldName) - } - goto end - } - - fs = handleStruct(configName+fieldName, fieldType) - fieldType = fmt.Sprintf("*%s", configName+fieldName) - -end: - { - return &Field{ - Name: fieldName, - Type: fieldType, - SourceType: sourceType, - TypeNotStar: strings.TrimPrefix(fieldType, "*"), - Fields: fs, - SliceField: sliceField, - } - } -} - -type Field struct { - Name string - Type string - SourceType string - TypeNotStar string - Fields []*Field - SliceField *Field - ExportParam string - Server bool - Describe string - Ignore bool -} - -func (slf *Field) Struct() string { - if len(slf.Fields) == 0 && slf.SliceField == nil { - return "" - } - tmpl, err := template.New("struct").Parse(generateGoStructTemplate) - if err != nil { - return "" - } - var buf bytes.Buffer - if len(slf.Fields) > 0 { - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - } else if slf.SliceField != nil { - if err = tmpl.Execute(&buf, slf.SliceField); err != nil { - return "" - } - } - s := buf.String() - return s -} - -func (slf *Field) String() string { - if len(slf.Fields) == 0 && slf.SliceField == nil { - return "" - } - tmpl, err := template.New("struct").Parse(generateGoStructTemplate) - if err != nil { - return "" - } - - var buf bytes.Buffer - if len(slf.Fields) > 0 { - if err = tmpl.Execute(&buf, slf); err != nil { - return "" - } - } else if slf.SliceField != nil { - if err = tmpl.Execute(&buf, slf.SliceField); err != nil { - return "" - } - } - s := buf.String() - for _, field := range slf.Fields { - s += fmt.Sprintf("%s", field.String()) - } - return s -} diff --git a/planner/configexport/internal/field_types.go b/planner/configexport/internal/field_types.go deleted file mode 100644 index d44ddd3..0000000 --- a/planner/configexport/internal/field_types.go +++ /dev/null @@ -1,304 +0,0 @@ -package internal - -import ( - "github.com/kercylan98/minotaur/utils/str" - "github.com/tidwall/gjson" - "math" - "strconv" - "strings" -) - -var basicTypeName = map[string]string{ - "string": "string", - "int": "int", - "int8": "int8", - "int16": "int16", - "int32": "int32", - "int64": "int64", - "uint": "uint", - "uint8": "uint8", - "uint16": "uint16", - "uint32": "uint32", - "uint64": "uint64", - "float32": "float32", - "float64": "float64", - "float": "float64", - "double": "float64", - "number": "float64", - "byte": "byte", - "rune": "rune", - "bool": "bool", - "boolean": "bool", -} - -var basicType = map[string]func(fieldValue string) any{ - "string": withStringType, - "int": withIntType, - "int8": withInt8Type, - "int16": withInt16Type, - "int32": withInt32Type, - "int64": withInt64Type, - "uint": withUintType, - "uint8": withUint8Type, - "uint16": withUint16Type, - "uint32": withUint32Type, - "uint64": withUint64Type, - "float32": withFloat32Type, - "float64": withFloat64Type, - "float": withFloat64Type, - "double": withFloat64Type, - "number": withFloat64Type, - "byte": withByteType, - "rune": withRuneType, - "bool": withBoolType, - "boolean": withBoolType, -} - -func getValueZero(fileType string) any { - switch basicTypeName[fileType] { - case "string": - return getValueWithType(fileType, "") - case "int": - return getValueWithType(fileType, "0") - case "int8": - return getValueWithType(fileType, "0") - case "int16": - return getValueWithType(fileType, "0") - case "int32": - return getValueWithType(fileType, "0") - case "int64": - return getValueWithType(fileType, "0") - case "uint": - return getValueWithType(fileType, "0") - case "uint8": - return getValueWithType(fileType, "0") - case "uint16": - return getValueWithType(fileType, "0") - case "uint32": - return getValueWithType(fileType, "0") - case "uint64": - return getValueWithType(fileType, "0") - case "float32": - return getValueWithType(fileType, "0") - case "float64": - return getValueWithType(fileType, "0") - case "byte": - return getValueWithType(fileType, "0") - case "rune": - return getValueWithType(fileType, "0") - case "bool": - return getValueWithType(fileType, "false") - } - return nil -} - -func getValueWithType(fieldType string, fieldValue string) any { - fieldType = strings.ToLower(strings.TrimSpace(fieldType)) - handle, exist := basicType[fieldType] - if exist { - return handle(fieldValue) - } else { - return withStructType(fieldType, fieldValue) - } -} - -func withStructType(fieldType string, fieldValue string) any { - // {id:int,name:string,info:{lv:int,exp:int}} - if strings.HasPrefix(fieldType, "[]") { - return withSliceType(fieldType, fieldValue) - } else if !strings.HasPrefix(fieldType, "{") || !strings.HasSuffix(fieldType, "}") { - return nil - } - var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") - var data = map[any]any{} - var fields []string - var field string - var leftBrackets []int - for i, c := range s { - switch c { - case ',': - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } else { - field += string(c) - } - case '{': - leftBrackets = append(leftBrackets, i) - field += string(c) - case '}': - leftBrackets = leftBrackets[:len(leftBrackets)-1] - field += string(c) - if len(leftBrackets) == 0 { - fields = append(fields, field) - field = "" - } - default: - field += string(c) - } - } - if len(field) > 0 { - fields = append(fields, field) - } - - for _, fieldInfo := range fields { - fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") - handle, exist := basicType[fieldType] - if exist { - data[fieldName] = handle(gjson.Get(fieldValue, fieldName).String()) - } else { - data[fieldName] = withStructType(fieldType, gjson.Get(fieldValue, fieldName).String()) - } - } - - return data -} - -func withSliceType(fieldType string, fieldValue string) any { - if !strings.HasPrefix(fieldType, "[]") { - return nil - } - t := strings.TrimPrefix(fieldType, "[]") - var data = map[any]any{} - gjson.ForEachLine(fieldValue, func(line gjson.Result) bool { - line.ForEach(func(key, value gjson.Result) bool { - handle, exist := basicType[t] - if exist { - data[len(data)] = handle(value.String()) - } else { - data[len(data)] = withStructType(t, value.String()) - } - return true - }) - return true - }) - return data -} - -func withStringType(fieldValue string) any { - return fieldValue -} - -func withIntType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - return value -} - -func withInt8Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int8(0) - } else if value > math.MaxInt8 { - return int8(math.MaxInt8) - } - return int8(value) -} - -func withInt16Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int16(0) - } else if value > math.MaxInt16 { - return int16(math.MaxInt16) - } - return int16(value) -} - -func withInt32Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return int32(0) - } else if value > math.MaxInt32 { - return int32(math.MaxInt32) - } - return int32(value) -} - -func withInt64Type(fieldValue string) any { - value, _ := strconv.ParseInt(fieldValue, 10, 64) - return value -} - -func withUintType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint(0) - } - return uint(value) -} - -func withUint8Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint8(0) - } else if value > math.MaxUint8 { - return uint8(math.MaxUint8) - } - return uint8(value) -} - -func withUint16Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint16(0) - } else if value > math.MaxUint16 { - return uint16(math.MaxUint16) - } - return uint16(value) -} - -func withUint32Type(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return uint32(0) - } else if value > math.MaxUint32 { - return uint32(math.MaxUint32) - } - return uint32(value) -} - -func withUint64Type(fieldValue string) any { - value, _ := strconv.ParseInt(fieldValue, 10, 64) - return uint64(value) -} - -func withFloat32Type(fieldValue string) any { - value, _ := strconv.ParseFloat(fieldValue, 32) - return value -} - -func withFloat64Type(fieldValue string) any { - value, _ := strconv.ParseFloat(fieldValue, 64) - return value -} - -func withByteType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < 0 { - return byte(0) - } else if value > math.MaxUint8 { - return byte(math.MaxInt8) - } - return byte(value) -} - -func withRuneType(fieldValue string) any { - value, _ := strconv.Atoi(fieldValue) - if value < math.MinInt32 { - return rune(0) - } else if value > math.MaxInt32 { - return rune(math.MaxInt32) - } - return rune(value) -} - -func withBoolType(fieldValue string) any { - switch fieldValue { - case "0", "false", "!": - return false - case "1", "true", "&": - return true - default: - return false - } -} diff --git a/planner/configexport/internal/postion.go b/planner/configexport/internal/postion.go deleted file mode 100644 index 5a7fcd9..0000000 --- a/planner/configexport/internal/postion.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -func NewPosition(x, y int) *Position { - return &Position{ - X: x, - Y: y, - } -} - -type Position struct { - X int - Y int -} diff --git a/planner/configexport/internal/template.go b/planner/configexport/internal/template.go deleted file mode 100644 index a332fff..0000000 --- a/planner/configexport/internal/template.go +++ /dev/null @@ -1,108 +0,0 @@ -package internal - -const ( - generateConfigTemplate = ` - -// {{.Name}}Define {{.DisplayName}} -type {{.Name}}Define struct { - {{range $index, $value := .Fields}}{{if eq $value.Server true}}{{if eq $value.Ignore false}}{{$value.Name}} {{$value.Type}} // {{$value.Describe}}{{end}}{{end}} - {{end}} -} - -func (slf *{{.Name}}Define) String() string { - if data, err := json.Marshal(slf); err == nil { - return string(data) - } - return "{}" -} - -{{range $index, $value := .Fields}}{{$value}}{{end}} -` - - generateGoStructTemplate = ` -{{if eq .Ignore false}} -type {{.TypeNotStar}} struct { - {{range $index, $value := .Fields}} - {{$value.Name}} {{$value.Type}} - {{end}} -} -{{end}} -` - - GenerateGoConfigTemplate = `// Code generated by minotaur-config-export. DO NOT EDIT. - -package {{.Package}} - -import ( - jsonIter "github.com/json-iterator/go" - "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" - "os" -) - -var json = jsonIter.ConfigCompatibleWithStandardLibrary -var full map[string]any - -var ( -{{range $index, $config := .Configs}} - // {{$config.Name}}Sign {{$config.DisplayName}}签名 - {{$config.Name}}Sign = "{{$config.Name}}" - // {{$config.Name}} {{$config.DisplayName}} - {{$config.Name}} {{$config.GetVariable}} - _{{$config.Name}} {{$config.GetVariable}} -{{end}} -) - -func LoadConfig(handle func(filename string, config any) error) { - var err error -{{range $index, $config := .Configs}} - _{{$config.Name}} = {{$config.GetVariableGen}} -{{if eq $config.IndexCount 0}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", _{{$config.Name}}); err != nil { - log.Error("Config", zap.String("Name", "{{$config.Name}}"), zap.Bool("Invalid", true), zap.Error(err)) - } -{{else}} - if err = handle("{{$config.Prefix}}{{$config.Name}}.json", &_{{$config.Name}}); err != nil { - log.Error("Config", zap.String("Name", "{{$config.Name}}"), zap.Bool("Invalid", true), zap.Error(err)) - } -{{end}} -{{end}} -} - -// Refresh 将加载后的配置刷新到线上 -func Refresh() { - full = make(map[string]any) -{{range $index, $config := .Configs}} - {{$config.Name}} = _{{$config.Name}} - full["{{$config.Name}}"] = {{$config.Name}} -{{end}} -} - -// DefaultLoad 默认提供的配置加载函数 -func DefaultLoad(filepath string) { - LoadConfig(func(filename string, config any) error { - bytes, err := os.ReadFile(filepath) - if err != nil { - return err - } - return json.Unmarshal(bytes, &config) - }) -} - -// GetFull 获取所有配置的 map 集合 -// - 通常用于前端配置通过后端接口获取的情况 -func GetFull() map[string]any { - return full -} -` - - GenerateGoDefineTemplate = `// Code generated by minotaur-config-export. DO NOT EDIT. - -package {{.Package}} - -{{range $index, $config := .Configs}} - {{$config.String}} -{{end}} - -` -) diff --git a/planner/pce/config.go b/planner/pce/config.go new file mode 100644 index 0000000..683ddaa --- /dev/null +++ b/planner/pce/config.go @@ -0,0 +1,19 @@ +package pce + +// Config 配置解析接口 +// - 用于将配置文件解析为可供分析的数据结构 +// - 可以在 cs 包中找到内置提供的实现及其模板,例如 cs.XlsxIndexConfig +type Config interface { + // GetConfigName 配置名称 + GetConfigName() string + // GetDisplayName 配置显示名称 + GetDisplayName() string + // GetDescription 配置描述 + GetDescription() string + // GetIndexCount 索引数量 + GetIndexCount() int + // GetFields 获取字段 + GetFields() []DataField + // GetData 获取数据 + GetData() [][]DataInfo +} diff --git a/planner/pce/cs/xlsx.go b/planner/pce/cs/xlsx.go new file mode 100644 index 0000000..b81c6a5 --- /dev/null +++ b/planner/pce/cs/xlsx.go @@ -0,0 +1,200 @@ +package cs + +import ( + "github.com/kercylan98/minotaur/planner/pce" + "github.com/kercylan98/minotaur/utils/str" + "github.com/tealeg/xlsx" + "regexp" + "strings" +) + +type XlsxExportType int + +const ( + XlsxExportTypeServer XlsxExportType = iota + XlsxExportTypeClient +) + +func NewXlsx(sheet *xlsx.Sheet, exportType XlsxExportType) *Xlsx { + config := &Xlsx{ + sheet: sheet, + exportType: exportType, + } + return config +} + +// Xlsx 内置的 Xlsx 配置 +type Xlsx struct { + sheet *xlsx.Sheet + exportType XlsxExportType +} + +func (slf *Xlsx) GetConfigName() string { + return str.FirstUpper(strings.TrimSpace(slf.sheet.Rows[0].Cells[1].String())) +} + +func (slf *Xlsx) GetDisplayName() string { + return slf.sheet.Name +} + +func (slf *Xlsx) GetDescription() string { + return slf.GetDisplayName() +} + +func (slf *Xlsx) GetIndexCount() int { + index, err := slf.sheet.Rows[1].Cells[1].Int() + if err != nil { + panic(err) + } + return index +} + +func (slf *Xlsx) GetFields() []pce.DataField { + var handle = func(index int, desc, name, fieldType, exportType *xlsx.Cell) (pce.DataField, bool) { + var field pce.DataField + if desc == nil || name == nil || fieldType == nil || exportType == nil { + return field, false + } + field = pce.DataField{ + Index: index, + Name: strings.ReplaceAll(strings.ReplaceAll(str.FirstUpper(name.String()), "\r", " "), "\n", " "), + Type: fieldType.String(), + ExportType: exportType.String(), + Desc: strings.ReplaceAll(strings.ReplaceAll(desc.String(), "\r", " "), "\n", " "), + } + if len(field.Name) == 0 || len(field.Type) == 0 || len(field.ExportType) == 0 { + return field, false + } + + if slf.checkFieldInvalid(field) { + return field, false + } + + return field, true + } + var fields []pce.DataField + if slf.GetIndexCount() > 0 { + for x := 1; x < slf.getWidth(); x++ { + if field, match := handle(x, slf.get(x, 3), slf.get(x, 4), slf.get(x, 5), slf.get(x, 6)); match { + fields = append(fields, field) + } + } + } else { + for y := 4; y < slf.getHeight(); y++ { + if field, match := handle(y, slf.get(0, y), slf.get(1, y), slf.get(2, y), slf.get(3, y)); match { + fields = append(fields, field) + } + } + } + return fields +} + +func (slf *Xlsx) GetData() [][]pce.DataInfo { + var data [][]pce.DataInfo + var fields = slf.GetFields() + if slf.GetIndexCount() > 0 { + for y := 7; y < slf.getHeight(); y++ { + var line []pce.DataInfo + var stop bool + + if prefixCell := slf.get(0, y); prefixCell != nil { + prefix := prefixCell.String() + if strings.HasPrefix(prefix, "#") { + continue + } + } + + for i, field := range slf.GetFields() { + var isIndex = i-1 < slf.GetIndexCount() + + var value string + if valueCell := slf.get(field.Index, y); valueCell != nil { + value = valueCell.String() + } else if isIndex { + stop = true + break + } + valueCell := slf.get(field.Index, y) + if valueCell == nil { + break + } + if len(fields) > i-1 { + line = append(line, pce.DataInfo{ + DataField: field, + Value: value, + }) + } + } + if len(line) > 0 { + data = append(data, line) + } + if stop { + break + } + } + } else { + var line []pce.DataInfo + for i, field := range slf.GetFields() { + var value string + if valueCell := slf.get(4, 4+i); valueCell != nil { + value = valueCell.String() + } + line = append(line, pce.DataInfo{ + DataField: field, + Value: value, + }) + } + data = append(data, line) + } + return data +} + +// getWidth 获取宽度 +func (slf *Xlsx) getWidth() int { + return slf.sheet.MaxCol +} + +// getHeight 获取高度 +func (slf *Xlsx) getHeight() int { + return slf.sheet.MaxRow +} + +// get 获取单元格 +func (slf *Xlsx) get(x, y int) *xlsx.Cell { + if x < 0 || y < 0 || y >= len(slf.sheet.Rows) { + return nil + } + row := slf.sheet.Rows[y] + if x >= len(row.Cells) { + return nil + } + return row.Cells[x] +} + +func (slf *Xlsx) checkFieldInvalid(field pce.DataField) bool { + switch strings.ToLower(field.ExportType) { + case "s": + if slf.exportType != XlsxExportTypeServer { + return true + } + case "c": + if slf.exportType != XlsxExportTypeClient { + return true + } + case "sc", "cs": + default: + return true + } + + pattern := "^[a-zA-Z][a-zA-Z0-9]*$" + reg := regexp.MustCompile(pattern) + if !reg.MatchString(field.Name) { + return true + } + + if strings.HasPrefix(field.Name, "#") || strings.HasPrefix(field.Type, "#") { + return true + } + + return false +} diff --git a/planner/configexport/template.xlsx b/planner/pce/cs/xlsx_template.xlsx similarity index 69% rename from planner/configexport/template.xlsx rename to planner/pce/cs/xlsx_template.xlsx index aa39090..7ebc65e 100644 Binary files a/planner/configexport/template.xlsx and b/planner/pce/cs/xlsx_template.xlsx differ diff --git a/planner/pce/cs/xlsx_test.go b/planner/pce/cs/xlsx_test.go new file mode 100644 index 0000000..5ae90f3 --- /dev/null +++ b/planner/pce/cs/xlsx_test.go @@ -0,0 +1,63 @@ +package cs_test + +import ( + "github.com/kercylan98/minotaur/planner/pce/cs" + . "github.com/smartystreets/goconvey/convey" + "github.com/tealeg/xlsx" + "testing" +) + +func TestNewIndexXlsxConfig(t *testing.T) { + Convey("TestNewIndexXlsxConfig", t, func() { + f, err := xlsx.OpenFile("./xlsx_template.xlsx") + if err != nil { + panic(err) + } + config := cs.NewXlsx(f.Sheets[1]) + So(config, ShouldNotBeNil) + }) +} + +func TestXlsxIndexConfig_GetConfigName(t *testing.T) { + Convey("TestXlsxIndexConfig_GetConfigName", t, func() { + f, err := xlsx.OpenFile("./xlsx_template.xlsx") + if err != nil { + panic(err) + } + config := cs.NewXlsx(f.Sheets[1]) + So(config.GetConfigName(), ShouldEqual, "IndexConfig") + }) +} + +func TestXlsxIndexConfig_GetDisplayName(t *testing.T) { + Convey("TestXlsxIndexConfig_GetDisplayName", t, func() { + f, err := xlsx.OpenFile("./xlsx_template.xlsx") + if err != nil { + panic(err) + } + config := cs.NewXlsx(f.Sheets[1]) + So(config.GetDisplayName(), ShouldEqual, "有索引") + }) +} + +func TestXlsxIndexConfig_GetDescription(t *testing.T) { + Convey("TestXlsxIndexConfig_GetDescription", t, func() { + f, err := xlsx.OpenFile("./xlsx_template.xlsx") + if err != nil { + panic(err) + } + config := cs.NewXlsx(f.Sheets[1]) + So(config.GetDescription(), ShouldEqual, "暂无描述") + }) +} + +func TestXlsxIndexConfig_GetIndexCount(t *testing.T) { + Convey("TestXlsxIndexConfig_GetIndexCount", t, func() { + f, err := xlsx.OpenFile("./xlsx_template.xlsx") + if err != nil { + panic(err) + } + config := cs.NewXlsx(f.Sheets[1]) + So(config.GetIndexCount(), ShouldEqual, 2) + }) +} diff --git a/planner/pce/data_tmpl.go b/planner/pce/data_tmpl.go new file mode 100644 index 0000000..09a36e2 --- /dev/null +++ b/planner/pce/data_tmpl.go @@ -0,0 +1,7 @@ +package pce + +// DataTmpl 数据导出模板 +type DataTmpl interface { + // Render 渲染模板 + Render(data map[any]any) (string, error) +} diff --git a/planner/pce/exporter.go b/planner/pce/exporter.go new file mode 100644 index 0000000..6df3a4f --- /dev/null +++ b/planner/pce/exporter.go @@ -0,0 +1,28 @@ +package pce + +// NewExporter 创建导出器 +func NewExporter() *Exporter { + return &Exporter{} +} + +// Exporter 导出器 +type Exporter struct{} + +// ExportStruct 导出结构 +func (slf *Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) ([]byte, error) { + raw, err := tmpl.Render(tmplStruct...) + if err != nil { + return nil, err + } + return []byte(raw), nil +} + +// ExportData 导出数据 +func (slf *Exporter) ExportData(tmpl DataTmpl, data map[any]any) ([]byte, error) { + raw, err := tmpl.Render(data) + if err != nil { + return nil, err + } + + return []byte(raw), nil +} diff --git a/planner/pce/field.go b/planner/pce/field.go new file mode 100644 index 0000000..e2672dd --- /dev/null +++ b/planner/pce/field.go @@ -0,0 +1,23 @@ +package pce + +import ( + "reflect" + "strings" +) + +// Field 基本字段类型接口 +type Field interface { + // TypeName 字段类型名称 + TypeName() string + // Zero 获取零值 + Zero() any + // Parse 解析 + Parse(value string) any +} + +// GetFieldGolangType 获取字段的 Golang 类型 +func GetFieldGolangType(field Field) string { + typeOf := reflect.TypeOf(field).Elem() + kind := strings.ToLower(typeOf.Kind().String()) + return kind +} diff --git a/planner/pce/field_test.go b/planner/pce/field_test.go new file mode 100644 index 0000000..b8e0626 --- /dev/null +++ b/planner/pce/field_test.go @@ -0,0 +1,11 @@ +package pce_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/planner/pce" + "testing" +) + +func TestGetFieldGolangType(t *testing.T) { + fmt.Println(pce.GetFieldGolangType(new(pce.String))) +} diff --git a/planner/pce/fields.go b/planner/pce/fields.go new file mode 100644 index 0000000..cb168c6 --- /dev/null +++ b/planner/pce/fields.go @@ -0,0 +1,491 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/super" + "math" + "strconv" +) + +var fields = []Field{ + new(Int), + new(Int8), + new(Int16), + new(Int32), + new(Int64), + new(Uint), + new(Uint8), + new(Uint16), + new(Uint32), + new(Uint64), + new(Float32), + new(Float64), + new(String), + new(Bool), + new(Byte), + new(Rune), + new(Complex64), + new(Complex128), + new(Uintptr), + new(Double), + new(Float), + new(Long), + new(Short), + new(Char), + new(Number), + new(Integer), + new(Boolean), +} + +// GetFields 获取所有内置支持的字段 +func GetFields() []Field { + return fields +} + +type Int int + +func (slf Int) TypeName() string { + return "int" +} + +func (slf Int) Zero() any { + return int(0) +} + +func (slf Int) Parse(value string) any { + return super.StringToInt(value) +} + +type Int8 int8 + +func (slf Int8) TypeName() string { + return "int8" +} + +func (slf Int8) Zero() any { + return int8(0) +} + +func (slf Int8) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int8(0) + } else if v > math.MaxInt8 { + return int8(math.MaxInt8) + } + return int8(v) +} + +type Int16 int16 + +func (slf Int16) TypeName() string { + return "int16" +} + +func (slf Int16) Zero() any { + return int16(0) +} + +func (slf Int16) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int16(0) + } else if v > math.MaxInt16 { + return int16(math.MaxInt16) + } + return int16(v) +} + +type Int32 int32 + +func (slf Int32) TypeName() string { + return "int32" +} + +func (slf Int32) Zero() any { + return int32(0) +} + +func (slf Int32) Parse(value string) any { + v := super.StringToInt(value) + if v < 0 { + return int32(0) + } else if v > math.MaxInt32 { + return int32(math.MaxInt32) + } + return int32(v) +} + +type Int64 int64 + +func (slf Int64) TypeName() string { + return "int64" +} + +func (slf Int64) Zero() any { + return int64(0) +} + +func (slf Int64) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Uint uint + +func (slf Uint) TypeName() string { + return "uint" +} + +func (slf Uint) Zero() any { + return uint(0) +} + +func (slf Uint) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint(0) + } + return uint(v) +} + +type Uint8 uint8 + +func (slf Uint8) TypeName() string { + return "uint8" +} + +func (slf Uint8) Zero() any { + return uint8(0) +} + +func (slf Uint8) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint8(0) + } else if v > math.MaxUint8 { + return uint8(math.MaxUint8) + } + return uint8(v) +} + +type Uint16 uint16 + +func (slf Uint16) TypeName() string { + return "uint16" +} + +func (slf Uint16) Zero() any { + return uint16(0) +} + +func (slf Uint16) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint16(0) + } else if v > math.MaxUint16 { + return uint16(math.MaxUint16) + } + return uint16(v) +} + +type Uint32 uint32 + +func (slf Uint32) TypeName() string { + return "uint32" +} + +func (slf Uint32) Zero() any { + return uint32(0) +} + +func (slf Uint32) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return uint32(0) + } else if v > math.MaxUint32 { + return uint32(math.MaxUint32) + } + return uint32(v) +} + +type Uint64 uint64 + +func (slf Uint64) TypeName() string { + return "uint64" +} + +func (slf Uint64) Zero() any { + return uint64(0) +} + +func (slf Uint64) Parse(value string) any { + v, _ := strconv.ParseUint(value, 10, 64) + if v < 0 { + return uint64(0) + } + return v +} + +type Float32 float32 + +func (slf Float32) TypeName() string { + return "float32" +} + +func (slf Float32) Zero() any { + return float32(0) +} + +func (slf Float32) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 32) + return v +} + +type Float64 float64 + +func (slf Float64) TypeName() string { + return "float64" +} + +func (slf Float64) Zero() any { + return float64(0) +} + +func (slf Float64) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type String string + +func (slf String) TypeName() string { + return "string" +} + +func (slf String) Zero() any { + return "" +} + +func (slf String) Parse(value string) any { + return value +} + +type Bool bool + +func (slf Bool) TypeName() string { + return "bool" +} + +func (slf Bool) Zero() any { + return false +} + +func (slf Bool) Parse(value string) any { + v, _ := strconv.ParseBool(value) + return v +} + +type Byte byte + +func (slf Byte) TypeName() string { + return "byte" +} + +func (slf Byte) Zero() any { + return byte(0) +} + +func (slf Byte) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return byte(0) + } else if v > math.MaxUint8 { + return byte(math.MaxUint8) + } + return byte(v) +} + +type Rune rune + +func (slf Rune) TypeName() string { + return "rune" +} + +func (slf Rune) Zero() any { + return rune(0) +} + +func (slf Rune) Parse(value string) any { + v, _ := strconv.Atoi(value) + if v < 0 { + return rune(0) + } else if v > math.MaxInt32 { + return rune(math.MaxInt32) + } + return rune(v) +} + +type Complex64 complex64 + +func (slf Complex64) TypeName() string { + return "complex64" +} + +func (slf Complex64) Zero() any { + return complex64(0) +} + +func (slf Complex64) Parse(value string) any { + v, _ := strconv.ParseComplex(value, 64) + return v +} + +type Complex128 complex128 + +func (slf Complex128) TypeName() string { + return "complex128" +} + +func (slf Complex128) Zero() any { + return complex128(0) +} + +func (slf Complex128) Parse(value string) any { + v, _ := strconv.ParseComplex(value, 128) + return v +} + +type Uintptr uintptr + +func (slf Uintptr) TypeName() string { + return "uintptr" +} + +func (slf Uintptr) Zero() any { + return uintptr(0) +} + +func (slf Uintptr) Parse(value string) any { + v, _ := strconv.ParseUint(value, 10, 64) + return uintptr(v) +} + +type Double float64 + +func (slf Double) TypeName() string { + return "double" +} + +func (slf Double) Zero() any { + return float64(0) +} + +func (slf Double) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type Float float32 + +func (slf Float) TypeName() string { + return "float" +} + +func (slf Float) Zero() any { + return float32(0) +} + +func (slf Float) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 32) + return v +} + +type Long int64 + +func (slf Long) TypeName() string { + return "long" +} + +func (slf Long) Zero() any { + return int64(0) +} + +func (slf Long) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Short int16 + +func (slf Short) TypeName() string { + return "short" +} + +func (slf Short) Zero() any { + return int16(0) +} + +func (slf Short) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 16) + return v +} + +type Char int8 + +func (slf Char) TypeName() string { + return "char" +} + +func (slf Char) Zero() any { + return int8(0) +} + +func (slf Char) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 8) + return v +} + +type Number float64 + +func (slf Number) TypeName() string { + return "number" +} + +func (slf Number) Zero() any { + return float64(0) +} + +func (slf Number) Parse(value string) any { + v, _ := strconv.ParseFloat(value, 64) + return v +} + +type Integer int64 + +func (slf Integer) TypeName() string { + return "integer" +} + +func (slf Integer) Zero() any { + return int64(0) +} + +func (slf Integer) Parse(value string) any { + v, _ := strconv.ParseInt(value, 10, 64) + return v +} + +type Boolean bool + +func (slf Boolean) TypeName() string { + return "boolean" +} + +func (slf Boolean) Zero() any { + return false +} + +func (slf Boolean) Parse(value string) any { + v, _ := strconv.ParseBool(value) + return v +} diff --git a/planner/pce/loader.go b/planner/pce/loader.go new file mode 100644 index 0000000..b6bd373 --- /dev/null +++ b/planner/pce/loader.go @@ -0,0 +1,175 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/str" + "github.com/tidwall/gjson" + "strings" +) + +// NewLoader 创建加载器 +// - 加载器被用于加载配置表的数据和结构信息 +func NewLoader(fields []Field) *Loader { + loader := &Loader{ + fields: make(map[string]Field), + } + for _, f := range fields { + loader.fields[f.TypeName()] = f + } + return loader +} + +// Loader 配置加载器 +type Loader struct { + fields map[string]Field +} + +// LoadStruct 加载结构 +func (slf *Loader) LoadStruct(config Config) *TmplStruct { + var tmpl = &TmplStruct{ + Name: str.FirstUpper(config.GetConfigName()), + Desc: config.GetDescription(), + IndexCount: config.GetIndexCount(), + } + for i, field := range config.GetFields() { + f := tmpl.addField(tmpl.Name, str.FirstUpper(field.Name), field.Desc, field.Type, slf.fields) + if i < tmpl.IndexCount { + f.isIndex = true + } + } + return tmpl +} + +// LoadData 加载配置并得到配置数据 +func (slf *Loader) LoadData(config Config) map[any]any { + var source = make(map[any]any) + var action = source + var indexCount = config.GetIndexCount() - 1 + for _, row := range config.GetData() { + var item = make(map[any]any) + for index, field := range row { + bind, exist := slf.fields[field.Type] + var value any + if exist { + if len(field.Value) == 0 { + value = bind.Zero() + } else { + value = bind.Parse(field.Value) + } + } else { + value = slf.structInterpreter(field.Type, field.Value) + } + + if value != nil { + item[field.Name] = value + } + + if index < indexCount { + m, exist := action[value] + if !exist { + m = map[any]any{} + action[value] = m + action = m.(map[any]any) + } else { + action = m.(map[any]any) + } + } else if index == indexCount { + action[value] = item + } else if indexCount == -1 { + source = item + } + } + action = source + } + + return source +} + +// sliceInterpreter 切片解释器 +func (slf *Loader) sliceInterpreter(fieldType, fieldValue string) any { + if !strings.HasPrefix(fieldType, "[]") { + return nil + } + t := strings.TrimPrefix(fieldType, "[]") + var data = map[any]any{} + gjson.ForEachLine(fieldValue, func(line gjson.Result) bool { + line.ForEach(func(key, value gjson.Result) bool { + field, exist := slf.fields[t] + if exist { + data[len(data)] = field.Parse(value.String()) + } else { + data[len(data)] = slf.structInterpreter(t, value.String()) + } + return true + }) + return true + }) + return data +} + +// structInterpreter 结构体解释器 +// - {id:int,name:string,info:{lv:int,exp:int}} +func (slf *Loader) structInterpreter(fieldType, fieldValue string) any { + if strings.HasPrefix(fieldType, "[]") { + return slf.sliceInterpreter(fieldType, fieldValue) + } else if !strings.HasPrefix(fieldType, "{") || !strings.HasSuffix(fieldType, "}") { + return nil + } + var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") + var data = map[any]any{} + var fields []string + var field string + var leftBrackets []int + for i, c := range s { + switch c { + case ',': + if len(leftBrackets) == 0 { + fields = append(fields, field) + field = "" + } else { + field += string(c) + } + case '{': + leftBrackets = append(leftBrackets, i) + field += string(c) + case '}': + leftBrackets = leftBrackets[:len(leftBrackets)-1] + field += string(c) + if len(leftBrackets) == 0 { + fields = append(fields, field) + field = "" + } + default: + field += string(c) + } + } + if len(field) > 0 { + fields = append(fields, field) + } + + for _, fieldInfo := range fields { + fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") + field, exist := slf.fields[fieldType] + if exist { + data[fieldName] = field.Parse(gjson.Get(fieldValue, fieldName).String()) + } else { + data[fieldName] = slf.structInterpreter(fieldType, gjson.Get(fieldValue, fieldName).String()) + } + } + + return data +} + +// DataInfo 配置数据 +type DataInfo struct { + DataField // 字段 + Value string // 字段值 +} + +// DataField 配置数据字段 +type DataField struct { + Index int // 字段索引 + Name string // 字段名称 + Desc string // 字段描述 + Type string // 字段类型 + ExportType string // 导出类型 +} diff --git a/planner/pce/tmpl.go b/planner/pce/tmpl.go new file mode 100644 index 0000000..3bf9319 --- /dev/null +++ b/planner/pce/tmpl.go @@ -0,0 +1,7 @@ +package pce + +// Tmpl 配置结构模板接口 +type Tmpl interface { + // Render 渲染模板 + Render(templates ...*TmplStruct) (string, error) +} diff --git a/planner/pce/tmpl_field.go b/planner/pce/tmpl_field.go new file mode 100644 index 0000000..3c67ce6 --- /dev/null +++ b/planner/pce/tmpl_field.go @@ -0,0 +1,105 @@ +package pce + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/str" + "strings" +) + +// TmplField 模板字段 +type TmplField struct { + Name string // 字段名称 + Desc string // 字段描述 + Type string // 字段类型 + Struct *TmplStruct // 结构类型字段结构信息 + Index int // 字段索引 + slice bool // 是否是切片类型 + isIndex bool // 是否是索引字段 +} + +// IsIndex 是否是索引字段 +func (slf *TmplField) IsIndex() bool { + return slf.isIndex +} + +// IsStruct 是否是结构类型 +func (slf *TmplField) IsStruct() bool { + return slf.Struct != nil +} + +// IsSlice 是否是切片类型 +func (slf *TmplField) IsSlice() bool { + return slf.slice +} + +// setStruct 设置结构类型 +func (slf *TmplField) setStruct(parent, name, desc, fieldType string, fields map[string]Field) { + slf.Struct = &TmplStruct{ + Name: fmt.Sprintf("%s%s", parent, name), + Desc: desc, + } + slf.handleStruct(slf.Struct.Name, fieldType, fields) +} + +// handleStruct 处理结构类型 +func (slf *TmplField) handleStruct(fieldName, fieldType string, fields map[string]Field) { + if strings.HasPrefix(fieldType, "[]") { + slf.handleSlice(fieldName, fieldType, fields) + return + } else if !strings.HasPrefix(fieldType, "{") || !strings.HasSuffix(fieldType, "}") { + return + } + var s = strings.TrimSuffix(strings.TrimPrefix(fieldType, "{"), "}") + var fs []string + var field string + var leftBrackets []int + for i, c := range s { + switch c { + case ',': + if len(leftBrackets) == 0 { + fs = append(fs, field) + field = "" + } else { + field += string(c) + } + case '{': + leftBrackets = append(leftBrackets, i) + field += string(c) + case '}': + leftBrackets = leftBrackets[:len(leftBrackets)-1] + field += string(c) + if len(leftBrackets) == 0 { + fs = append(fs, field) + field = "" + } + default: + field += string(c) + } + } + if len(field) > 0 { + fs = append(fs, field) + } + + for _, fieldInfo := range fs { + fieldName, fieldType := str.KV(strings.TrimSpace(fieldInfo), ":") + slf.Struct.addField(slf.Struct.Name, str.FirstUpper(fieldName), fieldName, fieldType, fields) + } + + slf.Type = slf.Struct.Name +} + +// handleSlice 处理切片类型 +func (slf *TmplField) handleSlice(fieldName, fieldType string, fields map[string]Field) { + if !strings.HasPrefix(fieldType, "[]") { + return + } + slf.slice = true + t := strings.TrimPrefix(fieldType, "[]") + if hash.Exist(fields, t) { + slf.Struct = nil + slf.Type = t + } else { + slf.handleStruct(fieldName, t, fields) + } +} diff --git a/planner/pce/tmpl_struct.go b/planner/pce/tmpl_struct.go new file mode 100644 index 0000000..11d727b --- /dev/null +++ b/planner/pce/tmpl_struct.go @@ -0,0 +1,41 @@ +package pce + +import ( + "github.com/kercylan98/minotaur/utils/hash" +) + +// TmplStruct 模板结构 +type TmplStruct struct { + Name string // 结构名称 + Desc string // 结构描述 + Fields []*TmplField // 字段列表 + IndexCount int // 索引数量 +} + +// addField 添加字段 +func (slf *TmplStruct) addField(parent, name, desc, fieldType string, fields map[string]Field) *TmplField { + field := &TmplField{ + Name: name, + Desc: desc, + Type: fieldType, + } + if !hash.Exist(fields, fieldType) { + field.setStruct(parent, name, desc, fieldType, fields) + } else { + field.Type = GetFieldGolangType(fields[fieldType]) + } + slf.Fields = append(slf.Fields, field) + return field +} + +// AllChildren 获取所有子结构 +func (slf *TmplStruct) AllChildren() []*TmplStruct { + var children []*TmplStruct + for _, field := range slf.Fields { + if field.IsStruct() { + children = append(children, field.Struct) + children = append(children, field.Struct.AllChildren()...) + } + } + return children +} diff --git a/planner/pce/tmpls/golang.go b/planner/pce/tmpls/golang.go new file mode 100644 index 0000000..f7afb2b --- /dev/null +++ b/planner/pce/tmpls/golang.go @@ -0,0 +1,221 @@ +package tmpls + +import ( + "fmt" + "github.com/kercylan98/minotaur/planner/pce" + "strings" +) + +// NewGolang 创建一个 Golang 配置导出模板 +func NewGolang(packageName string) *Golang { + return &Golang{ + Package: packageName, + } +} + +// Golang 配置导出模板 +type Golang struct { + Package string + Templates []*pce.TmplStruct +} + +func (slf *Golang) Render(templates ...*pce.TmplStruct) (string, error) { + slf.Templates = templates + return render(`// Code generated by minotaur. DO NOT EDIT. + package {{.Package}} + + import ( + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/log" + "sync" + ) + + type Sign string + + const ( + {{- range .Templates}} + {{.Name}}Sign Sign = "{{.Name}}" // {{.Desc}} + {{- end}} + ) + + var ( + json = jsonIter.ConfigCompatibleWithStandardLibrary + configs map[Sign]any + signs = []Sign{ + {{- range .Templates}} + {{.Name}}Sign, + {{- end}} + } + mutex sync.Mutex + ) + + var ( + {{- range .Templates}} + {{- if $.HasIndex .}} + {{.Name}} {{$.GetVariable .}} // {{.Desc}} + {{- end}} + {{- end}} + ) + + var ( + {{- range .Templates}} + {{- if $.HasIndex .}}{{- else}} + {{.Name}} *{{$.GetConfigName .}} // {{.Desc}} + {{- end}} + {{- end}} + ) + + var ( + {{- range .Templates}} + {{- if $.HasIndex .}} + _{{.Name}} {{$.GetVariable .}} // {{.Desc}} + {{- end}} + {{- end}} + ) + + var ( + {{- range .Templates}} + {{- if $.HasIndex .}}{{- else}} + _{{.Name}} *{{$.GetConfigName .}} // {{.Desc}} + {{- end}} + {{- end}} + ) + + {{- range .Templates}} + // {{$.GetConfigName .}} {{.Desc}} + type {{$.GetConfigName .}} struct { + {{- range .Fields}} + {{- if .IsSlice}} + {{- if .IsStruct}} + {{.Name}} []*{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} []{{.Type}} // {{.Desc}} + {{- end}} + {{- else}} + {{- if .IsStruct}} + {{.Name}} *{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} {{.Type}} // {{.Desc}} + {{- end}} + {{- end}} + {{- end}} + } + + func (slf *{{$.GetConfigName .}}) String() string { + if data, err := json.Marshal(slf); err == nil { + return string(data) + } + return "{}" + } + {{- end}} + + {{- range .Templates}} + {{- range .AllChildren}} + // {{.Name}} {{.Desc}} + type {{.Name}} struct { + {{- range .Fields}} + {{- if .IsSlice}} + {{- if .IsStruct}} + {{.Name}} []*{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} []{{.Type}} // {{.Desc}} + {{- end}} + {{- else}} + {{- if .IsStruct}} + {{.Name}} *{{.Struct.Name}} // {{.Desc}} + {{- else}} + {{.Name}} {{.Type}} // {{.Desc}} + {{- end}} + {{- end}} + {{- end}} + } + + {{- end}} + {{- end}} + + // Load 通过自定义的方式加载配置 + // - 通常建议使用该方法加载配置,因为仅执行一次加解锁 + func LoadWithHandle(handle func(sign Sign, config any, json jsonIter.API) error) { + mutex.Lock() + defer mutex.Unlock() + for _, sign := range signs { + switch sign { + {{- range .Templates}} + case {{.Name}}Sign: + {{- if $.HasIndex .}} + temp := make({{$.GetVariable .}}) + {{- else}} + temp := new({{$.GetConfigName .}}) + {{- end}} + if err := handle(sign, &temp, json);err != nil { + log.Error("Config", log.String("Name", "{{.Name}}"), log.Bool("Invalid", true), log.Err(err)) + }else { + _{{.Name}} = temp + } + {{- end}} + } + } + } + + // LoadIndexConfig 通过 JSON 加载配置 + func LoadWithJSON(sign Sign, data []byte) { + switch sign { + {{- range .Templates}} + case {{.Name}}Sign: + {{- if $.HasIndex .}} + temp := make({{$.GetVariable .}}) + {{- else}} + temp := new({{$.GetConfigName .}}) + {{- end}} + if err := json.Unmarshal(data, &{{.Name}}); err != nil { + log.Error("Config", log.String("Name", "{{.Name}}"), log.Bool("Invalid", true), log.Err(err)) + return + } + _{{.Name}} = temp + {{- end}} + } + } + + // Refresh 将加载后的配置刷新到线上 + func Refresh() { + mutex.Lock() + defer mutex.Unlock() + cs := make(map[Sign]any) + + {{- range .Templates}} + {{.Name}} = _{{.Name}} + cs[{{.Name}}Sign] = {{.Name}} + {{- end}} + + configs = cs + } + + // GetConfigs 获取所有配置 + func GetConfigs() map[Sign]any { + return configs + } + + // GetConfigSigns 获取所有配置的标识 + func GetConfigSigns() []Sign { + return signs + } + `, slf) +} + +func (slf *Golang) GetVariable(config *pce.TmplStruct) string { + var prefix string + for _, field := range config.Fields { + if field.IsIndex() { + prefix += fmt.Sprintf("map[%s]", field.Type) + } + } + return fmt.Sprintf("%s*%s", prefix, slf.GetConfigName(config)) +} + +func (slf *Golang) GetConfigName(config *pce.TmplStruct) string { + return strings.ReplaceAll(config.Name, "Config", "Configuration") +} + +func (slf *Golang) HasIndex(config *pce.TmplStruct) bool { + return config.IndexCount > 0 +} diff --git a/planner/pce/tmpls/json.go b/planner/pce/tmpls/json.go new file mode 100644 index 0000000..50fffa8 --- /dev/null +++ b/planner/pce/tmpls/json.go @@ -0,0 +1,29 @@ +package tmpls + +import ( + "bytes" + jsonIter "github.com/json-iterator/go" + "github.com/kercylan98/minotaur/utils/str" +) + +func NewJSON() *JSON { + return &JSON{ + API: jsonIter.ConfigCompatibleWithStandardLibrary, + } +} + +type JSON struct { + jsonIter.API +} + +func (slf *JSON) Render(data map[any]any) (string, error) { + buffer := &bytes.Buffer{} + encoder := jsonIter.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + err := encoder.Encode(data) + if err != nil { + return str.None, err + } + return buffer.String(), nil +} diff --git a/planner/pce/tmpls/tmpls.go b/planner/pce/tmpls/tmpls.go new file mode 100644 index 0000000..ee184b1 --- /dev/null +++ b/planner/pce/tmpls/tmpls.go @@ -0,0 +1,19 @@ +package tmpls + +import ( + "bytes" + "github.com/kercylan98/minotaur/utils/str" + "text/template" +) + +func render(temp string, o any) (string, error) { + tmpl, err := template.New(temp).Parse(temp) + if err != nil { + return str.None, err + } + var buf bytes.Buffer + if err = tmpl.Execute(&buf, o); err != nil { + return str.None, err + } + return buf.String(), nil +} diff --git a/report/reporter_strategy.go b/report/reporter_strategy.go index d76143b..eecaaba 100644 --- a/report/reporter_strategy.go +++ b/report/reporter_strategy.go @@ -9,9 +9,9 @@ import ( // ReporterStrategy 上报器策略 type ReporterStrategy func(reporter *Reporter) -// ReportStrategyLoop 循环上报 +// StrategyLoop 循环上报 // - 将在创建后上报一次,并且在每隔一段时间后继续上报 -func ReportStrategyLoop(t time.Duration) ReporterStrategy { +func StrategyLoop(t time.Duration) ReporterStrategy { return func(reporter *Reporter) { reporter.ticker.Loop(fmt.Sprintf("ReportStrategyLoop_%d", t.Milliseconds()), timer.Instantly, t, timer.Forever, func() { if err := reporter.Report(); err != nil && reporter.errorHandle != nil { @@ -21,8 +21,8 @@ func ReportStrategyLoop(t time.Duration) ReporterStrategy { } } -// ReportStrategyFixedTime 将在每天的固定时间上报 -func ReportStrategyFixedTime(hour, min, sec int) ReporterStrategy { +// StrategyFixedTime 将在每天的固定时间上报 +func StrategyFixedTime(hour, min, sec int) ReporterStrategy { return func(reporter *Reporter) { now := time.Now() current := now.Unix() diff --git a/server/cross/nats.go b/server/cross/nats.go index 43b2f78..8b13d21 100644 --- a/server/cross/nats.go +++ b/server/cross/nats.go @@ -7,7 +7,6 @@ import ( "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/synchronization" "github.com/nats-io/nats.go" - "go.uber.org/zap" "time" ) @@ -47,10 +46,10 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p nats.ReconnectWait(time.Second*5), nats.MaxReconnects(-1), nats.DisconnectErrHandler(func(conn *nats.Conn, err error) { - log.Error(nasMark, zap.String("info", "disconnect"), zap.Error(err)) + log.Error(nasMark, log.String("info", "disconnect"), log.Err(err)) }), nats.ReconnectHandler(func(conn *nats.Conn) { - log.Info(nasMark, zap.String("info", "reconnect")) + log.Info(nasMark, log.String("info", "reconnect")) }), ) } @@ -63,7 +62,7 @@ func (slf *Nats) Init(server *server.Server, packetHandle func(serverId int64, p message := slf.messagePool.Get() defer slf.messagePool.Release(message) if err := json.Unmarshal(msg.Data, &message); err != nil { - log.Error(nasMark, zap.Error(err)) + log.Error(nasMark, log.Err(err)) return } packetHandle(message.ServerId, message.Packet) diff --git a/server/event.go b/server/event.go index 02b2402..c906c2d 100644 --- a/server/event.go +++ b/server/event.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/runtimes" - "go.uber.org/zap" "reflect" + "runtime/debug" "sync" "time" ) @@ -41,13 +41,15 @@ type event struct { // RegStopEvent 服务器停止时将立即执行被注册的事件处理函数 func (slf *event) RegStopEvent(handle StopEventHandle) { slf.stopEventHandles = append(slf.stopEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnStopEvent() { - for _, handle := range slf.stopEventHandles { - handle(slf.Server) - } + PushSystemMessage(slf.Server, func() { + for _, handle := range slf.stopEventHandles { + handle(slf.Server) + } + }) } // RegConsoleCommandEvent 控制台收到指令时将立即执行被注册的事件处理函数 @@ -65,33 +67,41 @@ func (slf *event) RegConsoleCommandEvent(command string, handle ConsoleCommandEv }() }) slf.consoleCommandEventHandles[command] = append(slf.consoleCommandEventHandles[command], handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnConsoleCommandEvent(command string) { - handles, exist := slf.consoleCommandEventHandles[command] - if !exist { - switch command { - case "exit", "quit", "close", "shutdown", "EXIT", "QUIT", "CLOSE", "SHUTDOWN": - log.Info("Console", zap.String("Receive", command), zap.String("Action", "Shutdown")) - slf.Server.shutdown(nil) - return + PushSystemMessage(slf.Server, func() { + handles, exist := slf.consoleCommandEventHandles[command] + if !exist { + switch command { + case "exit", "quit", "close", "shutdown", "EXIT", "QUIT", "CLOSE", "SHUTDOWN": + log.Info("Console", log.String("Receive", command), log.String("Action", "Shutdown")) + slf.Server.shutdown(nil) + return + } + log.Warn("Server", log.String("Command", "unregistered")) + } else { + for _, handle := range handles { + handle(slf.Server) + } } - log.Warn("Server", zap.String("Command", "unregistered")) - } else { - for _, handle := range handles { - handle(slf.Server) - } - } + }) } // RegStartBeforeEvent 在服务器初始化完成启动前立刻执行被注册的事件处理函数 func (slf *event) RegStartBeforeEvent(handle StartBeforeEventHandle) { slf.startBeforeEventHandles = append(slf.startBeforeEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnStartBeforeEvent() { + defer func() { + if err := recover(); err != nil { + log.Error("Server", log.String("OnStartBeforeEvent", fmt.Sprintf("%v", err))) + debug.PrintStack() + } + }() for _, handle := range slf.startBeforeEventHandles { handle(slf.Server) } @@ -100,13 +110,15 @@ func (slf *event) OnStartBeforeEvent() { // RegStartFinishEvent 在服务器启动完成时将立刻执行被注册的事件处理函数 func (slf *event) RegStartFinishEvent(handle StartFinishEventHandle) { slf.startFinishEventHandles = append(slf.startFinishEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnStartFinishEvent() { - for _, handle := range slf.startFinishEventHandles { - handle(slf.Server) - } + PushSystemMessage(slf.Server, func() { + for _, handle := range slf.startFinishEventHandles { + handle(slf.Server) + } + }) } // RegConnectionClosedEvent 在连接关闭后将立刻执行被注册的事件处理函数 @@ -115,15 +127,17 @@ func (slf *event) RegConnectionClosedEvent(handle ConnectionClosedEventHandle) { panic(ErrNetworkIncompatibleHttp) } slf.connectionClosedEventHandles = append(slf.connectionClosedEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnConnectionClosedEvent(conn *Conn, err any) { - for _, handle := range slf.connectionClosedEventHandles { - handle(slf.Server, conn, err) - } - conn.Close() - slf.Server.online.Delete(conn.GetID()) + PushSystemMessage(slf.Server, func() { + for _, handle := range slf.connectionClosedEventHandles { + handle(slf.Server, conn, err) + } + conn.Close() + slf.Server.online.Delete(conn.GetID()) + }) } // RegConnectionOpenedEvent 在连接打开后将立刻执行被注册的事件处理函数 @@ -132,14 +146,16 @@ func (slf *event) RegConnectionOpenedEvent(handle ConnectionOpenedEventHandle) { panic(ErrNetworkIncompatibleHttp) } slf.connectionOpenedEventHandles = append(slf.connectionOpenedEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnConnectionOpenedEvent(conn *Conn) { - slf.Server.online.Set(conn.GetID(), conn) - for _, handle := range slf.connectionOpenedEventHandles { - handle(slf.Server, conn) - } + PushSystemMessage(slf.Server, func() { + slf.Server.online.Set(conn.GetID(), conn) + for _, handle := range slf.connectionOpenedEventHandles { + handle(slf.Server, conn) + } + }) } // RegConnectionReceivePacketEvent 在接收到数据包时将立刻执行被注册的事件处理函数 @@ -148,7 +164,7 @@ func (slf *event) RegConnectionReceivePacketEvent(handle ConnectionReceivePacket panic(ErrNetworkIncompatibleHttp) } slf.connectionReceivePacketEventHandles = append(slf.connectionReceivePacketEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnConnectionReceivePacketEvent(conn *Conn, packet Packet) { @@ -160,7 +176,7 @@ func (slf *event) OnConnectionReceivePacketEvent(conn *Conn, packet Packet) { // RegReceiveCrossPacketEvent 在接收到跨服数据包时将立即执行被注册的事件处理函数 func (slf *event) RegReceiveCrossPacketEvent(handle ReceiveCrossPacketEventHandle) { slf.receiveCrossPacketEventHandles = append(slf.receiveCrossPacketEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnReceiveCrossPacketEvent(serverId int64, packet []byte) { @@ -172,25 +188,29 @@ func (slf *event) OnReceiveCrossPacketEvent(serverId int64, packet []byte) { // RegMessageErrorEvent 在处理消息发生错误时将立即执行被注册的事件处理函数 func (slf *event) RegMessageErrorEvent(handle MessageErrorEventHandle) { slf.messageErrorEventHandles = append(slf.messageErrorEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnMessageErrorEvent(message *Message, err error) { - for _, handle := range slf.messageErrorEventHandles { - handle(slf.Server, message, err) - } + PushSystemMessage(slf.Server, func() { + for _, handle := range slf.messageErrorEventHandles { + handle(slf.Server, message, err) + } + }) } // RegMessageLowExecEvent 在处理消息缓慢时将立即执行被注册的事件处理函数 func (slf *event) RegMessageLowExecEvent(handle MessageLowExecEventHandle) { slf.messageLowExecEventHandles = append(slf.messageLowExecEventHandles, handle) - log.Info("Server", zap.String("RegEvent", runtimes.CurrentRunningFuncName()), zap.String("handle", reflect.TypeOf(handle).String())) + log.Info("Server", log.String("RegEvent", runtimes.CurrentRunningFuncName()), log.String("handle", reflect.TypeOf(handle).String())) } func (slf *event) OnMessageLowExecEvent(message *Message, cost time.Duration) { - for _, handle := range slf.messageLowExecEventHandles { - handle(slf.Server, message, cost) - } + PushSystemMessage(slf.Server, func() { + for _, handle := range slf.messageLowExecEventHandles { + handle(slf.Server, message, cost) + } + }) } func (slf *event) check() { @@ -198,12 +218,12 @@ func (slf *event) check() { case NetworkHttp, NetworkGRPC: default: if len(slf.connectionReceivePacketEventHandles) == 0 { - log.Warn("Server", zap.String("ConnectionReceivePacketEvent", "invalid server, no packets processed")) + log.Warn("Server", log.String("ConnectionReceivePacketEvent", "invalid server, no packets processed")) } } if len(slf.receiveCrossPacketEventHandles) > 0 && slf.cross == nil { - log.Warn("Server", zap.String("ReceiveCrossPacketEvent", "invalid server, not register cross server")) + log.Warn("Server", log.String("ReceiveCrossPacketEvent", "invalid server, not register cross server")) } } diff --git a/server/gnet.go b/server/gnet.go index 73f9d57..06877eb 100644 --- a/server/gnet.go +++ b/server/gnet.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/kercylan98/minotaur/utils/log" "github.com/panjf2000/gnet" - "go.uber.org/zap" "time" ) @@ -23,7 +22,7 @@ func (slf *gNet) OnShutdown(server gnet.Server) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() if err := gnet.Stop(ctx, fmt.Sprintf("%s://%s", slf.network, slf.addr)); err != nil { - log.Error("Server", zap.String("Minotaur GNet Server", "Shutdown"), zap.Error(err)) + log.Error("Server", log.String("Minotaur GNet Server", "Shutdown"), log.Err(err)) } } diff --git a/server/message.go b/server/message.go index 8c9d5cb..6bac3ae 100644 --- a/server/message.go +++ b/server/message.go @@ -21,6 +21,9 @@ const ( // MessageTypeAsync 异步消息类型 MessageTypeAsync + + // MessageTypeSystem 系统消息类型 + MessageTypeSystem ) var messageNames = map[MessageType]string{ @@ -29,6 +32,7 @@ var messageNames = map[MessageType]string{ MessageTypeCross: "MessageTypeCross", MessageTypeTicker: "MessageTypeTicker", MessageTypeAsync: "MessageTypeAsync", + MessageTypeSystem: "MessageTypeSystem", } const ( @@ -146,6 +150,14 @@ func PushAsyncMessage(srv *Server, caller func() error, callback func(err error) srv.pushMessage(msg) } +// PushSystemMessage 向特定服务器中推送 MessageTypeSystem 消息 +func PushSystemMessage(srv *Server, handle func(), mark ...any) { + msg := srv.messagePool.Get() + msg.t = MessageTypeSystem + msg.attrs = append([]any{handle}, mark...) + srv.pushMessage(msg) +} + // SetMessagePacketVisualizer 设置消息可视化函数 // - 消息可视化将在慢消息等情况用于打印,使用自定消息可视化函数可以便于开发者进行调试 // - 默认的消息可视化函数将直接返回消息的字符串表示 diff --git a/server/multiple.go b/server/multiple.go index e3ddad2..06a96f9 100644 --- a/server/multiple.go +++ b/server/multiple.go @@ -2,7 +2,6 @@ package server import ( "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" "os" "os/signal" "sync" @@ -60,14 +59,14 @@ func (slf *MultipleServer) Run() { } wait.Wait() - log.Info("Server", zap.String(serverMultipleMark, "====================================================================")) + log.Info("Server", log.String(serverMultipleMark, "====================================================================")) for _, server := range slf.servers { - log.Info("Server", zap.String(serverMultipleMark, "RunningInfo"), - zap.Any("network", server.network), - zap.String("listen", server.addr), + log.Info("Server", log.String(serverMultipleMark, "RunningInfo"), + log.Any("network", server.network), + log.String("listen", server.addr), ) } - log.Info("Server", zap.String(serverMultipleMark, "====================================================================")) + log.Info("Server", log.String(serverMultipleMark, "====================================================================")) systemSignal := make(chan os.Signal, 1) signal.Notify(systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) diff --git a/server/options.go b/server/options.go index 610d35b..3039366 100644 --- a/server/options.go +++ b/server/options.go @@ -4,7 +4,6 @@ import ( "github.com/gin-contrib/pprof" "github.com/kercylan98/minotaur/utils/log" "github.com/kercylan98/minotaur/utils/timer" - "go.uber.org/zap" "google.golang.org/grpc" "reflect" "time" @@ -87,7 +86,7 @@ func WithDeadlockDetect(t time.Duration) Option { return func(srv *Server) { if t > 0 { srv.deadlockDetect = t - log.Info("DeadlockDetect", zap.String("Time", t.String())) + log.Info("DeadlockDetect", log.String("Time", t.String())) } } } @@ -153,11 +152,11 @@ func WithCross(crossName string, serverId int64, cross Cross) Option { srv.pushMessage(msg) }) if err != nil { - log.Info("Cross", zap.Int64("ServerID", serverId), zap.String("Cross", reflect.TypeOf(cross).String()), zap.String("State", "WaitNatsRun")) + log.Info("Cross", log.Int64("ServerID", serverId), log.String("Cross", reflect.TypeOf(cross).String()), log.String("State", "WaitNatsRun")) time.Sleep(1 * time.Second) goto start } - log.Info("Cross", zap.Int64("ServerID", serverId), zap.String("Cross", reflect.TypeOf(cross).String())) + log.Info("Cross", log.Int64("ServerID", serverId), log.String("Cross", reflect.TypeOf(cross).String())) } } } diff --git a/server/server.go b/server/server.go index 162aafe..e89b0c2 100644 --- a/server/server.go +++ b/server/server.go @@ -14,7 +14,6 @@ import ( "github.com/panjf2000/gnet" "github.com/panjf2000/gnet/pkg/logging" "github.com/xtaci/kcp-go/v5" - "go.uber.org/zap" "google.golang.org/grpc" "net" "net/http" @@ -114,6 +113,7 @@ func (slf *Server) Run(addr string) error { slf.event.check() slf.addr = addr var protoAddr = fmt.Sprintf("%s://%s", slf.network, slf.addr) + var messageInitFinish = make(chan struct{}, 1) var connectionInitHandle = func(callback func()) { slf.messagePool = synchronization.NewPool[*Message](slf.messagePoolSize, func() *Message { @@ -132,6 +132,7 @@ func (slf *Server) Run(addr string) error { go callback() } go func() { + messageInitFinish <- struct{}{} for message := range slf.messageChannel { slf.dispatchMessage(message) } @@ -312,13 +313,16 @@ func (slf *Server) Run(addr string) error { return ErrCanNotSupportNetwork } + <-messageInitFinish + close(messageInitFinish) + messageInitFinish = nil if slf.multiple == nil { - log.Info("Server", zap.String(serverMark, "====================================================================")) - log.Info("Server", zap.String(serverMark, "RunningInfo"), - zap.Any("network", slf.network), - zap.String("listen", slf.addr), + log.Info("Server", log.String(serverMark, "====================================================================")) + log.Info("Server", log.String(serverMark, "RunningInfo"), + log.Any("network", slf.network), + log.String("listen", slf.addr), ) - log.Info("Server", zap.String(serverMark, "====================================================================")) + log.Info("Server", log.String(serverMark, "====================================================================")) slf.OnStartFinishEvent() signal.Notify(slf.systemSignal, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) @@ -417,15 +421,15 @@ func (slf *Server) shutdown(err error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() if shutdownErr := slf.httpServer.Shutdown(ctx); shutdownErr != nil { - log.Error("Server", zap.Error(shutdownErr)) + log.Error("Server", log.Err(shutdownErr)) } } if err != nil { if slf.multiple != nil { slf.multiple.RegExitEvent(func() { - log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr), - zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err)) + log.Panic("Server", log.Any("network", slf.network), log.String("listen", slf.addr), + log.String("action", "shutdown"), log.String("state", "exception"), log.Err(err)) }) for i, server := range slf.multiple.servers { if server.addr == slf.addr { @@ -434,12 +438,12 @@ func (slf *Server) shutdown(err error) { } } } else { - log.Panic("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr), - zap.String("action", "shutdown"), zap.String("state", "exception"), zap.Error(err)) + log.Panic("Server", log.Any("network", slf.network), log.String("listen", slf.addr), + log.String("action", "shutdown"), log.String("state", "exception"), log.Err(err)) } } else { - log.Info("Server", zap.Any("network", slf.network), zap.String("listen", slf.addr), - zap.String("action", "shutdown"), zap.String("state", "normal")) + log.Info("Server", log.Any("network", slf.network), log.String("listen", slf.addr), + log.String("action", "shutdown"), log.String("state", "normal")) } if slf.gServer == nil { slf.closeChannel <- struct{}{} @@ -474,7 +478,7 @@ func (slf *Server) pushMessage(message *Message) { func (slf *Server) low(message *Message, present time.Time, expect time.Duration) { cost := time.Since(present) if cost > expect { - log.Warn("Server", zap.String("type", "low-message"), zap.String("cost", cost.String()), zap.String("message", message.String()), zap.Stack("stack")) + log.Warn("Server", log.String("type", "low-message"), log.String("cost", cost.String()), log.String("message", message.String()), log.Stack("stack")) slf.OnMessageLowExecEvent(message, cost) } } @@ -491,7 +495,7 @@ func (slf *Server) dispatchMessage(msg *Message) { select { case <-ctx.Done(): if err := ctx.Err(); err == context.DeadlineExceeded { - log.Warn("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("SuspectedDeadlock", msg.attrs)) + log.Warn("Server", log.String("MessageType", messageNames[msg.t]), log.Any("SuspectedDeadlock", msg.attrs)) } } }() @@ -501,7 +505,7 @@ func (slf *Server) dispatchMessage(msg *Message) { defer func() { if err := recover(); err != nil { stack := string(debug.Stack()) - log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("MessageAttrs", msg.attrs), zap.Any("error", err), zap.String("stack", stack)) + log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("MessageAttrs", msg.attrs), log.Any("error", err), log.String("stack", stack)) fmt.Println(stack) if e, ok := err.(error); ok { slf.OnMessageErrorEvent(msg, e) @@ -530,11 +534,11 @@ func (slf *Server) dispatchMessage(msg *Message) { err, action := attrs[0].(error), attrs[1].(MessageErrorAction) switch action { case MessageErrorActionNone: - log.Panic("Server", zap.Error(err)) + log.Panic("Server", log.Err(err)) case MessageErrorActionShutdown: slf.shutdown(err) default: - log.Warn("Server", zap.String("not support message error action", action.String())) + log.Warn("Server", log.String("not support message error action", action.String())) } case MessageTypeCross: slf.OnReceiveCrossPacketEvent(attrs[0].(int64), attrs[1].([]byte)) @@ -547,7 +551,7 @@ func (slf *Server) dispatchMessage(msg *Message) { defer func() { if err := recover(); err != nil { stack := string(debug.Stack()) - log.Error("Server", zap.String("MessageType", messageNames[msg.t]), zap.Any("error", err), zap.String("stack", stack)) + log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", stack)) fmt.Println(stack) if e, ok := err.(error); ok { slf.OnMessageErrorEvent(msg, e) @@ -560,15 +564,18 @@ func (slf *Server) dispatchMessage(msg *Message) { slf.messagePool.Release(msg) } }() - if err := handle(); err != nil { - if cb { - callback(err) - } + err := handle() + if cb { + callback(err) + } else { + log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack()))) } }); err != nil { panic(err) } + case MessageTypeSystem: + attrs[0].(func())() default: - log.Warn("Server", zap.String("not support message type", msg.t.String())) + log.Warn("Server", log.String("not support message type", msg.t.String())) } } diff --git a/utils/asynchronous/map.go b/utils/asynchronous/map.go index a852ff1..36524a4 100644 --- a/utils/asynchronous/map.go +++ b/utils/asynchronous/map.go @@ -146,7 +146,7 @@ func (slf *Map[Key, Value]) rangeFree(handle func(key Key, value Value, skip fun func (slf *Map[Key, Value]) Keys() []Key { var s = make([]Key, 0, len(slf.data)) - for k, _ := range slf.data { + for k := range slf.data { s = append(s, k) } return s diff --git a/utils/file/file.go b/utils/file/file.go index c17936c..871eeb4 100644 --- a/utils/file/file.go +++ b/utils/file/file.go @@ -102,10 +102,10 @@ func LineCount(filePath string) int { return line } -// FilePaths 获取指定目录下的所有文件路径 +// Paths 获取指定目录下的所有文件路径 // - 包括了子目录下的文件 // - 不包含目录 -func FilePaths(dir string) []string { +func Paths(dir string) []string { var paths []string abs, err := filepath.Abs(dir) if err != nil { @@ -119,7 +119,7 @@ func FilePaths(dir string) []string { for _, file := range files { fileAbs := filepath.Join(abs, file.Name()) if file.IsDir() { - paths = append(paths, FilePaths(fileAbs)...) + paths = append(paths, Paths(fileAbs)...) continue } paths = append(paths, fileAbs) diff --git a/utils/file/file_test.go b/utils/file/file_test.go index 7f24ff4..ae45998 100644 --- a/utils/file/file_test.go +++ b/utils/file/file_test.go @@ -10,7 +10,7 @@ import ( func TestFilePaths(t *testing.T) { var line int var fileCount int - for _, path := range file.FilePaths(`D:\sources\minotaur`) { + for _, path := range file.Paths(`D:\sources\minotaur`) { if !strings.HasSuffix(path, ".go") { continue } diff --git a/utils/geometry/navmesh/navmesh_example_test.go b/utils/geometry/navmesh/navmesh_example_test.go index 5075147..2571a51 100644 --- a/utils/geometry/navmesh/navmesh_example_test.go +++ b/utils/geometry/navmesh/navmesh_example_test.go @@ -69,7 +69,7 @@ func ExampleNavMesh_FindPath() { for x := sx; x <= bx; x++ { for y := sy; y <= by; y++ { - fp.Put(geometry.NewPoint[int](int(x), int(y)), '+') + fp.Put(geometry.NewPoint[int](x, y), '+') } } } diff --git a/utils/hash/srot_map.go b/utils/hash/srot_map.go index 9df2975..a913413 100644 --- a/utils/hash/srot_map.go +++ b/utils/hash/srot_map.go @@ -55,7 +55,7 @@ func (slf *SortMap[K, V]) For(handle func(key K, value V) bool) { func (slf *SortMap[K, V]) ForSort(handle func(key K, value V) bool) { var indexes []int - for i, _ := range slf.s { + for i := range slf.s { indexes = append(indexes, i) } sort.Ints(indexes) @@ -85,7 +85,7 @@ func (slf *SortMap[K, V]) ToSlice() []V { func (slf *SortMap[K, V]) ToSliceSort() []V { var indexes []int - for i, _ := range slf.s { + for i := range slf.s { indexes = append(indexes, i) } sort.Ints(indexes) diff --git a/utils/log/field.go b/utils/log/field.go index 5919e66..80ef71e 100644 --- a/utils/log/field.go +++ b/utils/log/field.go @@ -156,4 +156,7 @@ var ( // Any 接受一个键和一个任意值,并选择将它们表示为字段的最佳方式,仅在必要时才回退到基于反射的方法。 // 由于 byteuint8 和 runeint32 是别名,Any 无法区分它们。为了尽量减少意外情况,[]byte 值被视为二进制 blob,字节值被视为 uint8,而 runes 始终被视为整数 Any = zap.Any + + // Err 是常见习语 NamedError("error", err) 的简写 + Err = zap.Error ) diff --git a/utils/maths/math.go b/utils/maths/math.go index 03ef7f4..991ddb6 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -27,7 +27,7 @@ func Pow(a, n int) int { if n == 1 { return a } - var result int = 1 + var result = 1 factor := a for n != 0 { if n&1 != 0 { diff --git a/utils/super/gofmt.go b/utils/super/gofmt.go new file mode 100644 index 0000000..a4f2d24 --- /dev/null +++ b/utils/super/gofmt.go @@ -0,0 +1,9 @@ +package super + +import "os/exec" + +// GoFormat go 代码格式化 +func GoFormat(filePath string) { + cmd := exec.Command("gofmt", "-w", filePath) + _ = cmd.Run() +} diff --git a/utils/super/match.go b/utils/super/match.go new file mode 100644 index 0000000..4c83a8b --- /dev/null +++ b/utils/super/match.go @@ -0,0 +1,34 @@ +package super + +import "reflect" + +// Matcher 匹配器 +type Matcher[Value, Result any] struct { + value Value + r Result + d bool +} + +// Match 匹配 +func Match[Value, Result any](value Value) *Matcher[Value, Result] { + return &Matcher[Value, Result]{ + value: value, + } +} + +// Case 匹配 +func (slf *Matcher[Value, Result]) Case(value Value, result Result) *Matcher[Value, Result] { + if !slf.d && reflect.DeepEqual(slf.value, value) { + slf.r = result + slf.d = true + } + return slf +} + +// Default 默认 +func (slf *Matcher[Value, Result]) Default(value Result) Result { + if slf.d { + return slf.r + } + return value +} diff --git a/utils/super/match_test.go b/utils/super/match_test.go new file mode 100644 index 0000000..3f094c0 --- /dev/null +++ b/utils/super/match_test.go @@ -0,0 +1,24 @@ +package super_test + +import ( + "github.com/kercylan98/minotaur/utils/super" + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +func TestMatch(t *testing.T) { + Convey("TestMatch", t, func() { + So(super.Match[int, string](1). + Case(1, "a"). + Case(2, "b"). + Default("c"), ShouldEqual, "a") + So(super.Match[int, string](2). + Case(1, "a"). + Case(2, "b"). + Default("c"), ShouldEqual, "b") + So(super.Match[int, string](3). + Case(1, "a"). + Case(2, "b"). + Default("c"), ShouldEqual, "c") + }) +} diff --git a/utils/super/parse.go b/utils/super/parse.go new file mode 100644 index 0000000..bcb742e --- /dev/null +++ b/utils/super/parse.go @@ -0,0 +1,9 @@ +package super + +import "strconv" + +// StringToInt 字符串转换为整数 +func StringToInt(value string) int { + i, _ := strconv.Atoi(value) + return i +} diff --git a/utils/synchronization/map.go b/utils/synchronization/map.go index 947f35a..f1b0974 100644 --- a/utils/synchronization/map.go +++ b/utils/synchronization/map.go @@ -216,7 +216,7 @@ func (slf *Map[Key, Value]) Keys() []Key { defer slf.lock.RUnlock() } var s = make([]Key, 0, len(slf.data)) - for k, _ := range slf.data { + for k := range slf.data { s = append(s, k) } return s diff --git a/utils/synchronization/map_segment.go b/utils/synchronization/map_segment.go index 853bd44..414e72e 100644 --- a/utils/synchronization/map_segment.go +++ b/utils/synchronization/map_segment.go @@ -183,7 +183,7 @@ func (slf *MapSegment[Key, Value]) RangeFree(handle func(key Key, value Value, s func (slf *MapSegment[Key, Value]) Keys() []Key { var s = make([]Key, 0, len(slf.cache)) slf.lock.RLock() - for k, _ := range slf.cache { + for k := range slf.cache { s = append(s, k) } defer slf.lock.RUnlock() diff --git a/utils/synchronization/pool.go b/utils/synchronization/pool.go index e8a9da4..9b2ed24 100644 --- a/utils/synchronization/pool.go +++ b/utils/synchronization/pool.go @@ -2,7 +2,6 @@ package synchronization import ( "github.com/kercylan98/minotaur/utils/log" - "go.uber.org/zap" "sync" ) @@ -42,7 +41,7 @@ func (slf *Pool[T]) Get() T { slf.mutex.Unlock() slf.warn++ if slf.warn >= 100 { - log.Warn("Pool", zap.String("Get", "the number of buffer members is insufficient, consider whether it is due to unreleased or inappropriate buffer size")) + log.Warn("Pool", log.String("Get", "the number of buffer members is insufficient, consider whether it is due to unreleased or inappropriate buffer size")) slf.warn = 0 } return slf.generator()