101 lines
2.2 KiB
Go
101 lines
2.2 KiB
Go
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()
|
||
}
|