commit ef219bef1e2034c1ce73b87a9987e57d48158653 Author: zyj Date: Sat Jul 12 13:35:47 2025 +0800 init diff --git a/dev.md b/dev.md new file mode 100644 index 0000000..45b28b2 --- /dev/null +++ b/dev.md @@ -0,0 +1,1068 @@ +# 使用 Go 语言在 Windows 上开发简易 PM2 式进程守护系统 + +下面我将为您详细讲解如何使用 Go 语言在 Windows 上开发一个类似 PM2 的进程守护系统。这个系统将包含核心功能如进程启动、停止、重启、监控和日志管理。 + +## 系统架构设计 + +``` ++-------------------+ +-----------------+ +-----------------+ +| 命令行客户端 | | 守护进程 | | 被守护进程 | +| (pm2-win-cli) |<--->| (pm2-win-daemon)|---->| (your-app.exe) | ++-------------------+ +-----------------+ +-----------------+ + ↑ ↑ ↑ ↑ + | | | | ++-------------------+ +-----------------+ +-----------------+ +| 用户交互 | | 进程管理 | | 子进程 | +| 命令解析 | | 监控重启 | | 执行实际任务 | +| 状态显示 | | 日志管理 | +-----------------+ ++-------------------+ | RPC服务 | + +-----------------+ +``` + +## 完整实现代码 + +### 1. 守护进程主程序 (`daemon/main.go`) + +```go +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "os/exec" + "path/filepath" + "strconv" + "sync" + "time" + + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/eventlog" +) + +const ( + serviceName = "PM2WinDaemon" + daemonPort = 9999 +) + +var ( + installFlag = flag.Bool("install", false, "Install service") + uninstallFlag = flag.Bool("uninstall", false, "Uninstall service") + runFlag = flag.Bool("run", false, "Run in console mode") +) + +func main() { + flag.Parse() + + if *installFlag { + installService() + return + } + + if *uninstallFlag { + uninstallService() + return + } + + if *runFlag { + runConsoleMode() + return + } + + // 检查是否在服务环境中运行 + isService, err := svc.IsWindowsService() + if err != nil { + log.Fatalf("Failed to determine if running as service: %v", err) + } + + if isService { + runService() + } else { + fmt.Println("Please run as service or with --run flag") + os.Exit(1) + } +} + +func runConsoleMode() { + log.Println("Running in console mode") + startDaemon() +} + +func runService() { + elog, err := eventlog.Open(serviceName) + if err != nil { + log.Fatalf("Failed to open event log: %v", err) + } + defer elog.Close() + + elog.Info(1, "Starting service") + + if err := svc.Run(serviceName, &serviceHandler{elog: elog}); err != nil { + elog.Error(1, fmt.Sprintf("Service failed: %v", err)) + return + } + + elog.Info(1, "Service stopped") +} + +func startDaemon() { + log.Println("PM2Win Daemon starting") + + // 创建进程管理器 + pm := NewProcessManager() + + // 启动RPC服务器 + go startRPCServer(pm) + + // 启动监控器 + go pm.StartMonitor() + + // 启动Web界面 + go startWebUI(pm) + + log.Println("Daemon is ready. Listening on port", daemonPort) + + // 等待退出信号 + quit := make(chan os.Signal, 1) + <-quit + pm.StopAll() + log.Println("Daemon stopped") +} + +func installService() { + exePath, err := filepath.Abs(os.Args[0]) + if err != nil { + log.Fatal(err) + } + + mgr, err := svc.Mgr.Connect() + if err != nil { + log.Fatal(err) + } + defer mgr.Disconnect() + + // 创建服务 + config := svc.Config{ + DisplayName: "PM2Win Daemon", + Description: "Process manager for Windows similar to PM2", + StartType: svc.StartAutomatic, + } + + service, err := mgr.CreateService(serviceName, exePath, config) + if err != nil { + log.Fatal("CreateService failed:", err) + } + defer service.Close() + + // 设置恢复策略 + recoveryActions := []svc.RecoveryAction{ + {Type: svc.ServiceRestart, Delay: 60 * time.Second}, + {Type: svc.ServiceRestart, Delay: 120 * time.Second}, + {Type: svc.NoAction}, + } + + if err := service.SetRecoveryActions(recoveryActions, 86400); err != nil { + log.Println("Warning: failed to set recovery actions:", err) + } + + // 创建事件日志源 + if err := eventlog.InstallAsEventCreate(serviceName, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { + log.Println("Warning: failed to create event source:", err) + } + + log.Println("Service installed successfully") +} + +func uninstallService() { + mgr, err := svc.Mgr.Connect() + if err != nil { + log.Fatal(err) + } + defer mgr.Disconnect() + + service, err := mgr.OpenService(serviceName) + if err != nil { + log.Fatal("Service not installed:", err) + } + defer service.Close() + + // 停止服务 + if status, err := service.Control(svc.Stop); err == nil { + timeout := time.Now().Add(10 * time.Second) + for status.State != svc.Stopped && time.Now().Before(timeout) { + time.Sleep(500 * time.Millisecond) + status, err = service.Query() + if err != nil { + break + } + } + } + + // 删除服务 + if err := service.Delete(); err != nil { + log.Fatal("DeleteService failed:", err) + } + + // 移除事件日志源 + if err := eventlog.Remove(serviceName); err != nil { + log.Println("Warning: failed to remove event source:", err) + } + + log.Println("Service uninstalled successfully") +} + +type serviceHandler struct { + elog *eventlog.Log +} + +func (h *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { + changes <- svc.Status{State: svc.StartPending} + + // 启动守护逻辑 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + go func() { + startDaemon() + cancel() + }() + + changes <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown} + + for { + select { + case c := <-r: + switch c.Cmd { + case svc.Stop, svc.Shutdown: + changes <- svc.Status{State: svc.StopPending} + cancel() + return false, 0 + case svc.Interrogate: + changes <- c.CurrentStatus + } + case <-ctx.Done(): + return false, 0 + } + } +} +``` + +### 2. 进程管理器 (`daemon/process_manager.go`) + +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "sync" + "time" +) + +// ProcessConfig 进程配置 +type ProcessConfig struct { + ID string `json:"id"` + Name string `json:"name"` + Command string `json:"command"` + Args []string `json:"args"` + WorkingDir string `json:"working_dir"` + Restart bool `json:"restart"` + MaxRestarts int `json:"max_restarts"` + LogFile string `json:"log_file"` +} + +// ProcessStatus 进程状态 +type ProcessStatus struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` // running, stopped, restarting + PID int `json:"pid"` + Restarts int `json:"restarts"` + StartTime time.Time `json:"start_time"` + Uptime string `json:"uptime"` + CPU float64 `json:"cpu"` + Memory uint64 `json:"memory"` +} + +// Process 被守护的进程 +type Process struct { + Config ProcessConfig + Status ProcessStatus + cmd *exec.Cmd + mu sync.Mutex + stopCh chan struct{} + log *os.File +} + +// ProcessManager 进程管理器 +type ProcessManager struct { + processes map[string]*Process + mu sync.RWMutex +} + +func NewProcessManager() *ProcessManager { + return &ProcessManager{ + processes: make(map[string]*Process), + } +} + +// StartProcess 启动新进程 +func (pm *ProcessManager) StartProcess(cfg ProcessConfig) error { + pm.mu.Lock() + defer pm.mu.Unlock() + + if _, exists := pm.processes[cfg.ID]; exists { + return fmt.Errorf("process with ID %s already exists", cfg.ID) + } + + process := &Process{ + Config: cfg, + Status: ProcessStatus{ + ID: cfg.ID, + Name: cfg.Name, + Status: "starting", + StartTime: time.Now(), + }, + stopCh: make(chan struct{}), + } + + // 设置日志文件 + if cfg.LogFile != "" { + logFile, err := os.OpenFile(cfg.LogFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("failed to open log file: %v", err) + } + process.log = logFile + } + + pm.processes[cfg.ID] = process + + // 启动进程 + go process.Run() + + return nil +} + +// StopProcess 停止进程 +func (pm *ProcessManager) StopProcess(id string) error { + pm.mu.RLock() + defer pm.mu.RUnlock() + + process, exists := pm.processes[id] + if !exists { + return fmt.Errorf("process with ID %s not found", id) + } + + return process.Stop() +} + +// RestartProcess 重启进程 +func (pm *ProcessManager) RestartProcess(id string) error { + pm.mu.Lock() + defer pm.mu.Unlock() + + process, exists := pm.processes[id] + if !exists { + return fmt.Errorf("process with ID %s not found", id) + } + + // 先停止 + if err := process.Stop(); err != nil { + return err + } + + // 重置状态 + process.Status.Status = "restarting" + process.Status.Restarts = 0 + + // 重新启动 + go process.Run() + + return nil +} + +// ListProcesses 列出所有进程 +func (pm *ProcessManager) ListProcesses() []ProcessStatus { + pm.mu.RLock() + defer pm.mu.RUnlock() + + statuses := make([]ProcessStatus, 0, len(pm.processes)) + for _, proc := range pm.processes { + proc.mu.Lock() + status := proc.Status + proc.mu.Unlock() + + // 更新运行时间 + if status.Status == "running" { + status.Uptime = time.Since(status.StartTime).Round(time.Second).String() + } + + // TODO: 获取CPU和内存使用率 + + statuses = append(statuses, status) + } + + return statuses +} + +// GetProcessStatus 获取单个进程状态 +func (pm *ProcessManager) GetProcessStatus(id string) (ProcessStatus, error) { + pm.mu.RLock() + defer pm.mu.RUnlock() + + process, exists := pm.processes[id] + if !exists { + return ProcessStatus{}, fmt.Errorf("process with ID %s not found", id) + } + + process.mu.Lock() + defer process.mu.Unlock() + + status := process.Status + if status.Status == "running" { + status.Uptime = time.Since(status.StartTime).Round(time.Second).String() + } + + return status, nil +} + +// StopAll 停止所有进程 +func (pm *ProcessManager) StopAll() { + pm.mu.Lock() + defer pm.mu.Unlock() + + for id, proc := range pm.processes { + if err := proc.Stop(); err != nil { + log.Printf("Failed to stop process %s: %v", id, err) + } + delete(pm.processes, id) + } +} + +// StartMonitor 启动监控器 +func (pm *ProcessManager) StartMonitor() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for range ticker.C { + pm.mu.RLock() + for _, proc := range pm.processes { + proc.mu.Lock() + if proc.Status.Status == "running" { + // 检查进程是否存活 + if proc.cmd.ProcessState != nil && proc.cmd.ProcessState.Exited() { + proc.Status.Status = "stopped" + + // 如果需要重启 + if proc.Config.Restart && + (proc.Config.MaxRestarts == 0 || proc.Status.Restarts < proc.Config.MaxRestarts) { + proc.Status.Restarts++ + go proc.Run() + } + } + } + proc.mu.Unlock() + } + pm.mu.RUnlock() + } +} + +// Run 运行进程 +func (p *Process) Run() { + p.mu.Lock() + p.Status.Status = "starting" + p.Status.StartTime = time.Now() + p.mu.Unlock() + + for { + select { + case <-p.stopCh: + return + default: + // 创建命令 + cmd := exec.Command(p.Config.Command, p.Config.Args...) + + // 设置工作目录 + if p.Config.WorkingDir != "" { + cmd.Dir = p.Config.WorkingDir + } + + // 设置输出 + if p.log != nil { + cmd.Stdout = p.log + cmd.Stderr = p.log + } + + p.mu.Lock() + p.cmd = cmd + p.Status.PID = cmd.Process.Pid + p.Status.Status = "running" + p.mu.Unlock() + + // 启动进程 + if err := cmd.Start(); err != nil { + log.Printf("Failed to start process %s: %v", p.Config.ID, err) + + p.mu.Lock() + p.Status.Status = "stopped" + p.mu.Unlock() + return + } + + log.Printf("Process %s started with PID %d", p.Config.ID, cmd.Process.Pid) + + // 等待进程退出 + err := cmd.Wait() + + p.mu.Lock() + if err != nil { + log.Printf("Process %s exited with error: %v", p.Config.ID, err) + } else { + log.Printf("Process %s exited normally", p.Config.ID) + } + + // 检查是否应该重启 + if !p.Config.Restart { + p.Status.Status = "stopped" + p.mu.Unlock() + return + } + + if p.Config.MaxRestarts > 0 && p.Status.Restarts >= p.Config.MaxRestarts { + p.Status.Status = "stopped" + p.mu.Unlock() + return + } + + p.Status.Restarts++ + p.mu.Unlock() + + // 延迟重启 + time.Sleep(5 * time.Second) + } + } +} + +// Stop 停止进程 +func (p *Process) Stop() error { + close(p.stopCh) + + p.mu.Lock() + defer p.mu.Unlock() + + if p.cmd == nil || p.cmd.Process == nil { + return nil + } + + // 先尝试正常终止 + if err := p.cmd.Process.Signal(os.Interrupt); err != nil { + log.Printf("Failed to send interrupt to process %s: %v", p.Config.ID, err) + } + + // 等待5秒 + timeout := time.After(5 * time.Second) + done := make(chan error, 1) + go func() { + _, err := p.cmd.Process.Wait() + done <- err + }() + + select { + case <-timeout: + // 强制终止 + if err := p.cmd.Process.Kill(); err != nil { + return fmt.Errorf("failed to kill process: %v", err) + } + case <-done: + } + + p.Status.Status = "stopped" + + // 关闭日志文件 + if p.log != nil { + p.log.Close() + } + + return nil +} +``` + +### 3. RPC 服务器 (`daemon/rpc_server.go`) + +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "strings" +) + +// RPCRequest RPC请求 +type RPCRequest struct { + Method string `json:"method"` + Params json.RawMessage `json:"params"` + ID int `json:"id"` +} + +// RPCResponse RPC响应 +type RPCResponse struct { + Result interface{} `json:"result,omitempty"` + Error string `json:"error,omitempty"` + ID int `json:"id"` +} + +// RPC方法处理 +type RPCHandler struct { + pm *ProcessManager +} + +func startRPCServer(pm *ProcessManager) { + handler := &RPCHandler{pm: pm} + + http.HandleFunc("/rpc", func(w http.ResponseWriter, r *http.Request) { + var req RPCRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + var resp RPCResponse + resp.ID = req.ID + + switch strings.ToLower(req.Method) { + case "start": + var cfg ProcessConfig + if err := json.Unmarshal(req.Params, &cfg); err != nil { + resp.Error = "Invalid params" + } else { + if err := pm.StartProcess(cfg); err != nil { + resp.Error = err.Error() + } else { + resp.Result = "Process started" + } + } + + case "stop": + var id string + if err := json.Unmarshal(req.Params, &id); err != nil { + resp.Error = "Invalid params" + } else { + if err := pm.StopProcess(id); err != nil { + resp.Error = err.Error() + } else { + resp.Result = "Process stopped" + } + } + + case "restart": + var id string + if err := json.Unmarshal(req.Params, &id); err != nil { + resp.Error = "Invalid params" + } else { + if err := pm.RestartProcess(id); err != nil { + resp.Error = err.Error() + } else { + resp.Result = "Process restarted" + } + } + + case "list": + resp.Result = pm.ListProcesses() + + case "status": + var id string + if err := json.Unmarshal(req.Params, &id); err != nil { + resp.Error = "Invalid params" + } else { + status, err := pm.GetProcessStatus(id) + if err != nil { + resp.Error = err.Error() + } else { + resp.Result = status + } + } + + default: + resp.Error = "Method not found" + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(resp) + }) + + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", daemonPort)) + if err != nil { + log.Fatalf("Failed to start RPC server: %v", err) + } + + log.Printf("RPC server listening on %s", listener.Addr()) + + if err := http.Serve(listener, nil); err != nil { + log.Fatalf("RPC server failed: %v", err) + } +} +``` + +### 4. Web 管理界面 (`daemon/web_ui.go`) + +```go +package main + +import ( + "fmt" + "net/http" + "text/template" +) + +func startWebUI(pm *ProcessManager) { + // 静态文件服务 + fs := http.FileServer(http.Dir("web/static")) + http.Handle("/static/", http.StripPrefix("/static/", fs)) + + // 主页 + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + tmpl, err := template.ParseFiles("web/templates/index.html") + if err != nil { + http.Error(w, "Template error", http.StatusInternalServerError) + return + } + + data := struct { + Processes []ProcessStatus + }{ + Processes: pm.ListProcesses(), + } + + tmpl.Execute(w, data) + }) + + // API端点 + http.HandleFunc("/api/processes", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(pm.ListProcesses()) + }) + + // 启动Web服务器 + port := 8080 + log.Printf("Web UI available at http://localhost:%d", port) + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), nil)) +} +``` + +### 5. 命令行客户端 (`client/pm2-win-cli.go`) + +```go +package main + +import ( + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "os" +) + +type Client struct { + ServerURL string +} + +func NewClient() *Client { + return &Client{ + ServerURL: "http://localhost:9999/rpc", + } +} + +func (c *Client) Call(method string, params interface{}) (interface{}, error) { + request := map[string]interface{}{ + "method": method, + "params": params, + "id": 1, + } + + reqBody, err := json.Marshal(request) + if err != nil { + return nil, err + } + + resp, err := http.Post(c.ServerURL, "application/json", bytes.NewReader(reqBody)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + var rpcResp struct { + Result interface{} `json:"result"` + Error string `json:"error"` + } + + if err := json.NewDecoder(resp.Body).Decode(&rpcResp); err != nil { + return nil, err + } + + if rpcResp.Error != "" { + return nil, fmt.Errorf(rpcResp.Error) + } + + return rpcResp.Result, nil +} + +func main() { + startCmd := flag.NewFlagSet("start", flag.ExitOnError) + startID := startCmd.String("id", "", "Process ID") + startName := startCmd.String("name", "", "Process name") + startCmd := startCmd.String("cmd", "", "Command to execute") + startArgs := startCmd.String("args", "", "Command arguments (comma separated)") + startDir := startCmd.String("dir", "", "Working directory") + startLog := startCmd.String("log", "", "Log file path") + + stopCmd := flag.NewFlagSet("stop", flag.ExitOnError) + stopID := stopCmd.String("id", "", "Process ID") + + restartCmd := flag.NewFlagSet("restart", flag.ExitOnError) + restartID := restartCmd.String("id", "", "Process ID") + + listCmd := flag.NewFlagSet("list", flag.ExitOnError) + + if len(os.Args) < 2 { + fmt.Println("Usage: pm2-win-cli [options]") + fmt.Println("Commands:") + fmt.Println(" start - Start a new process") + fmt.Println(" stop - Stop a process") + fmt.Println(" restart - Restart a process") + fmt.Println(" list - List all processes") + os.Exit(1) + } + + client := NewClient() + + switch os.Args[1] { + case "start": + startCmd.Parse(os.Args[2:]) + if *startID == "" || *startCmd == "" { + fmt.Println("ID and command are required") + os.Exit(1) + } + + args := []string{} + if *startArgs != "" { + args = strings.Split(*startArgs, ",") + } + + config := map[string]interface{}{ + "id": *startID, + "name": *startName, + "command": *startCmd, + "args": args, + "working_dir": *startDir, + "log_file": *startLog, + "restart": true, + "max_restarts": 10, + } + + result, err := client.Call("start", config) + if err != nil { + log.Fatal(err) + } + fmt.Println(result) + + case "stop": + stopCmd.Parse(os.Args[2:]) + if *stopID == "" { + fmt.Println("ID is required") + os.Exit(1) + } + + result, err := client.Call("stop", *stopID) + if err != nil { + log.Fatal(err) + } + fmt.Println(result) + + case "restart": + restartCmd.Parse(os.Args[2:]) + if *restartID == "" { + fmt.Println("ID is required") + os.Exit(1) + } + + result, err := client.Call("restart", *restartID) + if err != nil { + log.Fatal(err) + } + fmt.Println(result) + + case "list": + listCmd.Parse(os.Args[2:]) + result, err := client.Call("list", nil) + if err != nil { + log.Fatal(err) + } + + processes := result.([]interface{}) + fmt.Println("ID\tName\tStatus\tPID\tRestarts\tUptime") + for _, p := range processes { + proc := p.(map[string]interface{}) + fmt.Printf("%s\t%s\t%s\t%d\t%d\t%s\n", + proc["id"], proc["name"], proc["status"], + int(proc["pid"].(float64)), int(proc["restarts"].(float64)), + proc["uptime"]) + } + + default: + fmt.Println("Unknown command:", os.Args[1]) + os.Exit(1) + } +} +``` + +## 系统功能详解 + +### 1. 守护进程 (`daemon/main.go`) + +- **服务管理**: + - `installService()`: 将守护进程安装为 Windows 服务 + - `uninstallService()`: 卸载服务 + - `runService()`: 作为 Windows 服务运行 + - `runConsoleMode()`: 控制台模式运行(调试用) + +- **核心功能**: + - `startDaemon()`: 启动守护主逻辑 + - `serviceHandler`: Windows 服务处理接口实现 + +### 2. 进程管理器 (`daemon/process_manager.go`) + +- **数据结构**: + - `ProcessConfig`: 进程配置(ID、命令、参数、日志文件等) + - `ProcessStatus`: 进程状态(PID、状态、重启次数等) + - `Process`: 被守护的进程实例 + - `ProcessManager`: 管理所有进程的中央控制器 + +- **核心方法**: + - `StartProcess()`: 根据配置启动新进程 + - `StopProcess()`: 停止指定进程 + - `RestartProcess()`: 重启指定进程 + - `ListProcesses()`: 列出所有进程状态 + - `StartMonitor()`: 监控进程状态并自动重启 + - `Run()`: 进程运行逻辑(包含自动重启机制) + - `Stop()`: 停止进程(优雅终止) + +### 3. RPC 服务器 (`daemon/rpc_server.go`) + +- **通信协议**: + - 基于 HTTP/JSON 的简单 RPC 协议 + - 支持的方法: start, stop, restart, list, status + +- **请求处理**: + - 解析客户端请求 + - 调用对应的进程管理方法 + - 返回结果或错误信息 + +### 4. Web 管理界面 (`daemon/web_ui.go`) + +- **功能组件**: + - 静态文件服务: 提供 CSS/JS 资源 + - 主页模板: 显示进程列表 + - API 端点: 提供进程数据接口 + +- **用户界面**: + - 进程列表视图 + - 启动/停止/重启按钮 + - 实时状态更新 + +### 5. 命令行客户端 (`client/pm2-win-cli.go`) + +- **命令支持**: + - `start`: 启动新进程 + - `stop`: 停止进程 + - `restart`: 重启进程 + - `list`: 列出所有进程 + +- **参数解析**: + - 使用 `flag` 包解析命令行参数 + - 验证必要参数 + - 构造 RPC 请求 + +## 使用示例 + +### 1. 安装守护进程服务 + +```powershell +# 编译守护进程 +go build -o pm2-win-daemon.exe daemon/main.go + +# 安装服务 +pm2-win-daemon.exe -install +``` + +### 2. 启动服务 + +```powershell +Start-Service -Name PM2WinDaemon +``` + +### 3. 使用命令行客户端 + +```powershell +# 编译客户端 +go build -o pm2-win-cli.exe client/pm2-win-cli.go + +# 启动 Node.js 应用 +pm2-win-cli.exe start -id myapp -name "My Node App" -cmd "node" -args "app.js" -dir "C:\myapp" -log "C:\logs\myapp.log" + +# 列出所有进程 +pm2-win-cli.exe list + +# 停止进程 +pm2-win-cli.exe stop -id myapp + +# 重启进程 +pm2-win-cli.exe restart -id myapp +``` + +### 4. 访问 Web 界面 + +打开浏览器访问: `http://localhost:8080` + +## 生产环境增强建议 + +1. **安全加固**: + - 添加 RPC 认证 + - 使用 HTTPS + - 限制访问 IP + +2. **监控集成**: + - 添加 Prometheus 指标端点 + - 集成 Windows 事件日志 + - 实现健康检查 API + +3. **高级功能**: + - 进程资源限制(CPU/内存) + - 配置文件管理 + - 日志轮转 + - 集群模式支持 + +4. **用户界面增强**: + - 实时日志查看器 + - 性能图表 + - 配置文件编辑器 + +5. **部署优化**: + - 制作安装程序 + - 添加自动更新功能 + - 提供系统托盘图标 + +这个简易的 PM2 式进程守护系统提供了核心的进程管理功能,包括启动、停止、重启、监控和自动重启。通过命令行客户端和 Web 界面,用户可以方便地管理后台进程。系统设计为 Windows 服务运行,确保长期稳定工作,适合在 Windows 服务器上部署各种后台应用和服务。 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6ec5338 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module go-process-guardian + +go 1.24.3 + +require golang.org/x/sys v0.34.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a197cce --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/main.go b/main.go new file mode 100644 index 0000000..4e4259a --- /dev/null +++ b/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "flag" + "fmt" + "os" +) + +var ( + v = flag.Bool("v", false, "查看版本") + start = flag.String("start", "", "启动文件地址") + name = flag.String("name", "", "名称") + restart = flag.String("restart", "", "重启") + stop = flag.String("stop", "", "停止") +) + +func main() { + flag.Parse() + // 获取当前进程信息 + fmt.Println(os.Getpid()) + fmt.Println(os.Getppid()) + if *v { + fmt.Println("1.0.1") + } + if *start != "" { + fmt.Println("启动:", *start) + } + if *name != "" { + fmt.Println("名称:", *name) + } + if *restart != "" { + fmt.Println("重启:", *restart) + } + if *stop != "" { + fmt.Println("停止:", *stop) + } +}