ini - INI 配置文件解析库
ini 是一个功能强大的 INI 配置文件解析库,基于 go-ini 进行改进,主要增强了环境变量注入功能。支持多种数据源、类型转换、变量引用、环境变量替换等高级特性。
安装
go get -u go-slim.dev/ini快速开始
基础用法
package mainimport ( "fmt" "log" "go-slim.dev/ini")func main() { // 创建 INI 管理器并加载文件 cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { log.Fatal(err) } // 读取配置值 section := cfg.Section("database") host := section.String("host") port := section.MustInt("port", 3306) fmt.Printf("Database: %s:%d\n", host, port)}INI 文件示例
# config.ini# 应用配置[app]name = MyAppdebug = trueport = 8080# 数据库配置[database]host = localhostport = 3306username = rootpassword = secretmax_connections = 100# Redis 配置[redis]host = localhostport = 6379database = 0核心概念
1. Manager(管理器)
Manager 是 INI 配置的核心管理对象,负责加载、解析和管理所有的配置数据。
2. Section(节)
Section 代表 INI 文件中的一个配置节(用 [section_name] 标记)。
3. Key(键)
Key 代表配置节中的一个键值对。
层级结构
Manager └── Section 1 ├── Key 1 ├── Key 2 └── ... └── Section 2 ├── Key 1 └── ...核心功能
1. 创建管理器
New - 创建新的管理器
func New(opts Options) *Manager说明:
创建一个新的 INI 管理器,使用指定的选项。
参数:
opts:配置选项(见 Options 说明)
示例:
// 使用默认选项cfg := ini.New(ini.Options{})// 自定义选项cfg := ini.New(ini.Options{ Insensitive: true, // 键名不区分大小写 AllowBooleanKeys: true, // 允许布尔类型键 IgnoreInlineComment: false, // 不忽略行内注释})2. 加载数据源
Append - 追加数据源
func (m *Manager) Append(source any, others ...any) error说明:
追加一个或多个数据源并自动重新加载。支持多种数据源类型。
支持的数据源类型:
string:文件路径[]byte:字节切片io.Reader:读取器io.ReadCloser:可关闭的读取器DataSource:自定义数据源接口func() (io.ReadCloser, error):数据源工厂函数
示例:
cfg := ini.New(ini.Options{})// 从文件加载cfg.Append("config.ini")// 从多个文件加载cfg.Append("base.ini", "production.ini", "local.ini")// 从字节切片加载data := []byte(`[app]name = MyAppport = 8080`)cfg.Append(data)// 从 io.Reader 加载file, _ := os.Open("config.ini")cfg.Append(file)Batch - 批量操作
func (m *Manager) Batch(fn func(m *Manager) error) error说明:
批量执行多个操作,期间不会触发自动重新加载,所有操作完成后统一加载。
示例:
cfg := ini.New(ini.Options{})err := cfg.Batch(func(m *ini.Manager) error { // 批量加载多个文件,只在最后重新加载一次 if err := m.Append("base.ini"); err != nil { return err } if err := m.Append("override.ini"); err != nil { return err } return nil})Reload - 重新加载
func (m *Manager) Reload() error说明:
重新加载并解析所有数据源。会清空现有数据并重新解析。
示例:
// 修改配置文件后重新加载if err := cfg.Reload(); err != nil { log.Fatal(err)}3. Section 操作
Section - 获取节
func (m *Manager) Section(name string) *Section说明:
获取指定名称的节。如果节不存在,返回一个零值节(不会报错)。
示例:
// 获取节dbSection := cfg.Section("database")appSection := cfg.Section("app")// 默认节(文件开头没有节名的部分)defaultSection := cfg.Section("")GetSection - 获取节(带错误检查)
func (m *Manager) GetSection(name string) (*Section, error)说明:
获取指定名称的节。如果节不存在,返回错误。
示例:
section, err := cfg.GetSection("database")if err != nil { log.Printf("Section not found: %v", err) return}HasSection - 检查节是否存在
func (m *Manager) HasSection(name string) bool说明:
检查指定的节是否存在。
示例:
if cfg.HasSection("database") { // 数据库配置存在}NewSection - 创建新节
func (m *Manager) NewSection(name string) *Section说明:
创建一个新的节。如果节已存在,返回现有的节。
示例:
// 创建新节section := cfg.NewSection("cache")section.NewKey("driver", "redis")section.NewKey("host", "localhost")4. Key 操作
Key - 获取键
func (s *Section) Key(name string) *Key说明:
获取指定名称的键。如果键不存在,返回一个零值键(不会报错)。
示例:
section := cfg.Section("database")hostKey := section.Key("host")portKey := section.Key("port")GetKey - 获取键(带错误检查)
func (s *Section) GetKey(name string) (*Key, error)说明:
获取指定名称的键。如果键不存在,返回错误。
支持子节查找:如果当前节中找不到键,会向上查找父节。
示例:
section := cfg.Section("database")key, err := section.GetKey("host")if err != nil { log.Printf("Key not found: %v", err) return}HasKey - 检查键是否存在
func (s *Section) HasKey(name string) bool示例:
section := cfg.Section("database")if section.HasKey("password") { // 密码配置存在}NewKey - 创建新键
func (s *Section) NewKey(name, value string) *Key说明:
在节中创建一个新的键值对。如果键已存在,返回现有的键。
示例:
section := cfg.NewSection("app")section.NewKey("name", "MyApp")section.NewKey("version", "1.0.0")Keys - 获取所有键
func (s *Section) Keys() []*Key说明:
返回节中所有的键。
示例:
section := cfg.Section("database")for _, key := range section.Keys() { fmt.Printf("%s = %s\n", key.Name(), key.String())}5. 值读取 - 基本类型
String - 获取字符串值
func (s *Section) String(name string) stringfunc (k *Key) String() string示例:
// 从 Section 读取host := cfg.Section("database").String("host")// 从 Key 读取key := cfg.Section("database").Key("host")value := key.String()MustString - 获取字符串值(带默认值)
func (s *Section) MustString(name string, defaultVal ...string) stringfunc (k *Key) MustString(defaultVal string) string示例:
// 如果键不存在或为空,返回默认值host := cfg.Section("database").MustString("host", "localhost")Int/Int64/Uint/Uint64 - 获取整数值
func (s *Section) Int(name string) (int, error)func (s *Section) Int64(name string) (int64, error)func (s *Section) Uint(name string) (uint, error)func (s *Section) Uint64(name string) (uint64, error)示例:
port, err := cfg.Section("database").Int("port")if err != nil { log.Printf("Invalid port: %v", err)}maxConns, _ := cfg.Section("database").Uint("max_connections")MustInt/MustInt64/MustUint/MustUint64 - 获取整数值(带默认值)
func (s *Section) MustInt(name string, defaultVal ...int) intfunc (s *Section) MustInt64(name string, defaultVal ...int64) int64func (s *Section) MustUint(name string, defaultVal ...uint) uintfunc (s *Section) MustUint64(name string, defaultVal ...uint64) uint64示例:
// 转换失败或不存在时返回默认值port := cfg.Section("app").MustInt("port", 8080)timeout := cfg.Section("server").MustInt64("timeout", 30)maxSize := cfg.Section("upload").MustUint("max_size", 1024)Float64 - 获取浮点数值
func (s *Section) Float64(name string) (float64, error)func (s *Section) MustFloat64(name string, defaultVal ...float64) float64示例:
rate, err := cfg.Section("app").Float64("tax_rate")ratio := cfg.Section("app").MustFloat64("compression_ratio", 0.8)Bool - 获取布尔值
func (s *Section) Bool(name string) (bool, error)func (s *Section) MustBool(name string, defaultVal ...bool) bool支持的布尔值:
true: “1”, “t”, “T”, “true”, “TRUE”, “True”, “YES”, “yes”, “Yes”, “y”, “ON”, “on”, “On”false: “0”, “f”, “F”, “false”, “FALSE”, “False”, “NO”, “no”, “No”, “n”, “OFF”, “off”, “Off”
示例:
debug, err := cfg.Section("app").Bool("debug")verbose := cfg.Section("app").MustBool("verbose", false)Duration - 获取时间间隔
func (s *Section) Duration(name string) (time.Duration, error)func (s *Section) MustDuration(name string, defaultVal ...time.Duration) time.Duration示例:
// config.ini// [server]// timeout = 30s// interval = 5mtimeout, err := cfg.Section("server").Duration("timeout")interval := cfg.Section("server").MustDuration("interval", 1*time.Minute)Time - 获取时间值
func (s *Section) Time(name string) (time.Time, error)func (s *Section) TimeFormat(name string, format string) (time.Time, error)func (s *Section) MustTime(name string, defaultVal ...time.Time) time.Timefunc (s *Section) MustTimeFormat(name string, format string, defaultVal ...time.Time) time.Time示例:
// config.ini// [app]// created_at = 2023-12-25T10:30:45Z// updated_at = 2023/12/25 10:30:45// RFC3339 格式createdAt, err := cfg.Section("app").Time("created_at")// 自定义格式updatedAt, err := cfg.Section("app").TimeFormat("updated_at", "2006/01/02 15:04:05")// 带默认值defaultTime := time.Now()lastLogin := cfg.Section("user").MustTime("last_login", defaultTime)6. 列表值读取
Strings - 获取字符串列表
func (s *Section) Strings(name string, delim string) []stringfunc (k *Key) Strings(delim string) []string说明:
使用指定的分隔符分割值,返回字符串列表。
示例:
// config.ini// [app]// allowed_origins = http://localhost:3000,https://example.com,https://api.example.com// tags = go, web, apiorigins := cfg.Section("app").Strings("allowed_origins", ",")// []string{"http://localhost:3000", "https://example.com", "https://api.example.com"}tags := cfg.Section("app").Strings("tags", ",")// []string{"go", "web", "api"} (自动 trim 空格)Ints/Int64s/Uints/Uint64s - 获取整数列表
func (s *Section) Ints(name string, delim string) []intfunc (s *Section) Int64s(name string, delim string) []int64func (s *Section) Uints(name string, delim string) []uintfunc (s *Section) Uint64s(name string, delim string) []uint64说明:
分割值并转换为整数列表。无效的值会被转换为零值。
示例:
// config.ini// [app]// ports = 8080,8081,8082// retry_delays = 1,2,5,10ports := cfg.Section("app").Ints("ports", ",")// []int{8080, 8081, 8082}delays := cfg.Section("app").Int64s("retry_delays", ",")// []int64{1, 2, 5, 10}ValidInts/ValidInt64s - 获取有效整数列表
func (s *Section) ValidInts(name string, delim string) []intfunc (s *Section) ValidInt64s(name string, delim string) []int64说明:
分割值并转换为整数列表。无效的值会被跳过(不包含在结果中)。
示例:
// config.ini// [app]// ports = 8080,invalid,8081,8082ports := cfg.Section("app").Ints("ports", ",")// []int{8080, 0, 8081, 8082} (invalid 转换为 0)validPorts := cfg.Section("app").ValidInts("ports", ",")// []int{8080, 8081, 8082} (invalid 被跳过)StrictInts/StrictInt64s - 获取严格整数列表
func (s *Section) StrictInts(name string, delim string) ([]int, error)func (s *Section) StrictInt64s(name string, delim string) ([]int64, error)说明:
分割值并转换为整数列表。遇到第一个无效值时返回错误。
示例:
// config.ini// [app]// ports = 8080,8081,8082// bad_ports = 8080,invalid,8082ports, err := cfg.Section("app").StrictInts("ports", ",")// []int{8080, 8081, 8082}, nilbadPorts, err := cfg.Section("app").StrictInts("bad_ports", ",")// nil, error (遇到 "invalid" 时返回错误)Float64s/Bools/Times - 其他类型列表
// 浮点数列表func (s *Section) Float64s(name string, delim string) []float64func (s *Section) ValidFloat64s(name string, delim string) []float64func (s *Section) StrictFloat64s(name string, delim string) ([]float64, error)// 布尔值列表func (s *Section) Bools(name string, delim string) []boolfunc (s *Section) ValidBools(name string, delim string) []boolfunc (s *Section) StrictBools(name string, delim string) ([]bool, error)// 时间列表func (s *Section) Times(name string, delim string) []time.Timefunc (s *Section) TimesFormat(name string, format, delim string) []time.Timefunc (s *Section) ValidTimes(name string, delim string) []time.Timefunc (s *Section) StrictTimes(name string, delim string) ([]time.Time, error)示例:
// config.ini// [app]// rates = 0.1,0.2,0.3// features = true,false,true,false// timestamps = 2023-01-01T00:00:00Z,2023-06-01T00:00:00Zrates := cfg.Section("app").Float64s("rates", ",")features := cfg.Section("app").Bools("features", ",")timestamps := cfg.Section("app").Times("timestamps", ",")7. 值验证和范围检查
In - 检查值是否在候选列表中
func (s *Section) In(name string, defaultVal string, candidates []string) stringfunc (s *Section) InInt(name string, defaultVal int, candidates []int) intfunc (s *Section) InFloat64(name string, defaultVal float64, candidates []float64) float64说明:
检查值是否在给定的候选列表中。如果不在,返回默认值。
示例:
// config.ini// [app]// log_level = debug// port = 8080// 只允许特定的日志级别logLevel := cfg.Section("app").In("log_level", "info", []string{"debug", "info", "warn", "error"})// 返回 "debug"invalidLevel := cfg.Section("app").In("invalid_level", "info", []string{"debug", "info", "warn", "error"})// 键不存在,返回默认值 "info"// 只允许特定的端口port := cfg.Section("app").InInt("port", 8080, []int{8080, 8081, 8082})// 返回 8080Range - 范围检查
func (s *Section) RangeInt(name string, defaultVal, min, max int) intfunc (s *Section) RangeInt64(name string, defaultVal, min, max int64) int64func (s *Section) RangeFloat64(name string, defaultVal, min, max float64) float64func (s *Section) RangeTime(name string, defaultVal, min, max time.Time) time.Time说明:
检查值是否在给定范围内(包含边界)。如果超出范围,返回默认值。
示例:
// config.ini// [server]// port = 8080// workers = 4// rate_limit = 0.95// 端口必须在 1024-65535 之间port := cfg.Section("server").RangeInt("port", 8080, 1024, 65535)// 返回 8080(在范围内)badPort := cfg.Section("server").RangeInt("bad_port", 8080, 1024, 65535)// 假设 bad_port = 100,返回默认值 8080(超出范围)// 工作线程数必须在 1-16 之间workers := cfg.Section("server").RangeInt("workers", 4, 1, 16)// 速率限制必须在 0.0-1.0 之间rateLimit := cfg.Section("server").RangeFloat64("rate_limit", 0.8, 0.0, 1.0)Validate - 自定义验证
func (s *Section) Validate(name string, fn func(string) string) stringfunc (k *Key) Validate(fn func(string) string) string说明:
使用自定义函数验证并可能修改键值。
示例:
// config.ini// [app]// url = HTTP://EXAMPLE.COM/API// 转换为小写url := cfg.Section("app").Validate("url", func(value string) string { return strings.ToLower(value)})// 返回 "http://example.com/api"// 确保以斜杠结尾apiBase := cfg.Section("app").Validate("api_base", func(value string) string { if !strings.HasSuffix(value, "/") { return value + "/" } return value})8. 变量引用和环境变量
变量引用
INI 文件支持在值中引用其他键的值,使用 %(key)s 语法。
示例:
# config.ini[paths]root = /var/wwwdata = %(root)s/datalogs = %(root)s/logscache = %(root)s/cache[database]host = localhostport = 3306dsn = %(host)s:%(port)scfg := ini.New(ini.Options{})cfg.Append("config.ini")// 自动解析引用dataPath := cfg.Section("paths").String("data")// 返回 "/var/www/data"logsPath := cfg.Section("paths").String("logs")// 返回 "/var/www/logs"dsn := cfg.Section("database").String("dsn")// 返回 "localhost:3306"跨节引用:
如果当前节中找不到引用的键,会自动查找默认节(无名节):
# config.ini# 默认节root = /var/www[app]data_dir = %(root)s/datadataDir := cfg.Section("app").String("data_dir")// 返回 "/var/www/data" (root 从默认节获取)环境变量注入
这是 go-slim.dev/ini 相对于原版 go-ini 的主要增强功能。
支持两种语法注入环境变量:
语法 1:${VAR||default} - 空值回退
当环境变量不存在或为空字符串时,使用默认值。
语法 2:${VAR??default} - 不存在回退
只有当环境变量不存在时,才使用默认值(空字符串被视为有效值)。
示例:
# config.ini[database]# 从环境变量读取,不存在时使用默认值host = ${DB_HOST||localhost}port = ${DB_PORT||3306}username = ${DB_USER||root}password = ${DB_PASSWORD}[app]# 强制回退:即使环境变量存在但为空,也使用默认值api_key = ${API_KEY||default_key}# 弱回退:只有不存在才使用默认值feature_flag = ${FEATURE_FLAG??true}// 设置环境变量os.Setenv("DB_HOST", "prod-db.example.com")os.Setenv("DB_PORT", "5432")os.Setenv("FEATURE_FLAG", "") // 空字符串cfg := ini.New(ini.Options{})cfg.Append("config.ini")// 读取配置host := cfg.Section("database").String("host")// 返回 "prod-db.example.com" (从环境变量)port := cfg.Section("database").MustInt("port", 3306)// 返回 5432 (从环境变量)username := cfg.Section("database").String("username")// 返回 "root" (环境变量未设置,使用默认值)// || vs ??apiKey := cfg.Section("app").String("api_key")// 如果 API_KEY 为空字符串,返回 "default_key"featureFlag := cfg.Section("app").String("feature_flag")// 返回 "" (环境变量存在但为空,?? 不使用默认值)默认值中的引号:
默认值可以使用单引号或双引号包裹:
[app]message = ${WELCOME_MSG||'Hello, World!'}api_url = ${API_URL||"https://api.example.com"}9. 子节(Child Sections)
子节使用点号分隔符创建层级关系。
示例:
# config.ini[server]host = 0.0.0.0[server.http]port = 8080timeout = 30s[server.https]port = 8443timeout = 60scert = /path/to/cert.pemcfg := ini.New(ini.Options{ ChildSectionDelimiter: ".", // 默认就是 "."})cfg.Append("config.ini")// 访问子节httpSection := cfg.Section("server.http")httpPort := httpSection.MustInt("port", 80)httpsSection := cfg.Section("server.https")httpsPort := httpsSection.MustInt("port", 443)// 子节会继承父节的键// 如果 server.http 中没有 host,会从 server 中查找host := httpSection.String("host")// 返回 "0.0.0.0" (从父节 server 继承)获取父节:
httpsSection := cfg.Section("server.https")parentSection, hasParent := httpsSection.Parent()if hasParent { fmt.Println(parentSection.Name()) // "server"}Options 配置选项
type Options struct { // Loose 表示解析器是否忽略不存在的文件 Loose bool // Insensitive 表示是否强制所有节名和键名小写 Insensitive bool // InsensitiveSections 表示是否强制所有节名小写 InsensitiveSections bool // InsensitiveKeys 表示是否强制所有键名小写 InsensitiveKeys bool // IgnoreContinuation 表示是否忽略续行 IgnoreContinuation bool // IgnoreInlineComment 表示是否忽略行内注释 IgnoreInlineComment bool // AllowBooleanKeys 表示是否允许布尔类型的键(值为 true) // 主要用于 my.cnf 类型的配置文件 AllowBooleanKeys bool // AllowPythonMultilineValues 表示是否允许 Python 风格的多行值 AllowPythonMultilineValues bool // SpaceBeforeInlineComment 表示注释符号前是否需要空格 SpaceBeforeInlineComment bool // UnescapeValueDoubleQuotes 表示是否反转义双引号中的引号 UnescapeValueDoubleQuotes bool // UnescapeValueCommentSymbols 表示是否反转义注释符号 UnescapeValueCommentSymbols bool // KeyValueDelimiters 键值分隔符,默认 "=:" KeyValueDelimiters string // ChildSectionDelimiter 子节分隔符,默认 "." ChildSectionDelimiter string // PreserveSurroundedQuote 表示是否保留值周围的引号 PreserveSurroundedQuote bool // ReaderBufferSize 读取器缓冲区大小(字节) ReaderBufferSize int // AllowNonUniqueSections 表示是否允许同名节 AllowNonUniqueSections bool // AllowDuplicateShadowValues 表示是否允许重复的影子值 AllowDuplicateShadowValues bool // Mutex 并发锁(可自定义) Mutex Mutex // Transformer 自定义值转换器 Transformer ValueTransformer}常用配置示例
// 宽松模式:忽略不存在的文件cfg := ini.New(ini.Options{ Loose: true,})// 不区分大小写cfg := ini.New(ini.Options{ Insensitive: true,})// 允许布尔键(MySQL 配置风格)cfg := ini.New(ini.Options{ AllowBooleanKeys: true,})// 忽略行内注释cfg := ini.New(ini.Options{ IgnoreInlineComment: true,})// 自定义分隔符cfg := ini.New(ini.Options{ KeyValueDelimiters: "=", // 只允许 = 作为分隔符 ChildSectionDelimiter: "::", // 使用 :: 作为子节分隔符})使用场景
1. 应用配置管理
// config.ini// [app]// name = MyApp// version = 1.0.0// debug = ${DEBUG||false}// port = ${PORT||8080}//// [database]// host = ${DB_HOST||localhost}// port = ${DB_PORT||3306}// name = ${DB_NAME||mydb}// user = ${DB_USER||root}// password = ${DB_PASSWORD}type Config struct { App AppConfig Database DatabaseConfig}type AppConfig struct { Name string Version string Debug bool Port int}type DatabaseConfig struct { Host string Port int Name string User string Password string}func LoadConfig() (*Config, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } config := &Config{ App: AppConfig{ Name: cfg.Section("app").String("name"), Version: cfg.Section("app").String("version"), Debug: cfg.Section("app").MustBool("debug", false), Port: cfg.Section("app").MustInt("port", 8080), }, Database: DatabaseConfig{ Host: cfg.Section("database").String("host"), Port: cfg.Section("database").MustInt("port", 3306), Name: cfg.Section("database").String("name"), User: cfg.Section("database").String("user"), Password: cfg.Section("database").String("password"), }, } return config, nil}2. 多环境配置
// 加载基础配置 + 环境特定配置cfg := ini.New(ini.Options{Loose: true})// 批量加载err := cfg.Batch(func(m *ini.Manager) error { // 基础配置 m.Append("config.ini") // 环境特定配置 env := os.Getenv("APP_ENV") if env != "" { m.Append(fmt.Sprintf("config.%s.ini", env)) } // 本地覆盖配置 m.Append("config.local.ini") return nil})3. 服务配置与环境变量结合
// config.ini// [redis]// host = ${REDIS_HOST||localhost}// port = ${REDIS_PORT||6379}// password = ${REDIS_PASSWORD}// database = ${REDIS_DB||0}// max_retries = 3// pool_size = ${REDIS_POOL_SIZE||10}func NewRedisClient() (*redis.Client, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } section := cfg.Section("redis") client := redis.NewClient(&redis.Options{ Addr: fmt.Sprintf("%s:%d", section.String("host"), section.MustInt("port", 6379)), Password: section.String("password"), DB: section.MustInt("database", 0), MaxRetries: section.MustInt("max_retries", 3), PoolSize: section.MustInt("pool_size", 10), }) return client, nil}4. 动态配置重载
type ConfigManager struct { cfg *ini.Manager watchers []func(*ini.Manager) mu sync.RWMutex}func NewConfigManager(filename string) (*ConfigManager, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append(filename); err != nil { return nil, err } return &ConfigManager{ cfg: cfg, watchers: make([]func(*ini.Manager), 0), }, nil}func (cm *ConfigManager) Reload() error { cm.mu.Lock() defer cm.mu.Unlock() if err := cm.cfg.Reload(); err != nil { return err } // 通知所有观察者 for _, watcher := range cm.watchers { watcher(cm.cfg) } return nil}func (cm *ConfigManager) Watch(fn func(*ini.Manager)) { cm.mu.Lock() defer cm.mu.Unlock() cm.watchers = append(cm.watchers, fn)}func (cm *ConfigManager) GetInt(section, key string, defaultVal int) int { cm.mu.RLock() defer cm.mu.RUnlock() return cm.cfg.Section(section).MustInt(key, defaultVal)}5. 复杂配置验证
// config.ini// [server]// host = 0.0.0.0// port = 8080// workers = 4// timeout = 30s//// [limits]// max_connections = 1000// rate_limit = 100// request_size = 10485760func ValidateConfig(cfg *ini.Manager) error { // 验证端口范围 port := cfg.Section("server").MustInt("port", 8080) if port < 1024 || port > 65535 { return fmt.Errorf("invalid port: %d (must be 1024-65535)", port) } // 验证工作线程数 workers := cfg.Section("server").MustInt("workers", 4) if workers < 1 || workers > 256 { return fmt.Errorf("invalid workers: %d (must be 1-256)", workers) } // 验证超时时间 timeout, err := cfg.Section("server").Duration("timeout") if err != nil || timeout < time.Second || timeout > 5*time.Minute { return fmt.Errorf("invalid timeout: must be 1s-5m") } // 验证限制配置 maxConns := cfg.Section("limits").MustInt("max_connections", 100) if maxConns < 1 { return fmt.Errorf("max_connections must be positive") } return nil}API 参考
Manager 方法
| 方法 | 说明 |
|---|---|
New(opts Options) *Manager | 创建新管理器 |
Append(source any, others ...any) error | 追加数据源 |
Batch(fn func(m *Manager) error) error | 批量操作 |
Reload() error | 重新加载 |
Section(name string) *Section | 获取节 |
GetSection(name string) (*Section, error) | 获取节(带错误) |
HasSection(name string) bool | 检查节是否存在 |
NewSection(name string) *Section | 创建新节 |
Section 方法
| 方法 | 说明 |
|---|---|
Name() string | 获取节名 |
Parent() (*Section, bool) | 获取父节 |
Key(name string) *Key | 获取键 |
GetKey(name string) (*Key, error) | 获取键(带错误) |
HasKey(name string) bool | 检查键是否存在 |
NewKey(name, value string) *Key | 创建新键 |
Keys() []*Key | 获取所有键 |
值读取方法(Section/Key)
| 方法 | 说明 |
|---|---|
String(name string) string | 字符串值 |
MustString(name, defaultVal string) string | 带默认值的字符串 |
Int/Int64/Uint/Uint64(name string) (T, error) | 整数值 |
MustInt/MustInt64/...(name string, defaultVal T) T | 带默认值的整数 |
Float64(name string) (float64, error) | 浮点数值 |
MustFloat64(name, defaultVal float64) float64 | 带默认值的浮点数 |
Bool(name string) (bool, error) | 布尔值 |
MustBool(name string, defaultVal bool) bool | 带默认值的布尔值 |
Duration(name string) (time.Duration, error) | 时间间隔 |
Time/TimeFormat(name, format string) (time.Time, error) | 时间值 |
列表方法
| 方法 | 说明 |
|---|---|
Strings(name, delim string) []string | 字符串列表 |
Ints/Int64s/Uints/Uint64s(name, delim string) []T | 整数列表(无效值为0) |
ValidInts/ValidInt64s/...(name, delim string) []T | 有效整数列表(跳过无效值) |
StrictInts/StrictInt64s/...(name, delim string) ([]T, error) | 严格整数列表(遇错返回) |
Float64s/Bools/Times(name, delim string) []T | 其他类型列表 |
验证方法
| 方法 | 说明 |
|---|---|
In(name, defaultVal string, candidates []string) string | 候选值检查 |
InInt/InFloat64/...(name, defaultVal, candidates) T | 类型化候选值检查 |
RangeInt/RangeFloat64/...(name, defaultVal, min, max) T | 范围检查 |
Validate(name, fn func(string) string) string | 自定义验证 |
注意事项
1. 环境变量语法差异
理解 || 和 ?? 的区别:
# || 空值回退:环境变量不存在或为空时使用默认值value1 = ${VAR||default}# ?? 不存在回退:只有环境变量不存在时才使用默认值value2 = ${VAR??default}// 设置空字符串os.Setenv("VAR", "")cfg.Section("").String("value1") // "default" (|| 检查空值)cfg.Section("").String("value2") // "" (?? 不检查空值)2. 默认节
没有节名的键值对属于默认节(空字符串名称):
# 默认节root = /var/wwwdebug = true[app]name = MyApp// 访问默认节root := cfg.Section("").String("root")debug := cfg.Section("").MustBool("debug", false)3. 键名大小写
根据 Options 设置,键名可能被转换为小写:
// 区分大小写(默认)cfg := ini.New(ini.Options{})cfg.Section("app").String("UserName") // 必须完全匹配// 不区分大小写cfg := ini.New(ini.Options{Insensitive: true})cfg.Section("app").String("username") // 可以匹配 UserName、USERNAME 等cfg.Section("app").String("USERNAME") // 同样有效4. 多数据源合并
后加载的数据源会覆盖先加载的同名键:
cfg := ini.New(ini.Options{})cfg.Append("base.ini") // port = 8080cfg.Append("override.ini") // port = 9000port := cfg.Section("app").MustInt("port")// 返回 9000 (override.ini 覆盖了 base.ini)5. 类型转换失败
使用 Must* 系列函数时,转换失败会使用默认值而不是报错:
// config.ini: port = invalidport, err := cfg.Section("app").Int("port")// err != nil, port = 0port := cfg.Section("app").MustInt("port", 8080)// port = 8080 (转换失败,使用默认值)6. 变量引用循环
避免循环引用,否则会达到最大深度(99层):
# 错误示例:循环引用[bad]a = %(b)sb = %(a)s7. 线程安全
Manager 内部使用互斥锁保护并发访问。可以自定义 Mutex:
cfg := ini.New(ini.Options{ Mutex: &sync.RWMutex{}, // 使用读写锁})8. 子节继承
子节会从父节继承键,但不会递归继承:
[a]key1 = value1[a.b]key2 = value2[a.b.c]key3 = value3// a.b.c 可以访问 a.b 的键,但不能直接访问 a 的键cfg.Section("a.b.c").String("key2") // OK (从 a.b 继承)cfg.Section("a.b.c").String("key1") // 找不到 (不递归继承)最佳实践
1. 使用环境变量覆盖敏感配置
# config.ini - 提交到版本控制[database]host = ${DB_HOST||localhost}port = ${DB_PORT||3306}name = ${DB_NAME||mydb}user = ${DB_USER||root}# 密码从环境变量读取,无默认值password = ${DB_PASSWORD}[api]# API 密钥必须从环境变量提供key = ${API_KEY}secret = ${API_SECRET}2. 分层配置文件
// 基础 -> 环境 -> 本地cfg := ini.New(ini.Options{Loose: true})cfg.Batch(func(m *ini.Manager) error { m.Append("config.ini") // 基础配置 m.Append("config.production.ini") // 生产环境 m.Append("config.local.ini") // 本地覆盖(不提交) return nil})3. 配置验证
func LoadAndValidateConfig() (*ini.Manager, error) { cfg := ini.New(ini.Options{}) if err := cfg.Append("config.ini"); err != nil { return nil, err } // 验证必需配置 required := map[string][]string{ "database": {"host", "port", "name", "user", "password"}, "redis": {"host", "port"}, } for section, keys := range required { for _, key := range keys { if !cfg.Section(section).HasKey(key) { return nil, fmt.Errorf("missing required config: %s.%s", section, key) } } } return cfg, nil}4. 使用结构化配置
type Config struct { Server ServerConfig Database DatabaseConfig Redis RedisConfig}func (c *Config) Load(cfg *ini.Manager) error { c.Server.Load(cfg.Section("server")) c.Database.Load(cfg.Section("database")) c.Redis.Load(cfg.Section("redis")) return c.Validate()}func (c *Config) Validate() error { // 集中验证逻辑 return nil}5. 提供配置模板
# config.example.ini - 配置模板# 复制为 config.ini 并填写实际值[app]name = MyAppdebug = ${DEBUG||false}port = ${PORT||8080}[database]host = ${DB_HOST||localhost}port = ${DB_PORT||3306}name = ${DB_NAME||mydb}user = ${DB_USER||root}password = ${DB_PASSWORD} # 必填:数据库密码