merge: maestro/tsk_tFMU7-hKzfOf [tsk_tFMU7-hKzfOf] codes 激活码模块
解决与 1A 骨架的冲突:module 统一为 github.com/wangjia/pangolin/server (codes 分支原用 pangolin/server,7 个源文件 import 已改写); go.mod require 并集(redis 取 9.20.1);Makefile 以骨架为基底并入 build-codegen/test-unit/test-integration target。 go build/vet 通过,codes 单测通过。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// Package config holds application configuration loaded from environment variables.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config holds all application-level configuration.
|
||||
type Config struct {
|
||||
// DSN is the MySQL connection string.
|
||||
// Format: user:password@tcp(host:port)/dbname?parseTime=true&loc=UTC&time_zone=%%27UTC%%27
|
||||
DSN string
|
||||
|
||||
// RedisAddr is the Redis server address (host:port).
|
||||
RedisAddr string
|
||||
RedisPassword string
|
||||
RedisDB int
|
||||
|
||||
// WebhookSecret is the HMAC-SHA256 shared secret for the card-store webhook.
|
||||
// Must be set; no default.
|
||||
WebhookSecret string
|
||||
|
||||
// RedeemFailMax is the number of consecutive redeem failures before a 1-hour lock.
|
||||
// Default: 5
|
||||
RedeemFailMax int
|
||||
|
||||
// RedeemLockDuration is how long the lock lasts after hitting RedeemFailMax.
|
||||
// Default: 1 hour
|
||||
RedeemLockDuration time.Duration
|
||||
|
||||
// WebhookTimestampTolerance is the ±window for webhook timestamp validation.
|
||||
// Default: 5 minutes
|
||||
WebhookTimestampTolerance time.Duration
|
||||
|
||||
// WebhookNonceTTL is how long a webhook nonce is kept in Redis to prevent replay.
|
||||
// Should be > 2 * WebhookTimestampTolerance. Default: 15 minutes.
|
||||
WebhookNonceTTL time.Duration
|
||||
}
|
||||
|
||||
// FromEnv reads configuration from environment variables.
|
||||
// Returns an error if required variables are missing.
|
||||
func FromEnv() (*Config, error) {
|
||||
c := &Config{
|
||||
DSN: os.Getenv("DB_DSN"),
|
||||
RedisAddr: getEnvDefault("REDIS_ADDR", "127.0.0.1:6379"),
|
||||
RedisPassword: os.Getenv("REDIS_PASSWORD"),
|
||||
RedisDB: 0,
|
||||
WebhookSecret: os.Getenv("WEBHOOK_SECRET"),
|
||||
RedeemFailMax: 5,
|
||||
RedeemLockDuration: time.Hour,
|
||||
WebhookTimestampTolerance: 5 * time.Minute,
|
||||
WebhookNonceTTL: 15 * time.Minute,
|
||||
}
|
||||
|
||||
if c.DSN == "" {
|
||||
return nil, fmt.Errorf("config: DB_DSN is required")
|
||||
}
|
||||
if c.WebhookSecret == "" {
|
||||
return nil, fmt.Errorf("config: WEBHOOK_SECRET is required")
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getEnvDefault(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
Reference in New Issue
Block a user