// Package config 读取采购服务的启动配置。凭据只允许来自显式环境变量,避免把秘密写入代码或仓库。 package config import ( "errors" "fmt" "os" "strings" "golang.org/x/crypto/bcrypt" ) const ( adminUsernameEnv = "CMBUYER_ADMIN_USERNAME" adminPasswordBcryptEnv = "CMBUYER_ADMIN_PASSWORD_BCRYPT" sessionSecretEnv = "CMBUYER_SESSION_SECRET" cookieSecureEnv = "CMBUYER_COOKIE_SECURE" databaseSourceEnv = "CMBUYER_DATABASE_SOURCE" minimumSecretLength = 32 ) // Config 是启动采购服务所需的最小安全配置。 type Config struct { AdminUsername string AdminPasswordBcrypt string SessionSecret []byte CookieSecure bool DatabaseSource string } // LoadFromEnv 从进程环境读取配置。错误只指出缺失或非法的变量名,绝不回显秘密。 func LoadFromEnv() (Config, error) { return Load(os.LookupEnv) } // Load 使用 lookup 读取配置,以便在不污染进程环境的情况下测试启动边界。 func Load(lookup func(string) (string, bool)) (Config, error) { username, err := required(lookup, adminUsernameEnv) if err != nil { return Config{}, err } passwordHash, err := required(lookup, adminPasswordBcryptEnv) if err != nil { return Config{}, err } if _, err := bcrypt.Cost([]byte(passwordHash)); err != nil { return Config{}, fmt.Errorf("%s is not a valid bcrypt hash", adminPasswordBcryptEnv) } secret, err := required(lookup, sessionSecretEnv) if err != nil { return Config{}, err } if len([]byte(secret)) < minimumSecretLength { return Config{}, fmt.Errorf("%s must be at least %d bytes", sessionSecretEnv, minimumSecretLength) } cookieSecure := false if value, present := lookup(cookieSecureEnv); present { switch value { case "true": cookieSecure = true case "false": cookieSecure = false default: return Config{}, fmt.Errorf("%s must be exactly true or false", cookieSecureEnv) } } databaseSource, err := required(lookup, databaseSourceEnv) if err != nil { return Config{}, err } return Config{ AdminUsername: username, AdminPasswordBcrypt: passwordHash, SessionSecret: []byte(secret), CookieSecure: cookieSecure, DatabaseSource: databaseSource, }, nil } func required(lookup func(string) (string, bool), name string) (string, error) { value, present := lookup(name) if !present || strings.TrimSpace(value) == "" { return "", errors.New(name + " must be set") } return value, nil }