package main import ( "context" "errors" "fmt" "io" "net" "net/http" "os" "os/exec" "os/signal" "path/filepath" "runtime/debug" "syscall" "time" ) var version = "dev" func main() { os.Exit(realMain()) } func realMain() (exitCode int) { defer func() { if recovered := recover(); recovered != nil { fmt.Fprintf(os.Stderr, "readcard-go panic: %v\n%s\n", recovered, debug.Stack()) exitCode = 2 } }() if isWorker() { return runWorker() } return runSupervisor() } func isWorker() bool { if os.Getenv("CARD_NO_SUPERVISOR") == "1" { return true } for _, arg := range os.Args[1:] { if arg == "--worker" { return true } } return false } func runWorker() int { cfg, err := loadConfig() if err != nil { logBootstrapFailure("worker_config_failure", err) fmt.Fprintln(os.Stderr, err) return 1 } logger, err := newAppLogger(cfg, "worker") if err != nil { fmt.Fprintf(os.Stderr, "初始化日志失败: %v\n", err) return 1 } defer logger.Close() defer func() { if recovered := recover(); recovered != nil { logger.Error("工作进程 panic", "event", "worker_panic", "panic", fmt.Sprint(recovered), "stack", string(debug.Stack())) panic(recovered) } }() reader := newDLLReader(cfg) status := reader.Status() if status.Loaded { logger.Info("读卡 DLL 初始化成功", "event", "dll_loaded", "dll_dir", status.DLLDir) } else { logger.Error("读卡 DLL 初始化失败", "event", "dll_load_failure", "dll_dir", status.DLLDir, "error", status.Error) } address := net.JoinHostPort(cfg.Host, fmt.Sprint(cfg.Port)) listener, err := net.Listen("tcp", address) if err != nil { logger.Error("HTTP 服务监听失败", "event", "listen_failure", "address", address, "error", err) return 1 } server := &http.Server{ Handler: newHandler(cfg, reader, logger), ReadHeaderTimeout: 10 * time.Second, ReadTimeout: 30 * time.Second, WriteTimeout: 5 * time.Minute, IdleTimeout: 120 * time.Second, MaxHeaderBytes: 1 << 20, } logger.Info("读卡服务已启动", "event", "service_started", "version", version, "url", "http://"+address, "dll_dir", status.DLLDir, "arch", status.Arch) serveErr := make(chan error, 1) go func() { serveErr <- server.Serve(listener) }() signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, syscall.SIGTERM) defer signal.Stop(signals) select { case sig := <-signals: logger.Info("收到停止信号", "event", "shutdown_signal", "signal", sig.String()) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err = server.Shutdown(ctx); err != nil { logger.Error("HTTP 服务优雅停止失败", "event", "shutdown_failure", "error", err) return 1 } logger.Info("读卡服务已停止", "event", "service_stopped") return 0 case err = <-serveErr: if errors.Is(err, http.ErrServerClosed) { return 0 } logger.Error("HTTP 服务异常退出", "event", "server_failure", "error", err) return 1 } } func runSupervisor() int { cfg, err := loadConfig() if err != nil { logBootstrapFailure("supervisor_config_failure", err) fmt.Fprintln(os.Stderr, err) return 1 } logger, err := newAppLogger(cfg, "supervisor") if err != nil { fmt.Fprintf(os.Stderr, "初始化日志失败: %v\n", err) return 1 } defer logger.Close() exe, err := os.Executable() if err != nil { logger.Error("获取程序路径失败", "event", "supervisor_failure", "error", err) return 1 } signals := make(chan os.Signal, 1) signal.Notify(signals, os.Interrupt, syscall.SIGTERM) defer signal.Stop(signals) rapidRestarts := 0 for { started := time.Now() cmd := exec.Command(exe, "--worker") cmd.Dir = cfg.AppRoot cmd.Env = append(os.Environ(), "CARD_APP_ROOT="+cfg.AppRoot) cmd.Stdout = os.Stdout crashWriter, crashErr := newDailyWriter( cfg.LogDir, "crash", cfg.LogRetentionDays, int64(cfg.LogMaxFileMB)*1024*1024, int64(cfg.LogMaxTotalMB)*1024*1024, ) if crashErr != nil { logger.Error("打开崩溃日志失败", "event", "crash_log_failure", "error", crashErr) cmd.Stderr = os.Stderr } else { cmd.Stderr = io.MultiWriter(os.Stderr, crashWriter) } if err = cmd.Start(); err != nil { if crashWriter != nil { _ = crashWriter.Close() } logger.Error("启动工作进程失败", "event", "worker_start_failure", "error", err) return 1 } logger.Info("工作进程已启动", "event", "worker_started", "worker_pid", cmd.Process.Pid) waitDone := make(chan error, 1) go func() { waitDone <- cmd.Wait() }() select { case sig := <-signals: logger.Info("守护进程收到停止信号", "event", "supervisor_shutdown", "signal", sig.String(), "worker_pid", cmd.Process.Pid) _ = cmd.Process.Signal(os.Interrupt) select { case <-waitDone: case <-time.After(10 * time.Second): logger.Error("工作进程停止超时,强制结束", "event", "worker_kill", "worker_pid", cmd.Process.Pid) _ = cmd.Process.Kill() <-waitDone } if crashWriter != nil { _ = crashWriter.Close() } return 0 case waitErr := <-waitDone: if crashWriter != nil { _ = crashWriter.Close() } runFor := time.Since(started) exitCode := 0 if cmd.ProcessState != nil { exitCode = cmd.ProcessState.ExitCode() } logger.Error("工作进程异常退出", "event", "worker_crash", "worker_pid", cmd.Process.Pid, "exit_code", exitCode, "runtime_ms", runFor.Milliseconds(), "error", waitErr) if runFor < 30*time.Second { rapidRestarts++ } else { rapidRestarts = 0 } if rapidRestarts >= cfg.MaxRapidRestarts { logger.Error("工作进程短时间内连续崩溃,停止自动重启", "event", "restart_limit", "rapid_restarts", rapidRestarts) return 1 } logger.Warn("即将自动重启工作进程", "event", "worker_restart", "delay_ms", cfg.RestartDelayMS, "rapid_restarts", rapidRestarts) select { case sig := <-signals: logger.Info("重启等待期间收到停止信号", "event", "supervisor_shutdown", "signal", sig.String()) return 0 case <-time.After(time.Duration(cfg.RestartDelayMS) * time.Millisecond): } } } } func logBootstrapFailure(event string, failure error) { root, err := resolveAppRoot() if err != nil { return } cfg := defaultConfig(root) cfg.LogDir = filepath.Join(root, cfg.LogDir) logger, err := newAppLogger(cfg, "bootstrap") if err != nil { return } logger.Error("程序启动失败", "event", event, "error", failure) _ = logger.Close() }