132 lines
3.6 KiB
Go
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
|
|
}
|