You've already forked agentic-coding-workflow
feat(config): viper-based config loader with env + file + master key validation
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Package config 提供基于 viper 的应用配置加载,按 默认值 → 配置文件 → 环境变量 的优先级合并。
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config 是应用全局配置的根结构。
|
||||
type Config struct {
|
||||
Env string `mapstructure:"env"`
|
||||
DataDir string `mapstructure:"data_dir"`
|
||||
MasterKey []byte // 解码后的 32 字节
|
||||
HTTP HTTPConfig `mapstructure:"http"`
|
||||
DB DBConfig `mapstructure:"db"`
|
||||
Bootstrap BootstrapConfig `mapstructure:"bootstrap"`
|
||||
}
|
||||
|
||||
// HTTPConfig 描述 HTTP 服务监听参数。
|
||||
type HTTPConfig struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
}
|
||||
|
||||
// DBConfig 描述数据库连接参数。
|
||||
type DBConfig struct {
|
||||
DSN string `mapstructure:"dsn"`
|
||||
}
|
||||
|
||||
// BootstrapConfig 描述首次启动注入的管理员账号。
|
||||
type BootstrapConfig struct {
|
||||
AdminEmail string `mapstructure:"admin_email"`
|
||||
AdminPassword string `mapstructure:"admin_password"`
|
||||
}
|
||||
|
||||
// Load 从(按优先级递增)默认值 → 配置文件(如有)→ 环境变量 加载配置。
|
||||
// configFile 可为空字符串,仅用环境变量。
|
||||
func Load(configFile string) (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
// 默认值
|
||||
v.SetDefault("env", "development")
|
||||
v.SetDefault("data_dir", "./data")
|
||||
v.SetDefault("http.addr", ":8080")
|
||||
|
||||
if configFile != "" {
|
||||
v.SetConfigFile(configFile)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("read config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
v.SetEnvPrefix("APP")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// 显式绑定无默认值的嵌套键,以便 Unmarshal 能从环境变量取值。
|
||||
// (AutomaticEnv 仅对 v.Get* 直接查询生效;Unmarshal 只看 viper 已知的键集合。)
|
||||
for _, key := range []string{
|
||||
"db.dsn",
|
||||
"bootstrap.admin_email",
|
||||
"bootstrap.admin_password",
|
||||
"master_key",
|
||||
} {
|
||||
if err := v.BindEnv(key); err != nil {
|
||||
return nil, fmt.Errorf("bind env %s: %w", key, err)
|
||||
}
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
// MasterKey 是 hex 字符串,需要单独解码
|
||||
rawKey := v.GetString("master_key")
|
||||
if rawKey == "" {
|
||||
return nil, fmt.Errorf("APP_MASTER_KEY required")
|
||||
}
|
||||
key, err := hex.DecodeString(rawKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("APP_MASTER_KEY invalid hex: %w", err)
|
||||
}
|
||||
if len(key) != 32 {
|
||||
return nil, fmt.Errorf("APP_MASTER_KEY must be 32 bytes (got %d)", len(key))
|
||||
}
|
||||
cfg.MasterKey = key
|
||||
|
||||
if cfg.DB.DSN == "" {
|
||||
return nil, fmt.Errorf("APP_DB_DSN required")
|
||||
}
|
||||
return &cfg, nil
|
||||
}
|
||||
Reference in New Issue
Block a user