fix: P0-1 RateLimiter并发写安全 + P0-2工单操作错误码区分 + P1 rows.Close修复
P0-1 (limits.go): Allow()方法改为全程使用写锁保护counters map读写,避免RLock写入时的data race P0-2 (ticket_workflow.go+ticket_handler.go): Assign/Resolve/Close操作先查询ticket存在性和状态,返回明确的CS_TICKET_4001/CS_TKT_4002/CS_TICKET_4092/CS_TICKET_4093错误码,handler根据错误前缀路由HTTP状态码 P1-1 (ticket_store.go): 移除GetStats中3处手动rows.Close(),只保留defer Close()
This commit is contained in:
127
internal/config/config.go
Normal file
127
internal/config/config.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
HTTP HTTPConfig
|
||||
Postgres PostgresConfig
|
||||
Webhook WebhookConfig
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
Addr string
|
||||
ReadHeaderTimeout int
|
||||
ReadTimeout int
|
||||
WriteTimeout int
|
||||
IdleTimeout int
|
||||
MaxHeaderBytes int
|
||||
MaxBodyBytes int64
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
Enabled bool
|
||||
DSN string
|
||||
MigrationDir string
|
||||
MaxOpenConns int
|
||||
MaxIdleConns int
|
||||
ConnMaxLifetime int
|
||||
}
|
||||
|
||||
type WebhookConfig struct {
|
||||
Secret string
|
||||
TimestampHeader string
|
||||
SignatureHeader string
|
||||
MaxSkewSeconds int
|
||||
}
|
||||
|
||||
func Load() (*Config, error) {
|
||||
cfg := &Config{
|
||||
HTTP: HTTPConfig{
|
||||
Addr: getEnv("AI_CS_ADDR", ":8080"),
|
||||
ReadHeaderTimeout: getEnvInt("AI_CS_READ_HEADER_TIMEOUT_SEC", 5),
|
||||
ReadTimeout: getEnvInt("AI_CS_READ_TIMEOUT_SEC", 10),
|
||||
WriteTimeout: getEnvInt("AI_CS_WRITE_TIMEOUT_SEC", 15),
|
||||
IdleTimeout: getEnvInt("AI_CS_IDLE_TIMEOUT_SEC", 60),
|
||||
MaxHeaderBytes: getEnvInt("AI_CS_MAX_HEADER_BYTES", 1<<20),
|
||||
MaxBodyBytes: getEnvInt64("AI_CS_MAX_BODY_BYTES", 1<<20),
|
||||
},
|
||||
Postgres: PostgresConfig{
|
||||
Enabled: getEnvBool("AI_CS_POSTGRES_ENABLED", false),
|
||||
DSN: getEnv("AI_CS_POSTGRES_DSN", ""),
|
||||
MigrationDir: getEnv("AI_CS_POSTGRES_MIGRATION_DIR", "db/migration"),
|
||||
MaxOpenConns: getEnvInt("AI_CS_POSTGRES_MAX_OPEN_CONNS", 20),
|
||||
MaxIdleConns: getEnvInt("AI_CS_POSTGRES_MAX_IDLE_CONNS", 5),
|
||||
ConnMaxLifetime: getEnvInt("AI_CS_POSTGRES_CONN_MAX_LIFETIME_SEC", 300),
|
||||
},
|
||||
Webhook: WebhookConfig{
|
||||
Secret: getEnv("AI_CS_WEBHOOK_SECRET", ""),
|
||||
TimestampHeader: getEnv("AI_CS_WEBHOOK_TIMESTAMP_HEADER", "X-CS-Timestamp"),
|
||||
SignatureHeader: getEnv("AI_CS_WEBHOOK_SIGNATURE_HEADER", "X-CS-Signature"),
|
||||
MaxSkewSeconds: getEnvInt("AI_CS_WEBHOOK_MAX_SKEW_SECONDS", 300),
|
||||
},
|
||||
}
|
||||
if strings.TrimSpace(cfg.HTTP.Addr) == "" {
|
||||
return nil, fmt.Errorf("AI_CS_ADDR must not be empty")
|
||||
}
|
||||
if cfg.HTTP.MaxBodyBytes <= 0 {
|
||||
return nil, fmt.Errorf("AI_CS_MAX_BODY_BYTES must be positive")
|
||||
}
|
||||
if cfg.Postgres.Enabled && strings.TrimSpace(cfg.Postgres.DSN) == "" {
|
||||
return nil, fmt.Errorf("AI_CS_POSTGRES_DSN must not be empty when postgres is enabled")
|
||||
}
|
||||
if cfg.Webhook.MaxSkewSeconds <= 0 {
|
||||
return nil, fmt.Errorf("AI_CS_WEBHOOK_MAX_SKEW_SECONDS must be positive")
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func getEnv(key, fallback string) string {
|
||||
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func getEnvInt(key string, fallback int) int {
|
||||
value := strings.TrimSpace(os.Getenv(key))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func getEnvInt64(key string, fallback int64) int64 {
|
||||
value := strings.TrimSpace(os.Getenv(key))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
parsed, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func getEnvBool(key string, fallback bool) bool {
|
||||
value := strings.TrimSpace(strings.ToLower(os.Getenv(key)))
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
switch value {
|
||||
case "1", "true", "yes", "on":
|
||||
return true
|
||||
case "0", "false", "no", "off":
|
||||
return false
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user