169 lines
4.6 KiB
Go
169 lines
4.6 KiB
Go
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"`
|
||
GUIReaderType string `json:"gui_reader_type"`
|
||
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,
|
||
GUIReaderType: "ZJ_HZ_310000",
|
||
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 := strings.TrimSpace(os.Getenv("CARD_GUI_READER_TYPE")); value != "" {
|
||
cfg.GUIReaderType = 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.GUIReaderType) == "" {
|
||
return Config{}, errors.New("gui_reader_type 不能为空")
|
||
}
|
||
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()
|
||
}
|