2026-08-06 11:23:33 +08:00
|
|
|
|
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"`
|
2026-08-12 13:40:06 +08:00
|
|
|
|
GUIReaderType string `json:"gui_reader_type"`
|
2026-08-06 11:23:33 +08:00
|
|
|
|
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,
|
2026-08-12 13:40:06 +08:00
|
|
|
|
GUIReaderType: "ZJ_HZ_310000",
|
2026-08-06 11:23:33 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2026-08-12 13:40:06 +08:00
|
|
|
|
if value := strings.TrimSpace(os.Getenv("CARD_GUI_READER_TYPE")); value != "" {
|
|
|
|
|
|
cfg.GUIReaderType = value
|
|
|
|
|
|
}
|
2026-08-06 11:23:33 +08:00
|
|
|
|
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 不能为空")
|
|
|
|
|
|
}
|
2026-08-12 13:40:06 +08:00
|
|
|
|
if strings.TrimSpace(cfg.GUIReaderType) == "" {
|
|
|
|
|
|
return Config{}, errors.New("gui_reader_type 不能为空")
|
|
|
|
|
|
}
|
2026-08-06 11:23:33 +08:00
|
|
|
|
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()
|
|
|
|
|
|
}
|