2025-06-05 21:21:28 +07:00

132 lines
3.6 KiB
Go

package config
import (
"fmt"
"os"
"strings"
"github.com/go-playground/validator/v10"
"github.com/joho/godotenv"
"github.com/spf13/viper"
)
const (
envFile = "./.env"
)
// ConfigLoader định nghĩa interface để load cấu hình
type ConfigLoader interface {
Load() (*Config, error)
}
// ViperConfigLoader triển khai ConfigLoader với Viper
type ViperConfigLoader struct {
configPaths []string
configName string
configType string
envPrefix string
}
// NewConfigLoader tạo ConfigLoader mới với các giá trị mặc định
func NewConfigLoader() ConfigLoader {
return &ViperConfigLoader{
configPaths: []string{"./configs", ".", "./templates"},
configName: "config",
configType: "yaml",
envPrefix: "APP",
}
}
func (l *ViperConfigLoader) Load() (*Config, error) {
v := viper.New()
v.SetConfigName(l.configName)
v.SetConfigType(l.configType)
// Thêm các đường dẫn tìm kiếm file cấu hình
for _, path := range l.configPaths {
v.AddConfigPath(path)
}
// 1. Đọc file cấu hình chính (bắt buộc)
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file: %w (searched in: %v)", err, l.configPaths)
}
// 2. Đọc từ file .env nếu tồn tại (tùy chọn)
if err := l.loadEnvFile(v); err != nil {
return nil, fmt.Errorf("error loading .env file: %w", err)
}
// 3. Cấu hình đọc biến môi trường (có thể ghi đè các giá trị từ file)
v.AutomaticEnv()
v.SetEnvPrefix(l.envPrefix)
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
// 4. Đặt các giá trị mặc định tối thiểu
setDefaultValues(v)
// Bind cấu hình vào struct
var config Config
if err := v.Unmarshal(&config); err != nil {
return nil, fmt.Errorf("unable to decode config into struct: %w", err)
}
// Validate cấu hình
if err := validateConfig(&config); err != nil {
return nil, fmt.Errorf("config validation error: %w", err)
}
return &config, nil
}
// loadEnvFile đọc và xử lý file .env
func (l *ViperConfigLoader) loadEnvFile(v *viper.Viper) error {
if _, err := os.Stat(envFile); os.IsNotExist(err) {
return nil // Không có file .env cũng không phải lỗi
}
envMap, err := godotenv.Read(envFile)
if err != nil {
return fmt.Errorf("error parsing .env file: %w", err)
}
for key, val := range envMap {
if key == "" {
continue
}
// Chuyển đổi key từ DB_PASSWORD thành database.password
if parts := strings.SplitN(key, "_", 2); len(parts) == 2 {
prefix := strings.ToLower(parts[0])
suffix := strings.ReplaceAll(parts[1], "_", ".")
v.Set(fmt.Sprintf("%s.%s", prefix, suffix), val)
}
// Lưu giá trị gốc cho tương thích ngược
v.Set(key, val)
if err := os.Setenv(key, val); err != nil {
return fmt.Errorf("failed to set environment variable %s: %w", key, err)
}
}
return nil
}
// setDefaultValues thiết lập các giá trị mặc định tối thiểu cần thiết
// Lưu ý: Hầu hết các giá trị mặc định nên được định nghĩa trong file config.yaml
func setDefaultValues(v *viper.Viper) {
// Chỉ đặt các giá trị mặc định thực sự cần thiết ở đây
// Các giá trị khác sẽ được lấy từ file config.yaml bắt buộc
v.SetDefault("app.environment", "development")
v.SetDefault("log_level", "info")
}
// validateConfig xác thực cấu hình sử dụng thẻ validate
func validateConfig(config *Config) error {
validate := validator.New()
if err := validate.Struct(config); err != nil {
return fmt.Errorf("config validation failed: %w", err)
}
return nil
}