ini - INI 配置文件解析库
ini 是一个功能强大的 INI 配置文件解析库,基于 go-ini 进行改进,主要增强了环境变量注入功能。支持多种数据源、类型转换、变量引用、环境变量替换等高级特性。
安装
go get -u go-slim.dev/ini
快速开始
基础用法
package main
import (
"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 = MyApp
debug = true
port = 8080
# 数据库配置
[database]
host = localhost
port = 3306
username = root
password = secret
max_connections = 100
# Redis 配置
[redis]
host = localhost
port = 6379
database = 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 = MyApp
port = 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) string
func (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) string
func (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) int
func (s *Section) MustInt64(name string, defaultVal ...int64) int64
func (s *Section) MustUint(name string, defaultVal ...uint) uint
func (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 = 5m
timeout, 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.Time
func (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) []string
func (k *Key) Strings(delim string) []string
说明:
使用指定的分隔符分割值,返回字符串列表。
示例:
// config.ini
// [app]
// allowed_origins = http://localhost:3000,https://example.com,https://api.example.com
// tags = go, web, api
origins := 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) []int
func (s *Section) Int64s(name string, delim string) []int64
func (s *Section) Uints(name string, delim string) []uint
func (s *Section) Uint64s(name string, delim string) []uint64
说明:
分割值并转换为整数列表。无效的值会被转换为零值。
示例:
// config.ini
// [app]
// ports = 8080,8081,8082
// retry_delays = 1,2,5,10
ports := 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) []int
func (s *Section) ValidInt64s(name string, delim string) []int64
说明:
分割值并转换为整数列表。无效的值会被跳过(不包含在结果中)。
示例:
// config.ini
// [app]
// ports = 8080,invalid,8081,8082
ports := 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,8082
ports, err := cfg.Section("app").StrictInts("ports", ",")
// []int{8080, 8081, 8082}, nil
badPorts, err := cfg.Section("app").StrictInts("bad_ports", ",")
// nil, error (遇到 "invalid" 时返回错误)
Float64s/Bools/Times - 其他类型列表
// 浮点数列表
func (s *Section) Float64s(name string, delim string) []float64
func (s *Section) ValidFloat64s(name string, delim string) []float64
func (s *Section) StrictFloat64s(name string, delim string) ([]float64, error)
// 布尔值列表
func (s *Section) Bools(name string, delim string) []bool
func (s *Section) ValidBools(name string, delim string) []bool
func (s *Section) StrictBools(name string, delim string) ([]bool, error)
// 时间列表
func (s *Section) Times(name string, delim string) []time.Time
func (s *Section) TimesFormat(name string, format, delim string) []time.Time
func (s *Section) ValidTimes(name string, delim string) []time.Time
func (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:00Z
rates := 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) string
func (s *Section) InInt(name string, defaultVal int, candidates []int) int
func (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})
// 返回 8080
Range - 范围检查
func (s *Section) RangeInt(name string, defaultVal, min, max int) int
func (s *Section) RangeInt64(name string, defaultVal, min, max int64) int64
func (s *Section) RangeFloat64(name string, defaultVal, min, max float64) float64
func (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) string
func (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/www
data = %(root)s/data
logs = %(root)s/logs
cache = %(root)s/cache
[database]
host = localhost
port = 3306
dsn = %(host)s:%(port)s
cfg := 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/data
dataDir := 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 = 8080
timeout = 30s
[server.https]
port = 8443
timeout = 60s
cert = /path/to/cert.pem
cfg := 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 = 10485760
func 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/www
debug = 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 = 8080
cfg.Append("override.ini") // port = 9000
port := cfg.Section("app").MustInt("port")
// 返回 9000 (override.ini 覆盖了 base.ini)
5. 类型转换失败
使用 Must* 系列函数时,转换失败会使用默认值而不是报错:
// config.ini: port = invalid
port, err := cfg.Section("app").Int("port")
// err != nil, port = 0
port := cfg.Section("app").MustInt("port", 8080)
// port = 8080 (转换失败,使用默认值)
6. 变量引用循环
避免循环引用,否则会达到最大深度(99层):
# 错误示例:循环引用
[bad]
a = %(b)s
b = %(a)s
7. 线程安全
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 = MyApp
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} # 必填:数据库密码