package main import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "strconv" "strings" ) type Config struct { Host string `json:"host"` Port int `json:"port"` DLLDir string `json:"dll_dir"` CORSOrigin string `json:"cors_origin"` LogDir string `json:"log_dir"` LogRetentionDays int `json:"log_retention_days"` LogMaxFileMB int `json:"log_max_file_mb"` LogMaxTotalMB int `json:"log_max_total_mb"` RestartDelayMS int `json:"restart_delay_ms"` MaxRapidRestarts int `json:"max_rapid_restarts"` AppRoot string `json:"-"` } func defaultConfig(appRoot string) Config { return Config{ Host: "127.0.0.1", Port: 17880, DLLDir: "package/DWCardReaderDLL", CORSOrigin: "*", LogDir: "logs", LogRetentionDays: 30, LogMaxFileMB: 20, LogMaxTotalMB: 200, RestartDelayMS: 1500, MaxRapidRestarts: 5, AppRoot: appRoot, } } func resolveAppRoot() (string, error) { if root := strings.TrimSpace(os.Getenv("CARD_APP_ROOT")); root != "" { return filepath.Abs(root) } cwd, cwdErr := os.Getwd() if cwdErr == nil && fileExists(filepath.Join(cwd, "config.json")) { return filepath.Abs(cwd) } exe, exeErr := os.Executable() if exeErr == nil { return filepath.Abs(filepath.Dir(exe)) } if cwdErr != nil { return "", fmt.Errorf("无法确定程序目录: cwd=%v, executable=%v", cwdErr, exeErr) } return filepath.Abs(cwd) } func loadConfig() (Config, error) { root, err := resolveAppRoot() if err != nil { return Config{}, err } return loadConfigAt(root) } func loadConfigAt(root string) (Config, error) { root, err := filepath.Abs(root) if err != nil { return Config{}, fmt.Errorf("解析程序目录失败: %w", err) } cfg := defaultConfig(root) path := filepath.Join(root, "config.json") if data, readErr := os.ReadFile(path); readErr == nil { if err = json.Unmarshal(data, &cfg); err != nil { return Config{}, fmt.Errorf("配置文件格式错误 %s: %w", path, err) } } else if !errors.Is(readErr, os.ErrNotExist) { return Config{}, fmt.Errorf("读取配置文件失败 %s: %w", path, readErr) } cfg.AppRoot = root if value := strings.TrimSpace(os.Getenv("CARD_HOST")); value != "" { cfg.Host = value } if value := strings.TrimSpace(os.Getenv("CARD_PORT")); value != "" { cfg.Port, err = strconv.Atoi(value) if err != nil { return Config{}, fmt.Errorf("CARD_PORT 不是有效端口: %q", value) } } if value := strings.TrimSpace(os.Getenv("CARD_DLL_DIR")); value != "" { cfg.DLLDir = value } if value := os.Getenv("CARD_CORS_ORIGIN"); value != "" { cfg.CORSOrigin = value } if value := strings.TrimSpace(os.Getenv("CARD_LOG_DIR")); value != "" { cfg.LogDir = value } if value := strings.TrimSpace(os.Getenv("CARD_LOG_MAX_FILE_MB")); value != "" { cfg.LogMaxFileMB, err = strconv.Atoi(value) if err != nil { return Config{}, fmt.Errorf("CARD_LOG_MAX_FILE_MB 不是有效整数: %q", value) } } if value := strings.TrimSpace(os.Getenv("CARD_LOG_MAX_TOTAL_MB")); value != "" { cfg.LogMaxTotalMB, err = strconv.Atoi(value) if err != nil { return Config{}, fmt.Errorf("CARD_LOG_MAX_TOTAL_MB 不是有效整数: %q", value) } } if strings.TrimSpace(cfg.Host) == "" { return Config{}, errors.New("host 不能为空") } if cfg.Port < 1 || cfg.Port > 65535 { return Config{}, fmt.Errorf("port 超出范围: %d", cfg.Port) } if strings.TrimSpace(cfg.DLLDir) == "" { return Config{}, errors.New("dll_dir 不能为空") } if strings.TrimSpace(cfg.CORSOrigin) == "" { cfg.CORSOrigin = "*" } if strings.TrimSpace(cfg.LogDir) == "" { cfg.LogDir = "logs" } if cfg.LogRetentionDays <= 0 { cfg.LogRetentionDays = 30 } if cfg.LogMaxFileMB <= 0 { cfg.LogMaxFileMB = 20 } if cfg.LogMaxTotalMB <= 0 { cfg.LogMaxTotalMB = 200 } if cfg.LogMaxTotalMB < cfg.LogMaxFileMB*3 { return Config{}, fmt.Errorf("log_max_total_mb 至少应为 log_max_file_mb 的 3 倍(当前 %d < %d)", cfg.LogMaxTotalMB, cfg.LogMaxFileMB*3) } if cfg.RestartDelayMS < 100 { cfg.RestartDelayMS = 1500 } if cfg.MaxRapidRestarts <= 0 { cfg.MaxRapidRestarts = 5 } if !filepath.IsAbs(cfg.LogDir) { cfg.LogDir = filepath.Join(cfg.AppRoot, cfg.LogDir) } return cfg, nil } func fileExists(path string) bool { info, err := os.Stat(path) return err == nil && !info.IsDir() }