Files
junhong_cmp_fiber/pkg/config/config.go
huang 9c6d4a3bd4 实现个人客户微信认证和短信验证功能
- 添加个人客户微信登录和手机验证码登录接口
- 实现个人客户设备、ICCID、手机号关联管理
- 添加短信发送服务(HTTP 客户端)
- 添加微信认证服务(含 mock 实现)
- 添加 JWT Token 生成和验证工具
- 创建数据库迁移脚本(personal_customer 关联表)
- 修复测试文件中的路由注册参数错误
- 重构 scripts 目录结构(分离独立脚本到子目录)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-10 11:42:38 +08:00

225 lines
11 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 (
"errors"
"fmt"
"sync/atomic"
"time"
)
// globalConfig 保存当前配置,支持原子访问
var globalConfig atomic.Pointer[Config]
// Config 应用配置
type Config struct {
Server ServerConfig `mapstructure:"server"`
Redis RedisConfig `mapstructure:"redis"`
Database DatabaseConfig `mapstructure:"database"`
Queue QueueConfig `mapstructure:"queue"`
Logging LoggingConfig `mapstructure:"logging"`
Middleware MiddlewareConfig `mapstructure:"middleware"`
SMS SMSConfig `mapstructure:"sms"`
JWT JWTConfig `mapstructure:"jwt"`
}
// ServerConfig HTTP 服务器配置
type ServerConfig struct {
Address string `mapstructure:"address"` // 例如 ":3000"
ReadTimeout time.Duration `mapstructure:"read_timeout"` // 例如 "10s"
WriteTimeout time.Duration `mapstructure:"write_timeout"` // 例如 "10s"
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"` // 例如 "30s"
Prefork bool `mapstructure:"prefork"` // 多进程模式
}
// RedisConfig Redis 连接配置
type RedisConfig struct {
Address string `mapstructure:"address"` // 例如 "localhost"
Port int `mapstructure:"port"` // 例如 "6379"
Password string `mapstructure:"password"` // 无认证时留空
DB int `mapstructure:"db"` // 数据库编号0-15
PoolSize int `mapstructure:"pool_size"` // 最大连接数
MinIdleConns int `mapstructure:"min_idle_conns"` // 保活连接数
DialTimeout time.Duration `mapstructure:"dial_timeout"` // 例如 "5s"
ReadTimeout time.Duration `mapstructure:"read_timeout"` // 例如 "3s"
WriteTimeout time.Duration `mapstructure:"write_timeout"` // 例如 "3s"
}
// DatabaseConfig 数据库连接配置
type DatabaseConfig struct {
Host string `mapstructure:"host"` // 数据库主机地址
Port int `mapstructure:"port"` // 数据库端口
User string `mapstructure:"user"` // 数据库用户名
Password string `mapstructure:"password"` // 数据库密码(明文存储)
DBName string `mapstructure:"dbname"` // 数据库名称
SSLMode string `mapstructure:"sslmode"` // SSL 模式disable, require, verify-ca, verify-full
MaxOpenConns int `mapstructure:"max_open_conns"` // 最大打开连接数默认25
MaxIdleConns int `mapstructure:"max_idle_conns"` // 最大空闲连接数默认10
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"` // 连接最大生命周期默认5m
}
// QueueConfig 任务队列配置
type QueueConfig struct {
Concurrency int `mapstructure:"concurrency"` // Worker 并发数默认10
Queues map[string]int `mapstructure:"queues"` // 队列优先级配置(队列名 -> 权重)
RetryMax int `mapstructure:"retry_max"` // 最大重试次数默认5
Timeout time.Duration `mapstructure:"timeout"` // 任务超时时间默认10m
}
// LoggingConfig 日志配置
type LoggingConfig struct {
Level string `mapstructure:"level"` // debug, info, warn, error
Development bool `mapstructure:"development"` // 启用开发模式(美化输出)
AppLog LogRotationConfig `mapstructure:"app_log"` // 应用日志配置
AccessLog LogRotationConfig `mapstructure:"access_log"` // HTTP 访问日志配置
}
// LogRotationConfig Lumberjack 日志轮转配置
type LogRotationConfig struct {
Filename string `mapstructure:"filename"` // 日志文件路径
MaxSize int `mapstructure:"max_size"` // 轮转前的最大大小MB
MaxBackups int `mapstructure:"max_backups"` // 保留的旧文件最大数量
MaxAge int `mapstructure:"max_age"` // 保留旧文件的最大天数
Compress bool `mapstructure:"compress"` // 压缩轮转的文件
}
// MiddlewareConfig 中间件配置
type MiddlewareConfig struct {
EnableAuth bool `mapstructure:"enable_auth"` // 启用 keyauth 中间件
EnableRateLimiter bool `mapstructure:"enable_rate_limiter"` // 启用限流器默认false
RateLimiter RateLimiterConfig `mapstructure:"rate_limiter"` // 限流器配置
}
// RateLimiterConfig 限流器配置
type RateLimiterConfig struct {
Max int `mapstructure:"max"` // 时间窗口内的最大请求数
Expiration time.Duration `mapstructure:"expiration"` // 时间窗口(例如 "1m"
Storage string `mapstructure:"storage"` // "memory" 或 "redis"
}
// SMSConfig 短信服务配置
type SMSConfig struct {
GatewayURL string `mapstructure:"gateway_url"` // 短信网关地址
Username string `mapstructure:"username"` // 账号用户名
Password string `mapstructure:"password"` // 账号密码
Signature string `mapstructure:"signature"` // 短信签名(例如:【签名】)
Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间
}
// JWTConfig JWT 认证配置
type JWTConfig struct {
SecretKey string `mapstructure:"secret_key"` // JWT 签名密钥
TokenDuration time.Duration `mapstructure:"token_duration"` // Token 有效期
}
// Validate 验证配置值
func (c *Config) Validate() error {
// 服务器验证
if c.Server.Address == "" {
return fmt.Errorf("invalid configuration: server.address: must be non-empty (current value: empty)")
}
if c.Server.ReadTimeout < 5*time.Second || c.Server.ReadTimeout > 300*time.Second {
return fmt.Errorf("invalid configuration: server.read_timeout: duration out of range (current value: %s, expected: 5s-300s)", c.Server.ReadTimeout)
}
if c.Server.WriteTimeout < 5*time.Second || c.Server.WriteTimeout > 300*time.Second {
return fmt.Errorf("invalid configuration: server.write_timeout: duration out of range (current value: %s, expected: 5s-300s)", c.Server.WriteTimeout)
}
if c.Server.ShutdownTimeout < 10*time.Second || c.Server.ShutdownTimeout > 120*time.Second {
return fmt.Errorf("invalid configuration: server.shutdown_timeout: duration out of range (current value: %s, expected: 10s-120s)", c.Server.ShutdownTimeout)
}
// Redis 验证
if c.Redis.Address == "" {
return fmt.Errorf("invalid configuration: redis.address: must be non-empty (current value: empty)")
}
// Port 验证(独立字段)
if c.Redis.Port <= 0 || c.Redis.Port > 65535 {
return fmt.Errorf("invalid configuration: redis.port: port number out of range (current value: %d, expected: 1-65535)", c.Redis.Port)
}
if c.Redis.DB < 0 || c.Redis.DB > 15 {
return fmt.Errorf("invalid configuration: redis.db: database number out of range (current value: %d, expected: 0-15)", c.Redis.DB)
}
if c.Redis.PoolSize <= 0 || c.Redis.PoolSize > 1000 {
return fmt.Errorf("invalid configuration: redis.pool_size: pool size out of range (current value: %d, expected: 1-1000)", c.Redis.PoolSize)
}
if c.Redis.MinIdleConns < 0 || c.Redis.MinIdleConns > c.Redis.PoolSize {
return fmt.Errorf("invalid configuration: redis.min_idle_conns: must be 0 to pool_size (current value: %d, pool_size: %d)", c.Redis.MinIdleConns, c.Redis.PoolSize)
}
// 日志验证
validLevels := map[string]bool{"debug": true, "info": true, "warn": true, "error": true}
if !validLevels[c.Logging.Level] {
return fmt.Errorf("invalid configuration: logging.level: invalid log level (current value: %s, expected: debug, info, warn, error)", c.Logging.Level)
}
if c.Logging.AppLog.Filename == "" {
return fmt.Errorf("invalid configuration: logging.app_log.filename: must be non-empty valid file path")
}
if c.Logging.AccessLog.Filename == "" {
return fmt.Errorf("invalid configuration: logging.access_log.filename: must be non-empty valid file path")
}
if c.Logging.AppLog.MaxSize < 1 || c.Logging.AppLog.MaxSize > 1000 {
return fmt.Errorf("invalid configuration: logging.app_log.max_size: size out of range (current value: %d, expected: 1-1000 MB)", c.Logging.AppLog.MaxSize)
}
if c.Logging.AccessLog.MaxSize < 1 || c.Logging.AccessLog.MaxSize > 1000 {
return fmt.Errorf("invalid configuration: logging.access_log.max_size: size out of range (current value: %d, expected: 1-1000 MB)", c.Logging.AccessLog.MaxSize)
}
// 中间件验证
if c.Middleware.RateLimiter.Max <= 0 {
c.Middleware.RateLimiter.Max = 100 // 默认值
}
if c.Middleware.RateLimiter.Max > 10000 {
return fmt.Errorf("invalid configuration: middleware.rate_limiter.max: request limit out of range (current value: %d, expected: 1-10000)", c.Middleware.RateLimiter.Max)
}
validStorage := map[string]bool{"memory": true, "redis": true}
if c.Middleware.RateLimiter.Storage != "" && !validStorage[c.Middleware.RateLimiter.Storage] {
return fmt.Errorf("invalid configuration: middleware.rate_limiter.storage: invalid storage type (current value: %s, expected: memory or redis)", c.Middleware.RateLimiter.Storage)
}
// 短信服务验证
if c.SMS.GatewayURL == "" {
return fmt.Errorf("invalid configuration: sms.gateway_url: must be non-empty (current value: empty)")
}
if c.SMS.Username == "" {
return fmt.Errorf("invalid configuration: sms.username: must be non-empty (current value: empty)")
}
if c.SMS.Password == "" {
return fmt.Errorf("invalid configuration: sms.password: must be non-empty (current value: empty)")
}
if c.SMS.Signature == "" {
return fmt.Errorf("invalid configuration: sms.signature: must be non-empty (current value: empty)")
}
if c.SMS.Timeout < 5*time.Second || c.SMS.Timeout > 60*time.Second {
return fmt.Errorf("invalid configuration: sms.timeout: duration out of range (current value: %s, expected: 5s-60s)", c.SMS.Timeout)
}
// JWT 验证
if c.JWT.SecretKey == "" {
return fmt.Errorf("invalid configuration: jwt.secret_key: must be non-empty (current value: empty)")
}
if len(c.JWT.SecretKey) < 32 {
return fmt.Errorf("invalid configuration: jwt.secret_key: secret key too short (current length: %d, expected: >= 32)", len(c.JWT.SecretKey))
}
if c.JWT.TokenDuration < 1*time.Hour || c.JWT.TokenDuration > 720*time.Hour {
return fmt.Errorf("invalid configuration: jwt.token_duration: duration out of range (current value: %s, expected: 1h-720h)", c.JWT.TokenDuration)
}
return nil
}
// Get 返回当前配置
func Get() *Config {
return globalConfig.Load()
}
// Set 原子地更新全局配置
func Set(cfg *Config) error {
if cfg == nil {
return errors.New("config cannot be nil")
}
if err := cfg.Validate(); err != nil {
return err
}
globalConfig.Store(cfg)
return nil
}