Files
go-process-guardian/dev.md
2025-07-12 13:35:47 +08:00

1068 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 使用 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 <command> [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 服务器上部署各种后台应用和服务。