Files
mayfly-go/server/pkg/starter/config_tool.go
2026-01-25 14:16:16 +08:00

219 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package starter
import (
"fmt"
"mayfly-go/pkg/logx"
"mayfly-go/pkg/utils/structx"
"reflect"
"strconv"
"strings"
)
// ConfigItem 配置项
type ConfigItem interface {
// 如果不存在配置值,则设置默认值,并校验配置项
ApplyDefaults() error
}
// ConfigError 配置错误类型
type ConfigError struct {
Field string // 字段名
Message string // 错误描述
}
func (e *ConfigError) Error() string {
return fmt.Sprintf("配置错误: [%s] %s", e.Field, e.Message)
}
// NewConfigError 创建配置错误
func NewConfigError(field, message string) error {
return &ConfigError{
Field: field,
Message: message,
}
}
// LogDefaultValue 记录配置默认值日志
func LogDefaultValue(field, defaultValue any) {
logx.Warnf("配置项 [%s] 未设置,使用默认值: %v", field, defaultValue)
}
// ApplyConfigDefaults 应用所有配置项的默认值支持递归处理嵌套结构体和ConfigItem接口
func ApplyConfigDefaults(obj any, parentConfigPath ...string) error {
parentPath := strings.Join(parentConfigPath, ".")
configValue := reflect.ValueOf(obj).Elem()
configType := configValue.Type()
for i := 0; i < configValue.NumField(); i++ {
field := configValue.Field(i)
fieldType := configType.Field(i)
// 检查字段是否为导出的字段
if !field.CanSet() || !field.CanAddr() {
continue
}
// 如果字段是指针类型,需要先创建实例
if field.Kind() == reflect.Ptr {
if field.IsNil() {
newValue := reflect.New(field.Type().Elem())
field.Set(newValue)
}
field = field.Elem()
}
// 获取字段地址,用于调用方法
fieldAddr := field.Addr()
// 检查字段是否实现了 ConfigItem 接口
if fieldAddr.Type().Implements(reflect.TypeOf((*ConfigItem)(nil)).Elem()) {
// 实现了接口,调用 ApplyDefaults 方法
method := fieldAddr.MethodByName("ApplyDefaults")
if method.IsValid() {
results := method.Call(nil)
if len(results) > 0 {
if err, ok := results[0].Interface().(error); ok && err != nil {
return err
}
}
}
} else if field.Kind() == reflect.Struct {
// 为嵌套结构体确定其路径组件
structPath := getFieldName(fieldType)
var currentPath string
if parentPath != "" {
currentPath = parentPath + "." + structPath
} else {
currentPath = structPath
}
if err := ApplyConfigDefaults(fieldAddr.Interface(), currentPath); err != nil {
return err
}
} else {
// 处理普通字段的默认值设置,传递父路径
if err := applyConfigFieldDefaults(field, fieldType, parentPath); err != nil {
return err
}
}
}
return nil
}
// applyConfigFieldDefaults 处理单个字段的默认值设置和选项验证等
func applyConfigFieldDefaults(field reflect.Value, fieldType reflect.StructField, parentPath string) error {
// 构建当前字段的完整配置路径
currentPath := getFieldName(fieldType)
if parentPath != "" {
currentPath = parentPath + "." + currentPath
}
// 检查是否已设置值
if structx.IsZeroValue(field) {
defaultTag := fieldType.Tag.Get("default")
if defaultTag == "" {
return nil
}
if err := setFieldValue(field, defaultTag); err != nil {
return fmt.Errorf("设置字段 [%s] 的默认值失败: %v", currentPath, err)
}
LogDefaultValue(currentPath, defaultTag)
} else {
// 检查选项验证
optionsTag := fieldType.Tag.Get("options")
if optionsTag != "" {
if err := validateOptions(currentPath, field, fieldType, optionsTag); err != nil {
return err
}
}
}
return nil
}
// setFieldValue 设置字段值
func setFieldValue(field reflect.Value, defaultValue string) error {
switch field.Kind() {
case reflect.String:
field.SetString(defaultValue)
case reflect.Bool:
val, err := strconv.ParseBool(defaultValue)
if err != nil {
return err
}
field.SetBool(val)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := strconv.ParseInt(defaultValue, 10, 64)
if err != nil {
return err
}
field.SetInt(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := strconv.ParseUint(defaultValue, 10, 64)
if err != nil {
return err
}
field.SetUint(val)
case reflect.Float32, reflect.Float64:
val, err := strconv.ParseFloat(defaultValue, 64)
if err != nil {
return err
}
field.SetFloat(val)
default:
return fmt.Errorf("不支持的类型: %v", field.Kind())
}
return nil
}
// getFieldName 获取字段名称,用于日志输出
func getFieldName(field reflect.StructField) string {
yamlTag := field.Tag.Get("yaml")
if yamlTag != "" && yamlTag != "-" {
return yamlTag
}
return strings.ToLower(field.Name)
}
// validateOptions 验证字段值是否在允许的选项范围内
func validateOptions(configPath string, field reflect.Value, typeField reflect.StructField, optionsTag string) error {
// 解析选项列表
options := strings.Split(optionsTag, ",")
if len(options) == 0 {
return nil
}
// 清理选项空格
for i, option := range options {
options[i] = strings.TrimSpace(option)
}
var fieldValue string
switch field.Kind() {
case reflect.String:
fieldValue = field.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fieldValue = strconv.FormatInt(field.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fieldValue = strconv.FormatUint(field.Uint(), 10)
case reflect.Bool:
fieldValue = strconv.FormatBool(field.Bool())
default:
return fmt.Errorf("字段 %s 不支持选项验证,类型: %v", typeField.Name, field.Kind())
}
// 检查字段值是否在允许的选项中
for _, option := range options {
if fieldValue == option {
return nil
}
}
return NewConfigError(configPath, fmt.Sprintf("值 '%s' 不在允许的选项范围内: [%s]", fieldValue, strings.Join(options, ", ")))
}