daShangDao_xy_dll/utils/iniConfigUtil/iniConfigUtil.go

379 lines
11 KiB
Go
Raw Permalink 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 utils 提供配置文件加载工具支持从INI文件加载配置到结构体
// 支持类型: int, string, bool, float, time.Duration 及其切片类型
// 特性:
// - 支持默认值标签 `default`
// - 支持INI路径标签 `ini:"section.key"`
// - 支持时间单位转换标签 `multiplier`
// - 自动处理切片类型(逗号分隔)
package iniConfigUtil
import (
"log"
"os"
"reflect"
"regexp"
"strconv"
"time"
"github.com/go-ini/ini"
)
// LoadConfig 从INI文件加载配置到结构体
// 参数:
//
// configPtr: 指向配置结构体的指针(必须是结构体指针)
// filename: INI配置文件的路径//
//
// 返回:
//
// error: 加载成功返回nil失败返回ConfigError详细错误信息
//
// 用法说明:
// 1. 定义配置结构体使用标签声明INI映射关系和默认值
// 2. 调用LoadConfig(&config, "config.ini")
// 3. 检查错误并应用配置
//
// 示例结构体:
//
// type AppConfig struct {
// Port int `ini:"service.port" default:"8080"`
// Timeout time.Duration `ini:"service.timeout" multiplier:"1s"`
// Features []string `ini:"service.features"`
// }
func LoadConfig(configPtr interface{}, filename string) error {
// 验证输入必须是指针
if reflect.TypeOf(configPtr).Kind() != reflect.Ptr {
return &ConfigError{Message: "configPtr必须是指向结构体的指针"} // 返回错误
}
// 验证输入必须指向结构体
configValue := reflect.ValueOf(configPtr).Elem()
// 确保输入是结构体
if configValue.Kind() != reflect.Struct {
return &ConfigError{Message: "configPtr必须指向结构体"} // 返回错误
}
// 设置默认值(如果结构体有默认值)
setDefaultValues(configValue)
// 检查配置文件是否存在
if _, err := os.Stat(filename); os.IsNotExist(err) {
log.Printf("配置文件不存在: %s, 使用默认值", filename) // 打印信息
return nil // 返回错误
}
// 加载INI文件
iniCfg, err := ini.Load(filename)
// 处理错误
if err != nil {
return &ConfigError{Message: "加载配置文件失败", Cause: err} // 返回错误
}
// 映射配置到结构体
if err := mapConfig(iniCfg, configValue); err != nil {
return err // 返回错误
}
// 处理特殊类型如time.Duration
processSpecialTypes(configValue)
// 返回成功
return nil
}
// setDefaultValues 设置结构体字段的默认值
// 遍历结构体字段,检测`default`标签并设置初始值
// setDefaultValues 设置结构体字段的默认值
func setDefaultValues(configValue reflect.Value) {
// 获取结构体类型
configType := configValue.Type()
// 遍历字段
for i := 0; i < configType.NumField(); i++ {
// 获取字段信息
field := configType.Field(i)
// 获取字段值
fieldValue := configValue.Field(i)
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
setDefaultValues(fieldValue)
continue
}
// 如果字段已经设置了值,跳过
if !fieldValue.IsZero() {
continue // 跳过
}
// 检查是否有默认值标签
if defaultValue, ok := field.Tag.Lookup("default"); ok {
setValueFromString(fieldValue, defaultValue) // 设置字段值
}
}
}
// mapConfig 将INI配置映射到结构体字段
// 解析`ini`标签获取section和key读取对应配置值
func mapConfig(iniCfg *ini.File, configValue reflect.Value) error {
// 获取结构体类型
configType := configValue.Type()
// 遍历字段
for i := 0; i < configType.NumField(); i++ {
field := configType.Field(i) // 获取字段信息
fieldValue := configValue.Field(i) // 获取字段值
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
if err := mapConfig(iniCfg, fieldValue); err != nil {
return err
}
continue
}
// 获取INI标签
iniTag, ok := field.Tag.Lookup("ini")
// 如果没有INI标签跳过这个字段
if !ok || iniTag == "" {
continue // 跳过
}
// 解析INI键名支持section.key格式
sectionName, keyName := parseIniTag(iniTag)
// 获取INI值
section, err := iniCfg.GetSection(sectionName)
// 如果section不存在
if err != nil {
continue // 跳过这个字段
}
// 获取INI键值
key, err := section.GetKey(keyName)
// 如果key不存在
if err != nil {
continue //跳过这个字段
}
// 设置结构体字段值
if err := setFieldValue(fieldValue, key); err != nil {
// 返回错误
return &ConfigError{
Message: "设置字段值失败", // 返回错误信息
Cause: err, // 返回错误原因
Field: field.Name, // 返回字段名称
Tag: iniTag, // 返回标签
}
}
}
return nil
}
// parseIniTag 解析INI标签格式
// 输入: "section.key" 格式的字符串
// 返回: (section名称, key名称)
func parseIniTag(tag string) (section, key string) {
// 默认section
section = "DEFAULT"
// 默认key
key = tag
// 检查是否有section前缀
if parts := regexp.MustCompile(`^(\w+)\.(\w+)$`).FindStringSubmatch(tag); len(parts) == 3 {
section = parts[1] // 设置section
key = parts[2] // 设置key
}
// 返回section和key
return section, key
}
// setFieldValue 根据INI键值设置结构体字段值
// 自动处理基础类型和time.Duration类型转换
func setFieldValue(fieldValue reflect.Value, key *ini.Key) error {
// 检查字段类型
switch fieldValue.Kind() {
case reflect.String: // 字符串类型
fieldValue.SetString(key.String()) // 设置字段值
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
// 特殊处理time.Duration类型
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
duration, err := time.ParseDuration(key.String()) // 解析字符串为time.Duration
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetInt(int64(duration)) // 设置字段值
} else {
intValue, err := key.Int() // 获取整数值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetInt(int64(intValue)) // 设置字段值
}
case reflect.Bool: // 布尔类型
boolValue, err := key.Bool() // 获取布尔值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetBool(boolValue) // 设置字段值
case reflect.Float32, reflect.Float64: // 浮点类型
floatValue, err := key.Float64() // 获取浮点值
// 处理错误
if err != nil {
return err // 返回错误
}
fieldValue.SetFloat(floatValue) // 设置字段值
case reflect.Slice: // 切片类型
return setSliceValue(fieldValue, key) // 处理切片类型字段
default: // 其他类型
return &ConfigError{Message: "不支持的字段类型", FieldType: fieldValue.Type().String()} // 返回错误
}
// 返回成功
return nil
}
// setSliceValue 设置切片类型字段值
// 将逗号分隔的字符串解析为指定类型的切片
func setSliceValue(fieldValue reflect.Value, key *ini.Key) error {
// 获取切片元素类型
sliceType := fieldValue.Type().Elem()
// 获取逗号分隔的值
values := key.Strings(",")
// 创建新切片
slice := reflect.MakeSlice(fieldValue.Type(), len(values), len(values))
// 遍历值
for i, val := range values {
elemValue := reflect.New(sliceType).Elem() // 创建新元素
// 根据切片元素类型设置值
switch sliceType.Kind() {
case reflect.String: // 字符串类型
elemValue.SetString(val) // 设置字段值
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // 整数类型
intVal, err := strconv.ParseInt(val, 10, 64) // 解析字符串为整数
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetInt(intVal) // 设置字段值
case reflect.Float32, reflect.Float64: // 浮点类型
floatVal, err := strconv.ParseFloat(val, 64) // 解析字符串为浮点数
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetFloat(floatVal) // 设置字段值
case reflect.Bool: // 布尔类型
boolVal, err := strconv.ParseBool(val) // 解析字符串为布尔值
// 处理错误
if err != nil {
return err // 返回错误
}
elemValue.SetBool(boolVal) // 设置字段值
default:
return &ConfigError{Message: "不支持的切片元素类型", FieldType: sliceType.String()} // 返回错误
}
slice.Index(i).Set(elemValue) // 设置切片元素
}
// 设置切片字段值
fieldValue.Set(slice)
// 返回成功
return nil
}
// setValueFromString 从字符串解析值到结构体字段(用于默认值)
// 支持基础类型转换,不处理复杂类型
func setValueFromString(fieldValue reflect.Value, valueStr string) {
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(valueStr)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if intValue, err := strconv.ParseInt(valueStr, 10, 64); err == nil {
fieldValue.SetInt(intValue)
}
case reflect.Bool:
if boolValue, err := strconv.ParseBool(valueStr); err == nil {
fieldValue.SetBool(boolValue)
}
case reflect.Float32, reflect.Float64:
if floatValue, err := strconv.ParseFloat(valueStr, 64); err == nil {
fieldValue.SetFloat(floatValue)
}
case reflect.Slice:
// 切片类型需要特殊处理,这里简化处理
default:
// 保留原panic调用但修改提示信息
panic("未处理的默认值类型")
}
}
// processSpecialTypes 处理特殊类型转换
// 当前支持time.Duration的倍数转换使用multiplier标签
// processSpecialTypes 处理特殊类型转换
func processSpecialTypes(configValue reflect.Value) {
// 获取结构体类型
configType := configValue.Type()
// 遍历结构体字段
for i := 0; i < configType.NumField(); i++ {
field := configType.Field(i) // 获取字段
fieldValue := configValue.Field(i) // 获取字段值
// 如果字段是结构体,递归处理
if fieldValue.Kind() == reflect.Struct {
processSpecialTypes(fieldValue)
continue
}
// 处理time.Duration类型的倍数转换
if fieldValue.Type() == reflect.TypeOf(time.Duration(0)) {
// 检查是否存在multiplier标签
if multiplier, ok := field.Tag.Lookup("multiplier"); ok {
// 解析multiplier标签
if mult, err := time.ParseDuration(multiplier); err == nil {
duration := time.Duration(fieldValue.Int()) // 将字段值转换为time.Duration
fieldValue.SetInt(int64(duration * mult)) // 将字段值设置为转换后的时间
}
}
}
}
}
// ConfigError 自定义配置错误类型
// 包含错误原因、字段信息和原始错误
type ConfigError struct {
Message string
Cause error
Field string
FieldType string
Tag string
}
// Error 实现error接口提供详细错误信息
func (e *ConfigError) Error() string {
msg := "配置错误: " + e.Message
if e.Field != "" {
msg += " [字段: " + e.Field + "]"
}
if e.FieldType != "" {
msg += " [类型: " + e.FieldType + "]"
}
if e.Tag != "" {
msg += " [标签: " + e.Tag + "]"
}
if e.Cause != nil {
msg += " - " + e.Cause.Error()
}
return msg
}