1 import os
2 import subprocess
3 import sys
4 import time
5
6 import psutil
7 import win32event
8 import win32service
9 import win32serviceutil
10
11 PROJECT_NAME = "PROJECT_NAME"
12 STARTUP_FILE_NAME = "main.py"
13
14 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
15 python_dir = sys.executable.replace("pythonservice.exe", "python.exe")
16 script_dir = os.path.join(BASE_DIR, "logs", "log.log")
17
18 COMMAND = [python_dir, os.path.join(BASE_DIR, STARTUP_FILE_NAME)]
19 HEARTBEAT_FILE = os.path.join(BASE_DIR, "heartbeat.txt")
20 TIMEOUT = 300 # 5 分钟无心跳更新,认为卡死
21
22
23 class Service(win32serviceutil.ServiceFramework):
24 _svc_name_ = PROJECT_NAME
25 _svc_display_name_ = PROJECT_NAME
26 _svc_description_ = PROJECT_NAME
27
28 def __init__(self, args):
29 super().__init__(args)
30 self.stop_event = win32event.CreateEvent(None, 0, 0, None)
31 self.pid = None
32 self.running = True
33
34 def start_process(self):
35 """启动进程并返回 PID"""
36 proc = subprocess.Popen(COMMAND, creationflags=subprocess.CREATE_NEW_CONSOLE)
37 self.pid = proc.pid
38 print(f"启动进程 PID: {self.pid}")
39
40 def is_window_open(self):
41 """检查进程窗口是否打开"""
42 if not self.pid:
43 return False
44 try:
45 process = psutil.Process(self.pid)
46 return process.is_running()
47 except psutil.NoSuchProcess:
48 return False
49
50 def kill_process(self):
51 """强制关闭进程"""
52 if not self.pid:
53 return
54 try:
55 process = psutil.Process(self.pid)
56 parent = process.parent()
57 process.terminate()
58 time.sleep(1)
59 if parent and "cmd.exe" in parent.name().lower():
60 parent.terminate()
61 time.sleep(2)
62 except psutil.NoSuchProcess:
63 pass
64
65 def get_last_heartbeat(self):
66 """获取上次心跳时间"""
67 if not os.path.exists(HEARTBEAT_FILE):
68 return 0
69 try:
70 with open(HEARTBEAT_FILE, "r") as f:
71 return float(f.read().strip())
72 except:
73 return 0
74
75 def SvcStop(self):
76 """停止服务"""
77 self.running = False
78 self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
79 self.kill_process()
80 win32event.SetEvent(self.stop_event)
81
82 def SvcDoRun(self):
83 """服务运行逻辑"""
84 self.start_process()
85 while self.running:
86 time.sleep(15)
87
88 if self.pid is None and self.running:
89 self.start_process()
90 continue
91
92 if not self.is_window_open() and self.running:
93 print("进程窗口被关闭,重启...")
94 self.start_process()
95 continue
96
97 if not psutil.pid_exists(self.pid) and self.running:
98 print("进程崩溃,重启...")
99 self.kill_process()
100 self.start_process()
101 continue
102
103 # last_heartbeat = self.get_last_heartbeat()
104 # print("上次心跳:" + str(last_heartbeat))
105 # if time.time() - last_heartbeat > TIMEOUT and self.running:
106 # print("进程卡死,重启...")
107 # self.kill_process()
108 # self.start_process()
109
110
111 if __name__ == "__main__":
112 import servicemanager
113
114 if len(sys.argv) == 1:
115 try:
116 evtsrc_dll = os.path.abspath(servicemanager.__file__)
117 # 如果修改过名字,名字要统一
118 servicemanager.PrepareToHostSingle(Service)
119 # 如果修改过名字,名字要统一
120 servicemanager.Initialize('Service', evtsrc_dll)
121 servicemanager.StartServiceCtrlDispatcher()
122 except win32service.error as details:
123 import winerror
124
125 if details == winerror.ERROR_FAILED_SERVICE_CONTROLLER_CONNECT:
126 win32serviceutil.usage()
127 else:
128 # 如果修改过名字,名字要统一
129 win32serviceutil.HandleCommandLine(Service)