feat: 重构 config 和 configexport 包

- 配置加载包 config 更名为 configuration
- 配置导出包 configexport 更名为 pce
- 重构
config 包加载方式,采用加载器的方式,并且支持多加载器
- 重构 configexport
包,支持通过实现模板的方式导出不同格式的数据文件及结构文件
This commit is contained in:
kercylan98 2023-07-17 13:28:17 +08:00
parent 8e2b4ebc89
commit 7e7a504421
36 changed files with 1368 additions and 1580 deletions

View File

@ -1,89 +0,0 @@
package config
import (
jsonIter "github.com/json-iterator/go"
"github.com/kercylan98/minotaur/utils/log"
"github.com/kercylan98/minotaur/utils/timer"
"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", log.String("Name", filename), log.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 配置刷新事件处理函数 // RefreshEventHandle 配置刷新事件处理函数
type RefreshEventHandle func() 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

@ -1,213 +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"
"os/exec"
"path/filepath"
"runtime/debug"
"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",
log.String("File", xlsxPath),
log.String("Sheet", sheet.Name),
log.String("Info", "A configuration with the same name exists, skipped"),
)
case internal.ErrReadConfigFailedIgnore:
log.Info("ConfigExport",
log.String("File", xlsxPath),
log.String("Sheet", sheet.Name),
log.String("Info", "Excluded non-configuration table files"),
)
default:
log.Error("ConfigExport",
log.String("File", xlsxPath),
log.String("Sheet", sheet.Name),
log.String("Info", "Excluded non-configuration table files"),
)
debug.PrintStack()
}
} else {
if config == nil {
continue
}
ce.configs = append(ce.configs, config)
ce.exist[config.Name] = true
log.Info("ConfigExport",
log.String("File", xlsxPath),
log.String("Sheet", sheet.Name),
log.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",
log.String("File", ce.xlsxPath),
log.String("Sheet", config.Name),
log.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.Err("Config", log.String("Name", "IndexConfig"), log.Bool("Invalid", true), log.Err(err))
}
_EasyConfig = new(EasyConfigDefine)
if err = handle("EasyConfig.json", _EasyConfig); err != nil {
log.Err("Config", log.String("Name", "EasyConfig"), log.Bool("Invalid", true), log.Err(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,14 +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")
ErrReadConfigFailedWithNameDuplicate = errors.New("read config index count failed, duplicate field names")
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.Err("Config", log.String("Name", "{{$config.Name}}"), log.Bool("Invalid", true), log.Err(err))
}
{{else}}
if err = handle("{{$config.Prefix}}{{$config.Name}}.json", &_{{$config.Name}}); err != nil {
log.Err("Config", log.String("Name", "{{$config.Name}}"), log.Bool("Invalid", true), log.Err(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}}
`
)

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

@ -0,0 +1,16 @@
package pce
type Config interface {
// GetConfigName 配置名称
GetConfigName() string
// GetDisplayName 配置显示名称
GetDisplayName() string
// GetDescription 配置描述
GetDescription() string
// GetIndexCount 索引数量
GetIndexCount() int
// GetFields 获取字段
GetFields() []dataField
// GetData 获取数据
GetData() [][]dataInfo
}

133
planner/pce/config_xlsx.go Normal file
View File

@ -0,0 +1,133 @@
package pce
import (
"github.com/kercylan98/minotaur/utils/str"
"github.com/tealeg/xlsx"
"strings"
)
func NewIndexXlsxConfig(sheet *xlsx.Sheet) *XlsxIndexConfig {
config := &XlsxIndexConfig{
sheet: sheet,
}
return config
}
// XlsxIndexConfig 内置的 Xlsx 配置
type XlsxIndexConfig struct {
sheet *xlsx.Sheet
}
func (slf *XlsxIndexConfig) GetConfigName() string {
return str.FirstUpper(strings.TrimSpace(slf.sheet.Rows[0].Cells[1].String()))
}
func (slf *XlsxIndexConfig) GetDisplayName() string {
return slf.sheet.Name
}
func (slf *XlsxIndexConfig) GetDescription() string {
return "暂无描述"
}
func (slf *XlsxIndexConfig) GetIndexCount() int {
index, err := slf.sheet.Rows[1].Cells[1].Int()
if err != nil {
panic(err)
}
return index
}
func (slf *XlsxIndexConfig) GetFields() []dataField {
var fields []dataField
for x := 1; x < slf.getWidth(); x++ {
var (
desc = slf.get(x, 3)
name = slf.get(x, 4)
fieldType = slf.get(x, 5)
exportType = slf.get(x, 6)
)
if desc == nil || name == nil || fieldType == nil || exportType == nil {
continue
}
if len(desc.String()) == 0 || len(name.String()) == 0 || len(fieldType.String()) == 0 || len(exportType.String()) == 0 {
continue
}
fields = append(fields, dataField{
Name: name.String(),
Type: fieldType.String(),
ExportType: exportType.String(),
Desc: desc.String(),
})
}
return fields
}
func (slf *XlsxIndexConfig) GetData() [][]dataInfo {
var data [][]dataInfo
var fields = slf.GetFields()
for y := 7; y < slf.getHeight(); y++ {
var line []dataInfo
var stop bool
for x := 0; x < slf.getWidth(); x++ {
if prefixCell := slf.get(x, y); prefixCell != nil {
prefix := prefixCell.String()
if strings.HasPrefix(prefix, "#") {
break
}
}
if x == 0 {
continue
}
var isIndex = x-1 < slf.GetIndexCount()
var value string
if valueCell := slf.get(x, y); valueCell != nil {
value = valueCell.String()
} else if isIndex {
stop = true
break
}
valueCell := slf.get(x, y)
if valueCell == nil {
break
}
if len(fields) > x-1 {
line = append(line, dataInfo{
dataField: fields[x-1],
Value: value,
})
}
}
if len(line) > 0 {
data = append(data, line)
}
if stop {
break
}
}
return data
}
// getWidth 获取宽度
func (slf *XlsxIndexConfig) getWidth() int {
return slf.sheet.MaxCol
}
// getHeight 获取高度
func (slf *XlsxIndexConfig) getHeight() int {
return slf.sheet.MaxRow
}
// get 获取单元格
func (slf *XlsxIndexConfig) 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]
}

View File

@ -0,0 +1,25 @@
package pce_test
import (
"github.com/kercylan98/minotaur/planner/pce"
"github.com/tealeg/xlsx"
"testing"
)
func TestNewXlsxIndexConfig(t *testing.T) {
f, err := xlsx.OpenFile(`D:\sources\minotaur\planner\ce\template.xlsx`)
if err != nil {
panic(err)
}
xlsxConfig := pce.NewIndexXlsxConfig(f.Sheets[1])
loader := pce.NewLoader()
loader.BindField(
new(pce.Int),
new(pce.String),
)
loader.LoadStruct(xlsxConfig)
loader.LoadData(xlsxConfig)
}

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

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

@ -0,0 +1,53 @@
package pce
import (
"github.com/kercylan98/minotaur/utils/file"
"os/exec"
"path/filepath"
)
// NewExporter 创建导出器
func NewExporter(filePath string) *Exporter {
return &Exporter{
filePath: filePath,
}
}
// Exporter 导出器
type Exporter struct {
filePath string
}
// ExportStruct 导出结构
func (slf *Exporter) ExportStruct(tmpl Tmpl, tmplStruct ...*TmplStruct) error {
filePath, err := filepath.Abs(slf.filePath)
if err != nil {
return err
}
raw, err := tmpl.Render(tmplStruct)
if err != nil {
return err
}
if err = file.WriterFile(filePath, []byte(raw)); err != nil {
return err
}
cmd := exec.Command("gofmt", "-w", filePath)
return cmd.Run()
}
// ExportData 导出数据
func (slf *Exporter) ExportData(tmpl DataTmpl, data map[any]any) error {
filePath, err := filepath.Abs(slf.filePath)
if err != nil {
return err
}
raw, err := tmpl.Render(data)
if err != nil {
return err
}
if err = file.WriterFile(filePath, []byte(raw)); err != nil {
return err
}
return nil
}

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

@ -0,0 +1,25 @@
package pce
import (
"github.com/kercylan98/minotaur/utils/super"
"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())
name := strings.ToLower(typeOf.Name())
return super.If(kind == name, kind, name)
}

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

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

@ -0,0 +1,456 @@
package pce
import (
"github.com/kercylan98/minotaur/utils/super"
"math"
"strconv"
)
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() *Loader {
return &Loader{
fields: make(map[string]Field),
}
}
// Loader 配置加载器
type Loader struct {
fields map[string]Field
}
// BindField 绑定字段
func (slf *Loader) BindField(fields ...Field) {
for _, field := range fields {
slf.fields[field.TypeName()] = 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 < 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
}
}
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 {
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,38 @@
package pce
import (
"github.com/kercylan98/minotaur/utils/hash"
)
// TmplStruct 模板结构
type TmplStruct struct {
Name string // 结构名称
Desc string // 结构描述
Fields []*TmplField // 字段列表
}
// 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)
}
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
}

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

@ -0,0 +1,174 @@
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}}"
{{- end}}
)
var (
json = jsonIter.ConfigCompatibleWithStandardLibrary
configs map[Sign]any = map[Sign]any{
{{- range .Templates}}
{{.Name}}Sign: {{.Name}},
{{- end}}
}
signs = []Sign{
{{- range .Templates}}
{{.Name}}Sign,
{{- end}}
}
mutex sync.Mutex
)
var (
{{- range .Templates}}
{{.Name}} {{$.GetVariable .}}
{{- end}}
)
var (
{{- range .Templates}}
_{{.Name}} {{$.GetVariable .}}
{{- end}}
)
{{- range .Templates}}
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}}
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 {
{{- range .Templates}}
temp := make({{$.GetVariable .}})
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:
temp := make({{$.GetVariable .}})
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()
{{- range .Templates}}
{{.Name}} = _{{.Name}}
_{{.Name}} = nil
{{- end}}
}
`, 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")
}

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
}

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
}