package configx import ( "bufio" "errors" "os" "strings" "gopkg.in/yaml.v3" ) // LoadYAML 把指定 YAML 文件反序列化到已有配置对象里。 // 调用方通常会先准备一个带默认值的 struct,再让 YAML 局部覆盖它。 func LoadYAML(path string, out any) error { body, err := os.ReadFile(path) if err != nil { return err } return yaml.Unmarshal(body, out) } // LoadYAMLWithEnv 只展开 ${NAME} 形式的环境变量;普通 $ 字符保留,避免误伤密钥正文。 func LoadYAMLWithEnv(path string, out any) error { body, err := os.ReadFile(path) if err != nil { return err } return yaml.Unmarshal([]byte(expandBraceEnv(string(body))), out) } // LoadDotEnvFile 读取本地运行时 .env 文件;真实环境变量优先,文件里的值只补本地缺省。 func LoadDotEnvFile(path string) error { file, err := os.Open(path) if err != nil { if errors.Is(err, os.ErrNotExist) { return nil } return err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { key, value, ok := parseDotEnvLine(scanner.Text()) if !ok { continue } if _, exists := os.LookupEnv(key); exists { continue } if err := os.Setenv(key, value); err != nil { return err } } return scanner.Err() } func parseDotEnvLine(line string) (string, string, bool) { line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, "#") { return "", "", false } line = strings.TrimPrefix(line, "export ") key, value, ok := strings.Cut(line, "=") if !ok { return "", "", false } key = strings.TrimSpace(key) if key == "" { return "", "", false } value = strings.TrimSpace(value) if len(value) >= 2 { quote := value[0] if (quote == '"' || quote == '\'') && value[len(value)-1] == quote { value = value[1 : len(value)-1] } } return key, value, true } func expandBraceEnv(input string) string { var builder strings.Builder for i := 0; i < len(input); { if input[i] == '$' && i+1 < len(input) && input[i+1] == '{' { end := strings.IndexByte(input[i+2:], '}') if end >= 0 { key := input[i+2 : i+2+end] builder.WriteString(os.Getenv(key)) i += end + 3 continue } } builder.WriteByte(input[i]) i++ } return builder.String() }