Merge branch 'develop'

This commit is contained in:
kercylan98 2023-07-17 17:05:35 +08:00
commit a62db174f4
61 changed files with 1752 additions and 1699 deletions

View File

@ -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()
}

View File

@ -1,2 +0,0 @@
// Package config 基于配置导表功能实现的配置加载及刷新功能
package config

73
configuration/config.go Normal file
View File

@ -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)
}
}

2
configuration/doc.go Normal file
View File

@ -0,0 +1,2 @@
// Package configuration 基于配置导表功能实现的配置加载及刷新功能
package configuration

View File

@ -1,4 +1,4 @@
package config
package configuration
// RefreshEventHandle 配置刷新事件处理函数
type RefreshEventHandle func()

10
configuration/loader.go Normal file
View File

@ -0,0 +1,10 @@
package configuration
// Loader 配置加载器
type Loader interface {
// Load 加载配置
// - 加载后并不会刷新线上配置,需要执行 Refresh 函数对线上配置进行刷新
Load()
// Refresh 刷新线上配置
Refresh()
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -1,2 +0,0 @@
// Package configexport 提供了XLSX配置转换为JSON及Go代码的导表工具实现
package configexport

View File

@ -1,17 +0,0 @@
{
"Id": 1,
"Award": {
"0": "asd",
"1": "12"
},
"Other": {
"0": {
"id": 1,
"name": "张飞"
},
"1": {
"id": 2,
"name": "刘备"
}
}
}

View File

@ -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": "刘备"
}
}
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())
}

View File

@ -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")
)

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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}}
`
)

19
planner/pce/config.go Normal file
View File

@ -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
}

200
planner/pce/cs/xlsx.go Normal file
View File

@ -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
}

View File

@ -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)
})
}

7
planner/pce/data_tmpl.go Normal file
View File

@ -0,0 +1,7 @@
package pce
// DataTmpl 数据导出模板
type DataTmpl interface {
// Render 渲染模板
Render(data map[any]any) (string, error)
}

28
planner/pce/exporter.go Normal file
View File

@ -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
}

23
planner/pce/field.go Normal file
View File

@ -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
}

11
planner/pce/field_test.go Normal file
View File

@ -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)))
}

491
planner/pce/fields.go Normal file
View File

@ -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
}

175
planner/pce/loader.go Normal file
View File

@ -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 // 导出类型
}

7
planner/pce/tmpl.go Normal file
View File

@ -0,0 +1,7 @@
package pce
// Tmpl 配置结构模板接口
type Tmpl interface {
// Render 渲染模板
Render(templates ...*TmplStruct) (string, error)
}

105
planner/pce/tmpl_field.go Normal file
View File

@ -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)
}
}

View File

@ -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
}

221
planner/pce/tmpls/golang.go Normal file
View File

@ -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
}

29
planner/pce/tmpls/json.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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()

View File

@ -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)

View File

@ -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"))
}
}

View File

@ -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))
}
}

View File

@ -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 设置消息可视化函数
// - 消息可视化将在慢消息等情况用于打印,使用自定消息可视化函数可以便于开发者进行调试
// - 默认的消息可视化函数将直接返回消息的字符串表示

View File

@ -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)

View File

@ -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()))
}
}
}

View File

@ -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()))
}
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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), '+')
}
}
}

View File

@ -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)

View File

@ -156,4 +156,7 @@ var (
// Any 接受一个键和一个任意值,并选择将它们表示为字段的最佳方式,仅在必要时才回退到基于反射的方法。
// 由于 byteuint8 和 runeint32 是别名Any 无法区分它们。为了尽量减少意外情况,[]byte 值被视为二进制 blob字节值被视为 uint8而 runes 始终被视为整数
Any = zap.Any
// Err 是常见习语 NamedError("error", err) 的简写
Err = zap.Error
)

View File

@ -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 {

9
utils/super/gofmt.go Normal file
View File

@ -0,0 +1,9 @@
package super
import "os/exec"
// GoFormat go 代码格式化
func GoFormat(filePath string) {
cmd := exec.Command("gofmt", "-w", filePath)
_ = cmd.Run()
}

34
utils/super/match.go Normal file
View File

@ -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
}

24
utils/super/match_test.go Normal file
View File

@ -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")
})
}

9
utils/super/parse.go Normal file
View File

@ -0,0 +1,9 @@
package super
import "strconv"
// StringToInt 字符串转换为整数
func StringToInt(value string) int {
i, _ := strconv.Atoi(value)
return i
}

View File

@ -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

View File

@ -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()

View File

@ -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()