fix: restore reviewable migration evidence
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.misaka.ren/M1saka/token_thief/db"
|
||||
)
|
||||
|
||||
// Config 保存从环境变量加载的全部运行配置。
|
||||
type Config struct {
|
||||
ListenAddr string
|
||||
UpstreamURL *url.URL
|
||||
ClickHouseURL string
|
||||
MaxBodyBytes int64
|
||||
LogQueueSize int
|
||||
LogQueueBytes int64
|
||||
LogBatchSize int
|
||||
LogBatchInterval time.Duration
|
||||
LogWorkers int
|
||||
FilterFile string
|
||||
DBReconnectInterval time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
IdleTimeout time.Duration
|
||||
UpstreamTimeout time.Duration
|
||||
UpstreamResponseTimeout time.Duration
|
||||
UpstreamStreamIdleTimeout time.Duration
|
||||
UpstreamTLSInsecureSkipVerify bool
|
||||
TrustedProxies []netip.Prefix
|
||||
}
|
||||
|
||||
// Load 从进程环境读取配置;缺少必填字段时返回错误。
|
||||
func Load() (*Config, error) {
|
||||
maxBodyBytes, err := getEnvInt64("MAX_BODY_BYTES", 1<<20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logQueueSize, err := getEnvInt("LOG_QUEUE_SIZE", 256)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logQueueBytes, err := getEnvInt64("LOG_QUEUE_BYTES", 64<<20)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logBatchSize, err := getEnvInt("LOG_BATCH_SIZE", 50)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logBatchInterval, err := getEnvDuration("LOG_BATCH_INTERVAL", 2*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logWorkers, err := getEnvInt("LOG_WORKERS", 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbReconnectInterval, err := getEnvDuration("DB_RECONNECT_INTERVAL", 10*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readTimeout, err := getEnvDuration("READ_TIMEOUT", 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeTimeout, err := getEnvDuration("WRITE_TIMEOUT", 10*time.Minute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
idleTimeout, err := getEnvDuration("IDLE_TIMEOUT", 5*time.Minute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreamTimeout, err := getEnvDuration("UPSTREAM_TIMEOUT", 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreamResponseTimeout, err := getEnvDuration("UPSTREAM_RESPONSE_TIMEOUT", 30*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreamStreamIdleTimeout, err := getEnvDuration("UPSTREAM_STREAM_IDLE_TIMEOUT", 2*time.Minute)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upstreamTLSInsecureSkipVerify, err := getEnvBool("UPSTREAM_TLS_INSECURE_SKIP_VERIFY", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trustedProxies, err := getEnvPrefixes("TRUSTED_PROXIES")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
ListenAddr: getEnv("LISTEN_ADDR", ":8080"),
|
||||
ClickHouseURL: os.Getenv("CLICKHOUSE_URL"),
|
||||
MaxBodyBytes: maxBodyBytes,
|
||||
LogQueueSize: logQueueSize,
|
||||
LogQueueBytes: logQueueBytes,
|
||||
LogBatchSize: logBatchSize,
|
||||
LogBatchInterval: logBatchInterval,
|
||||
LogWorkers: logWorkers,
|
||||
FilterFile: getEnv("FILTER_FILE", "./filter.yaml"),
|
||||
DBReconnectInterval: dbReconnectInterval,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
IdleTimeout: idleTimeout,
|
||||
UpstreamTimeout: upstreamTimeout,
|
||||
UpstreamResponseTimeout: upstreamResponseTimeout,
|
||||
UpstreamStreamIdleTimeout: upstreamStreamIdleTimeout,
|
||||
UpstreamTLSInsecureSkipVerify: upstreamTLSInsecureSkipVerify,
|
||||
TrustedProxies: trustedProxies,
|
||||
}
|
||||
|
||||
upstream := os.Getenv("UPSTREAM_URL")
|
||||
if upstream == "" {
|
||||
return nil, errors.New("UPSTREAM_URL is required")
|
||||
}
|
||||
u, err := url.Parse(upstream)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid UPSTREAM_URL: %w", err)
|
||||
}
|
||||
if u.Scheme == "" || u.Host == "" {
|
||||
return nil, fmt.Errorf("invalid UPSTREAM_URL: %q", upstream)
|
||||
}
|
||||
cfg.UpstreamURL = u
|
||||
|
||||
if cfg.ClickHouseURL == "" {
|
||||
return nil, errors.New("CLICKHOUSE_URL is required")
|
||||
}
|
||||
if _, err := db.ClickHouseOptions(cfg.ClickHouseURL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
positiveValues := []struct {
|
||||
key string
|
||||
value int64
|
||||
}{
|
||||
{key: "MAX_BODY_BYTES", value: cfg.MaxBodyBytes},
|
||||
{key: "LOG_QUEUE_SIZE", value: int64(cfg.LogQueueSize)},
|
||||
{key: "LOG_QUEUE_BYTES", value: cfg.LogQueueBytes},
|
||||
{key: "LOG_BATCH_SIZE", value: int64(cfg.LogBatchSize)},
|
||||
{key: "LOG_BATCH_INTERVAL", value: int64(cfg.LogBatchInterval)},
|
||||
{key: "LOG_WORKERS", value: int64(cfg.LogWorkers)},
|
||||
{key: "DB_RECONNECT_INTERVAL", value: int64(cfg.DBReconnectInterval)},
|
||||
{key: "READ_TIMEOUT", value: int64(cfg.ReadTimeout)},
|
||||
{key: "WRITE_TIMEOUT", value: int64(cfg.WriteTimeout)},
|
||||
{key: "IDLE_TIMEOUT", value: int64(cfg.IdleTimeout)},
|
||||
{key: "UPSTREAM_TIMEOUT", value: int64(cfg.UpstreamTimeout)},
|
||||
{key: "UPSTREAM_RESPONSE_TIMEOUT", value: int64(cfg.UpstreamResponseTimeout)},
|
||||
{key: "UPSTREAM_STREAM_IDLE_TIMEOUT", value: int64(cfg.UpstreamStreamIdleTimeout)},
|
||||
}
|
||||
for _, item := range positiveValues {
|
||||
if item.value <= 0 {
|
||||
return nil, fmt.Errorf("%s must be greater than zero", item.key)
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getEnv(key, def string) string {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func getEnvInt(key string, def int) (int, error) {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s: %w", key, err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func getEnvInt64(key string, def int64) (int64, error) {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s: %w", key, err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func getEnvDuration(key string, def time.Duration) (time.Duration, error) {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
d, err := time.ParseDuration(v)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s: %w", key, err)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func getEnvBool(key string, def bool) (bool, error) {
|
||||
if v := os.Getenv(key); v != "" {
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid %s: %w", key, err)
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
return def, nil
|
||||
}
|
||||
|
||||
func getEnvPrefixes(key string) ([]netip.Prefix, error) {
|
||||
v := os.Getenv(key)
|
||||
if v == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(v, ",")
|
||||
prefixes := make([]netip.Prefix, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
value := strings.TrimSpace(part)
|
||||
if value == "" {
|
||||
return nil, fmt.Errorf("invalid %s: empty IP or CIDR", key)
|
||||
}
|
||||
|
||||
if addr, err := netip.ParseAddr(value); err == nil {
|
||||
prefixes = append(prefixes, netip.PrefixFrom(addr, addr.BitLen()))
|
||||
continue
|
||||
}
|
||||
|
||||
prefix, err := netip.ParsePrefix(value)
|
||||
if err != nil || !prefix.IsValid() || prefix != prefix.Masked() {
|
||||
return nil, fmt.Errorf("invalid %s entry %q", key, value)
|
||||
}
|
||||
prefixes = append(prefixes, prefix)
|
||||
}
|
||||
return prefixes, nil
|
||||
}
|
||||
Reference in New Issue
Block a user