Files
lijiaoqiao/supply-api/internal/config/config.go
Your Name aeeec34326 fix(supply-api): 修复P2-05数据库凭证日志泄露风险
1. 在DatabaseConfig中添加SafeDSN()方法,返回脱敏的连接信息
2. 在NewDB中使用SafeDSN()记录日志
3. 添加sanitizeErrorPassword()函数清理错误信息中的密码

修复的问题:P2-05 数据库凭证日志泄露风险
2026-04-03 10:06:14 +08:00

250 lines
7.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/spf13/viper"
)
// Config 应用配置
type Config struct {
Server ServerConfig
Database DatabaseConfig
Redis RedisConfig
Token TokenConfig
Audit AuditConfig
}
// ServerConfig HTTP服务配置
type ServerConfig struct {
Addr string
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
ShutdownTimeout time.Duration
}
// DatabaseConfig PostgreSQL配置
type DatabaseConfig struct {
Host string
Port int
User string
Password string
Database string
MaxOpenConns int
MaxIdleConns int
ConnMaxLifetime time.Duration
ConnMaxIdleTime time.Duration
}
// RedisConfig Redis配置
type RedisConfig struct {
Host string
Port int
Password string
DB int
PoolSize int
}
// TokenConfig Token运行时配置
type TokenConfig struct {
SecretKey string
Issuer string
AccessTokenTTL time.Duration
RefreshTokenTTL time.Duration
RevocationCacheTTL time.Duration
}
// AuditConfig 审计配置
type AuditConfig struct {
BufferSize int
FlushInterval time.Duration
ExportTimeout time.Duration
}
// DSN 返回数据库连接字符串(包含明文密码,仅限内部使用)
func (d *DatabaseConfig) DSN() string {
return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable",
d.User, d.Password, d.Host, d.Port, d.Database)
}
// SafeDSN 返回脱敏的数据库连接字符串(密码被替换为***),用于日志记录
// P2-05: 避免在日志中泄露数据库密码
func (d *DatabaseConfig) SafeDSN() string {
return fmt.Sprintf("postgres://%s:***@%s:%d/%s?sslmode=disable",
d.User, d.Host, d.Port, d.Database)
}
// Addr 返回Redis地址
func (r *RedisConfig) Addr() string {
return fmt.Sprintf("%s:%d", r.Host, r.Port)
}
// Load 加载配置
func Load(env string) (*Config, error) {
v := viper.New()
// 设置环境变量前缀
v.SetEnvPrefix("SUPPLY_API")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 默认配置
setDefaults(v)
// 加载配置文件
configFile := fmt.Sprintf("config.%s.yaml", env)
v.SetConfigName(configFile)
v.SetConfigType("yaml")
v.AddConfigPath(".")
v.AddConfigPath("./config")
// 允许环境变量覆盖
v.AutomaticEnv()
// 读取配置文件
if err := v.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return nil, fmt.Errorf("failed to read config: %w", err)
}
// 配置文件不存在时,使用环境变量
}
// 绑定环境变量
bindEnvVars(v)
var cfg Config
// Server配置
cfg.Server.Addr = v.GetString("server.addr")
cfg.Server.ReadTimeout = v.GetDuration("server.read_timeout")
cfg.Server.WriteTimeout = v.GetDuration("server.write_timeout")
cfg.Server.IdleTimeout = v.GetDuration("server.idle_timeout")
cfg.Server.ShutdownTimeout = v.GetDuration("server.shutdown_timeout")
// Database配置
cfg.Database.Host = v.GetString("database.host")
cfg.Database.Port = v.GetInt("database.port")
cfg.Database.User = v.GetString("database.user")
cfg.Database.Password = v.GetString("database.password")
cfg.Database.Database = v.GetString("database.database")
cfg.Database.MaxOpenConns = v.GetInt("database.max_open_conns")
cfg.Database.MaxIdleConns = v.GetInt("database.max_idle_conns")
cfg.Database.ConnMaxLifetime = v.GetDuration("database.conn_max_lifetime")
cfg.Database.ConnMaxIdleTime = v.GetDuration("database.conn_max_idle_time")
// Redis配置
cfg.Redis.Host = v.GetString("redis.host")
cfg.Redis.Port = v.GetInt("redis.port")
cfg.Redis.Password = v.GetString("redis.password")
cfg.Redis.DB = v.GetInt("redis.db")
cfg.Redis.PoolSize = v.GetInt("redis.pool_size")
// Token配置
cfg.Token.SecretKey = v.GetString("token.secret_key")
cfg.Token.Issuer = v.GetString("token.issuer")
cfg.Token.AccessTokenTTL = v.GetDuration("token.access_token_ttl")
cfg.Token.RefreshTokenTTL = v.GetDuration("token.refresh_token_ttl")
cfg.Token.RevocationCacheTTL = v.GetDuration("token.revocation_cache_ttl")
// Audit配置
cfg.Audit.BufferSize = v.GetInt("audit.buffer_size")
cfg.Audit.FlushInterval = v.GetDuration("audit.flush_interval")
cfg.Audit.ExportTimeout = v.GetDuration("audit.export_timeout")
return &cfg, nil
}
// setDefaults 设置默认值
func setDefaults(v *viper.Viper) {
// Server defaults
v.SetDefault("server.addr", ":18082")
v.SetDefault("server.read_timeout", 10*time.Second)
v.SetDefault("server.write_timeout", 15*time.Second)
v.SetDefault("server.idle_timeout", 30*time.Second)
v.SetDefault("server.shutdown_timeout", 5*time.Second)
// Database defaults
v.SetDefault("database.host", "localhost")
v.SetDefault("database.port", 5432)
v.SetDefault("database.user", "postgres")
v.SetDefault("database.password", "")
v.SetDefault("database.database", "supply_db")
v.SetDefault("database.max_open_conns", 25)
v.SetDefault("database.max_idle_conns", 5)
v.SetDefault("database.conn_max_lifetime", 1*time.Hour)
v.SetDefault("database.conn_max_idle_time", 10*time.Minute)
// Redis defaults
v.SetDefault("redis.host", "localhost")
v.SetDefault("redis.port", 6379)
v.SetDefault("redis.password", "")
v.SetDefault("redis.db", 0)
v.SetDefault("redis.pool_size", 10)
// Token defaults
v.SetDefault("token.issuer", "lijiaoqiao/supply-api")
v.SetDefault("token.access_token_ttl", 1*time.Hour)
v.SetDefault("token.refresh_token_ttl", 7*24*time.Hour)
v.SetDefault("token.revocation_cache_ttl", 30*time.Second)
// Audit defaults
v.SetDefault("audit.buffer_size", 1000)
v.SetDefault("audit.flush_interval", 5*time.Second)
v.SetDefault("audit.export_timeout", 30*time.Second)
}
// bindEnvVars 绑定环境变量
func bindEnvVars(v *viper.Viper) {
_ = v.BindEnv("server.addr", "SUPPLY_API_ADDR")
_ = v.BindEnv("server.read_timeout", "SUPPLY_API_READ_TIMEOUT")
_ = v.BindEnv("server.write_timeout", "SUPPLY_API_WRITE_TIMEOUT")
_ = v.BindEnv("database.host", "SUPPLY_DB_HOST")
_ = v.BindEnv("database.port", "SUPPLY_DB_PORT")
_ = v.BindEnv("database.user", "SUPPLY_DB_USER")
_ = v.BindEnv("database.password", "SUPPLY_DB_PASSWORD")
_ = v.BindEnv("database.database", "SUPPLY_DB_NAME")
_ = v.BindEnv("database.max_open_conns", "SUPPLY_DB_MAX_OPEN_CONNS")
_ = v.BindEnv("database.max_idle_conns", "SUPPLY_DB_MAX_IDLE_CONNS")
_ = v.BindEnv("redis.host", "SUPPLY_REDIS_HOST")
_ = v.BindEnv("redis.port", "SUPPLY_REDIS_PORT")
_ = v.BindEnv("redis.password", "SUPPLY_REDIS_PASSWORD")
_ = v.BindEnv("redis.db", "SUPPLY_REDIS_DB")
_ = v.BindEnv("token.secret_key", "SUPPLY_TOKEN_SECRET_KEY")
}
// MustLoad 加载配置失败时panic
func MustLoad(env string) *Config {
cfg, err := Load(env)
if err != nil {
panic("failed to load config: " + err.Error())
}
return cfg
}
// GetEnvInt 获取环境变量int值
func GetEnvInt(key string, defaultVal int) int {
if v := os.Getenv(key); v != "" {
if i, err := strconv.Atoi(v); err == nil {
return i
}
}
return defaultVal
}
// GetEnvDuration 获取环境变量duration值
func GetEnvDuration(key string, defaultVal time.Duration) time.Duration {
if v := os.Getenv(key); v != "" {
if d, err := time.ParseDuration(v); err == nil {
return d
}
}
return defaultVal
}