Skip to content

集成服务配置管理

概述

配置管理规范定义了集成服务的配置结构、校验方法和注入方式。遵循失快原则(Fail Fast),确保配置错误在应用启动时被发现。

核心原则

原则 1:配置结构化

使用结构体定义配置,而非零散的字符串变量。

go
// ✅ 正确:结构化配置
type StorageConfig struct {
    Type      string        `toml:"type"`
    Endpoint  string        `toml:"endpoint"`
    AccessKey string        `toml:"access_key"`
    SecretKey string        `toml:"secret_key"`
    Bucket    string        `toml:"bucket"`
    Timeout   time.Duration `toml:"timeout"`
}

// ❌ 错误:零散变量
var (
    storageType      = "s3"
    storageEndpoint  = "https://s3.amazonaws.com"
    storageAccessKey = "..."
)

原则 2:配置校验

在 Client 初始化时校验配置,而非运行时失败。

go
// Validate 校验配置完整性
func (c *StorageConfig) Validate() error {
    if c.Endpoint == "" {
        return errors.New("storage endpoint is required")
    }
    if c.AccessKey == "" {
        return errors.New("storage access_key is required")
    }
    if c.SecretKey == "" {
        return errors.New("storage secret_key is required")
    }
    if c.Timeout == 0 {
        c.Timeout = 30 * time.Second  // 设置默认值
    }
    return nil
}

原则 3:失快原则

配置错误应阻止应用启动,而非在运行时才发现。

go
func (a *App) Init(ctx context.Context, cfg *Config) error {
    // ✅ 初始化失败时应用无法启动
    storageClient, err := storage.NewClient(&cfg.Storage)
    if err != nil {
        return fmt.Errorf("failed to init storage client: %w", err)
    }
    a.storageClient = storageClient
    return nil
}

原则 4:敏感信息保护

敏感信息使用环境变量或密钥管理服务

go
// ✅ 正确:从环境变量读取
type Config struct {
    Storage StorageConfig `toml:"storage"`
}

func Load() (*Config, error) {
    var cfg Config
    // 从 TOML 文件加载非敏感配置
    if err := toml.DecodeFile("config.toml", &cfg); err != nil {
        return nil, err
    }

    // 从环境变量加载敏感信息
    cfg.Storage.AccessKey = os.Getenv("STORAGE_ACCESS_KEY")
    cfg.Storage.SecretKey = os.Getenv("STORAGE_SECRET_KEY")

    return &cfg, nil
}

配置定义

标准配置结构

go
// config/config.go
package config

import (
    "errors"
    "time"
)

// Config 应用配置
type Config struct {
    // 基础设施
    Server   ServerConfig   `toml:"server"`
    Database DatabaseConfig `toml:"database"`

    // 集成服务
    Storage StorageConfig `toml:"storage"`
    AI      AIConfig      `toml:"ai"`
    Email   EmailConfig   `toml:"email"`
    SMS     SMSConfig     `toml:"sms"`
    Cache   CacheConfig   `toml:"cache"`
}

// StorageConfig 对象存储配置
type StorageConfig struct {
    Type      string        `toml:"type"`       // s3/cos/oss/local
    Endpoint  string        `toml:"endpoint"`
    Region    string        `toml:"region"`
    AccessKey string        `toml:"access_key"` // 从环境变量加载
    SecretKey string        `toml:"secret_key"` // 从环境变量加载
    Bucket    string        `toml:"bucket"`
    Timeout   time.Duration `toml:"timeout"`
}

// Validate 校验配置
func (c *StorageConfig) Validate() error {
    if c.Type == "" {
        c.Type = "local"  // 默认本地存储
    }

    if c.Type != "local" {
        if c.Endpoint == "" {
            return errors.New("storage endpoint is required")
        }
        if c.AccessKey == "" {
            return errors.New("storage access_key is required")
        }
        if c.SecretKey == "" {
            return errors.New("storage secret_key is required")
        }
    }

    if c.Timeout == 0 {
        c.Timeout = 30 * time.Second
    }

    return nil
}

// AIConfig AI 服务配置
type AIConfig struct {
    Provider string        `toml:"provider"` // openai/deepseek/claude
    APIKey   string        `toml:"api_key"`  // 从环境变量加载
    BaseURL  string        `toml:"base_url"`
    Model    string        `toml:"model"`
    Timeout  time.Duration `toml:"timeout"`
}

func (c *AIConfig) Validate() error {
    if c.Provider == "" {
        return errors.New("ai provider is required")
    }
    if c.APIKey == "" {
        return errors.New("ai api_key is required")
    }
    if c.Model == "" {
        return errors.New("ai model is required")
    }
    if c.Timeout == 0 {
        c.Timeout = 60 * time.Second
    }
    return nil
}

// EmailConfig 邮件服务配置
type EmailConfig struct {
    SMTPHost string `toml:"smtp_host"`
    SMTPPort int    `toml:"smtp_port"`
    Username string `toml:"username"`
    Password string `toml:"password"` // 从环境变量加载
    From     string `toml:"from"`
}

func (c *EmailConfig) Validate() error {
    if c.SMTPHost == "" {
        return errors.New("email smtp_host is required")
    }
    if c.SMTPPort == 0 {
        c.SMTPPort = 587  // 默认端口
    }
    if c.Username == "" {
        return errors.New("email username is required")
    }
    if c.Password == "" {
        return errors.New("email password is required")
    }
    return nil
}

配置加载

配置文件示例

toml
# config.toml

[server]
port = 8080
env = "development"

[database]
host = "localhost"
port = 3306
database = "myapp"
username = "root"

[storage]
type = "s3"
endpoint = "https://s3.amazonaws.com"
region = "us-west-2"
bucket = "my-app-uploads"
timeout = "30s"

[ai]
provider = "openai"
base_url = "https://api.openai.com/v1"
model = "gpt-4"
timeout = "60s"

[email]
smtp_host = "smtp.gmail.com"
smtp_port = 587
username = "noreply@example.com"
from = "MyApp <noreply@example.com>"

加载函数

go
// config/loader.go
package config

import (
    "fmt"
    "os"

    "github.com/BurntSushi/toml"
)

// Load 加载配置
func Load(configPath string) (*Config, error) {
    var cfg Config

    // 1. 从 TOML 文件加载
    if _, err := toml.DecodeFile(configPath, &cfg); err != nil {
        return nil, fmt.Errorf("failed to load config: %w", err)
    }

    // 2. 从环境变量加载敏感信息
    cfg.Database.Password = os.Getenv("DB_PASSWORD")
    cfg.Storage.AccessKey = os.Getenv("STORAGE_ACCESS_KEY")
    cfg.Storage.SecretKey = os.Getenv("STORAGE_SECRET_KEY")
    cfg.AI.APIKey = os.Getenv("AI_API_KEY")
    cfg.Email.Password = os.Getenv("EMAIL_PASSWORD")

    // 3. 校验配置
    if err := cfg.Validate(); err != nil {
        return nil, fmt.Errorf("invalid config: %w", err)
    }

    return &cfg, nil
}

// Validate 校验所有配置
func (c *Config) Validate() error {
    if err := c.Storage.Validate(); err != nil {
        return fmt.Errorf("storage config: %w", err)
    }
    if err := c.AI.Validate(); err != nil {
        return fmt.Errorf("ai config: %w", err)
    }
    if err := c.Email.Validate(); err != nil {
        return fmt.Errorf("email config: %w", err)
    }
    return nil
}

Client 初始化

配置注入到 Client

go
// clients/storage/client.go
package storage

type Client struct {
    config *config.StorageConfig
    // ...
}

func NewClient(cfg *config.StorageConfig) (*Client, error) {
    // 1. 校验配置
    if err := cfg.Validate(); err != nil {
        return nil, fmt.Errorf("invalid storage config: %w", err)
    }

    // 2. 根据配置初始化
    client := &Client{
        config: cfg,
    }

    // 3. 初始化第三方 SDK
    switch cfg.Type {
    case "s3":
        // 初始化 S3 客户端
    case "cos":
        // 初始化 COS 客户端
    case "local":
        // 初始化本地存储
    }

    return client, nil
}

App 容器初始化

go
// app/app.go
func (a *App) Init(ctx context.Context, cfg *config.Config) error {
    a.config = cfg

    // 初始化 Clients
    if err := a.initClients(); err != nil {
        return err
    }

    return nil
}

func (a *App) initClients() error {
    // 对象存储
    storageClient, err := storage.NewClient(&a.config.Storage)
    if err != nil {
        return fmt.Errorf("failed to init storage client: %w", err)
    }
    a.storageClient = storageClient

    // AI 服务
    aiClient, err := ai.NewClient(&a.config.AI)
    if err != nil {
        return fmt.Errorf("failed to init ai client: %w", err)
    }
    a.aiClient = aiClient

    // 邮件服务
    emailClient, err := email.NewClient(&a.config.Email)
    if err != nil {
        return fmt.Errorf("failed to init email client: %w", err)
    }
    a.emailClient = emailClient

    return nil
}

环境变量

敏感信息环境变量

必须通过环境变量加载的敏感信息

  • 数据库密码:DB_PASSWORD
  • 对象存储密钥:STORAGE_ACCESS_KEY, STORAGE_SECRET_KEY
  • AI API 密钥:AI_API_KEY
  • 邮件密码:EMAIL_PASSWORD
  • 短信服务密钥:SMS_API_KEY

环境变量示例

bash
# .env
DB_PASSWORD=your_db_password
STORAGE_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
STORAGE_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
AI_API_KEY=sk-...
EMAIL_PASSWORD=your_email_password

最佳实践

1. 默认值设置

在 Validate 方法中设置默认值

go
func (c *StorageConfig) Validate() error {
    if c.Timeout == 0 {
        c.Timeout = 30 * time.Second  // 默认超时
    }
    if c.Type == "" {
        c.Type = "local"  // 默认类型
    }
    return nil
}

2. 配置示例文件

提供 config.toml.example 文件

toml
# config.toml.example
# 复制此文件为 config.toml 并填写实际配置

[storage]
type = "s3"
endpoint = "https://s3.amazonaws.com"
region = "us-west-2"
bucket = "your-bucket-name"
# access_key 和 secret_key 从环境变量加载

3. 配置文档

在 README 中说明配置项

markdown
## 配置

### 环境变量

- `DB_PASSWORD`: 数据库密码(必填)
- `STORAGE_ACCESS_KEY`: 对象存储访问密钥(必填)
- `STORAGE_SECRET_KEY`: 对象存储密钥(必填)
- `AI_API_KEY`: AI API 密钥(必填)

### 配置文件

复制 `config.toml.example``config.toml` 并修改配置。

检查清单

  • [ ] 配置使用结构体定义,不使用零散变量
  • [ ] 每个配置结构体实现 Validate() 方法
  • [ ] 敏感信息从环境变量加载,不写入配置文件
  • [ ] Client 初始化时校验配置
  • [ ] 配置错误阻止应用启动(失快原则)
  • [ ] 提供配置示例文件(config.toml.example
  • [ ] 在 Validate 中设置默认值

相关规范