diff --git a/build.txt b/build.txt index 03ea99b..ec5ff79 100644 --- a/build.txt +++ b/build.txt @@ -1,5 +1,6 @@ cd src pyinstaller --onefile --add-data "install/Common.py:install" .\install\InstallMariaDB.py -pyinstaller --onefile --add-data "install/Common.py:install" --add-data "datas/nssm.exe:datas" --add-data "datas/electromagnetic.jar:datas" --add-data "datas/init.sql:datas" .\install\InstallOrUpgradeComacDB.py +pyinstaller --onefile --add-data "install/Common.py:install" --add-data "datas/electromagnetic.jar:datas" --add-data "datas/init.sql:datas" --hidden-import win32timezone .\install\InstallOrUpgradeComacDB.py pyinstaller --onefile .\install\Uninstall.py -pyinstaller --onefile .\install\SetFile.py \ No newline at end of file +pyinstaller --onefile .\install\SetFile.py +pyinstaller --onefile --add-data "install/Common.py:install" --hidden-import win32timezone .\install\ManagerService.py \ No newline at end of file diff --git a/src/datas/electromagnetic.jar b/src/datas/electromagnetic.jar index c08f488..b298110 100644 Binary files a/src/datas/electromagnetic.jar and b/src/datas/electromagnetic.jar differ diff --git a/src/install/InstallOrUpgradeComacDB.py b/src/install/InstallOrUpgradeComacDB.py index 415662a..d6456f7 100644 --- a/src/install/InstallOrUpgradeComacDB.py +++ b/src/install/InstallOrUpgradeComacDB.py @@ -1,135 +1,72 @@ -from datetime import datetime - +import shutil from Common import * +current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( + os.path.abspath(__file__)) +manage_service_exe = os.path.join(current_dir, "ManagerService.exe") +app_log_dir = os.path.join(current_dir, "logs") +sql_path = get_resource_path(os.path.join("datas", "init.sql")) +jar_path = get_resource_path(os.path.join("datas", "electromagnetic.jar")) -class InstallOrUpgradeComacDb: +def set_sql(): + with open(sql_path, 'r', encoding='utf-8') as file: + lines = file.readlines() + modified = False + new_lines = [] + for line in lines: + if 'CREATE TABLE' in line and 'IF NOT EXISTS' not in line: + new_line = line.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS ') + new_lines.append(new_line) + modified = True + else: + new_lines.append(line) - def __init__(self, current_dir): + # 如果有修改,则写回文件 + if modified: + with open(sql_path, 'w', encoding='utf-8') as file: + file.writelines(new_lines) - self.run_dir = current_dir - self.app_log_dir = os.path.join(current_dir, "logs") - self.comac_db_running_port = 12396 - self.jar_path = get_resource_path(os.path.join("datas", "electromagnetic.jar")) - self.url = f'http://127.0.0.1:{self.comac_db_running_port}/index' - self.service_name = "ComacDatabase" - self.service_description = "数据库组件服务" - # 路径配置 - self.datas_dir = get_resource_path(os.path.join('datas')) - # NSSM可执行文件路径 - self.nssm_exe = get_resource_path(os.path.join('datas', 'nssm.exe')) - # Java和JAR文件路径 - self.java_path = os.path.join(current_dir, "jdk", "bin", "java.exe") - self.sql_path = get_resource_path(os.path.join("datas", "init.sql")) - pass + command2 = fr'{current_dir}\mariadb\bin\mysql --no-defaults -u root -p{mariadb_passowrd} -P {mariadb_port} {mariadb_init_schema} < {sql_path}' + with os.popen(command2) as stream: + res2 = stream.read() + logger.info(res2) + logger.info("sql设置成功") + pass - def start_comac_db(self): - delete_old_files(self.app_log_dir, 2) - self.__remove_pre_service() - self.__set_sql() - self.__register_and_start_service() - logger.info("运行完成,10秒钟后自动退出") - time.sleep(10) - pass +def set_jar(): + dest_jar_path = os.path.join(current_dir, "electromagnetic.jar") + shutil.copy(jar_path, dest_jar_path) + logger.info("jar设置成功") + pass - def __set_sql(self): - self.__set_replace() - logger.info("设置数据") - command2 = fr'{self.run_dir}\mariadb\bin\mysql --no-defaults -u root -p{mariadb_passowrd} -P {mariadb_port} {mariadb_init_schema} < {self.sql_path}' - with os.popen(command2) as stream: - res2 = stream.read() - logger.info(res2) - pass +def update_service(cmd): + command = [manage_service_exe, cmd] + result = subprocess.run(command, capture_output=True, text=True) - def __register_and_start_service(self): - # 服务配置 - # 构建NSSM安装命令 - formatted_time = datetime.now().strftime("%Y%m%d%H%M%S") - cmd = [ - str(self.nssm_exe), - "install", - self.service_name, - self.java_path, - f"-jar {self.jar_path} " - f"--logging.file.path={self.app_log_dir} " - f"--logging.file.name={self.app_log_dir}/app_{formatted_time}.log " - f"--winPrefix={self.run_dir} " - f"--spring.datasource.username={mariadb_user} " - f"--spring.datasource.password={mariadb_passowrd} " - f"--server.port={comac_db_port} " - f"--spring.datasource.url=jdbc:mariadb://127.0.0.1:{mariadb_port}/{mariadb_init_schema}?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&sslModel=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true" - ] - - # 执行安装命令 - logger.info("Installing service...") - result = subprocess.run(cmd, capture_output=True, text=True) - - if result.returncode != 0: - logger.info("Error installing service:") - logger.info(result.stderr) - return False - - logger.info("Service installed successfully!") - - # 设置服务显示名称和描述 - subprocess.run([str(self.nssm_exe), "set", self.service_name, "DisplayName", self.service_name]) - subprocess.run([str(self.nssm_exe), "set", self.service_name, "Description", self.service_description]) - - # 设置启动目录(可选,但推荐) - # subprocess.run([str(nssm_exe), "set", service_name, "AppDirectory", str(datas_dir)]) - - # 设置启动类型为自动(可选) - subprocess.run([str(self.nssm_exe), "set", self.service_name, "Start", "SERVICE_AUTO_START"]) - - logger.info("Service configuration completed.") - subprocess.run(rf"net start {self.service_name}") - time.sleep(10) - - def __remove_pre_service(self): - exist = check_service_exist(self.service_name) - if not exist: - return - logger.info("清理历史服务") - stop_command = ["sc", "stop", self.service_name] - delete_command = ["sc", "delete", self.service_name] - res1 = subprocess.run(stop_command, capture_output=True, text=True) - logger.info(res1.stdout) - res2 = subprocess.run(delete_command, capture_output=True, text=True) - logger.info(res2.stdout) - time.sleep(5) - pass - - def __set_replace(self): - with open(self.sql_path, 'r', encoding='utf-8') as file: - lines = file.readlines() - modified = False - new_lines = [] - for line in lines: - if 'CREATE TABLE' in line and 'IF NOT EXISTS' not in line: - new_line = line.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS ') - new_lines.append(new_line) - modified = True - else: - new_lines.append(line) - - # 如果有修改,则写回文件 - if modified: - with open(self.sql_path, 'w', encoding='utf-8') as file: - file.writelines(new_lines) + if result.returncode != 0: + logger.warning(f"操作失败,cmd is {cmd}") + else: + logger.info(f"操作成功,cmd is {cmd}") + pass if __name__ == '__main__': - - current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__)) - log_dir = os.path.join(current_dir, "logs") - ensure_dir(log_dir) + ensure_dir(app_log_dir) logger.add( - sink=os.path.join(log_dir, "InstallComacDB_{time}.log"), # 文件路径模板 + sink=os.path.join(app_log_dir, "InstallOrUpgradeComacDB.log"), # 文件路径模板 rotation="10 MB", # 文件大小达到10MB时轮转 retention="1 days", # 保留最近1天的日志 compression="zip", # 压缩旧日志节省空间 enqueue=True, # 线程安全写入 format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" # 自定义格式 ) - InstallOrUpgradeComacDb(current_dir).start_comac_db() + + set_sql() + set_jar() + + update_service("stop") + update_service("remove") + update_service("install") + update_service("start") + pass \ No newline at end of file diff --git a/src/install/ManagerService.py b/src/install/ManagerService.py new file mode 100644 index 0000000..fb293c9 --- /dev/null +++ b/src/install/ManagerService.py @@ -0,0 +1,173 @@ +from datetime import datetime + +import servicemanager +import win32event +import win32service +import win32serviceutil + +from Common import * + +current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( + os.path.abspath(__file__)) +service_name = "ComacDatabase" +service_description = "Comac数据库组件服务" +java_exe = os.path.join(current_dir, "jdk", "bin", "java.exe") +jar_path = os.path.join(current_dir, "electromagnetic.jar") +app_log_dir = os.path.join(current_dir, "logs") + +class ComacDBService(win32serviceutil.ServiceFramework): + _svc_name_ = service_name # 服务名称 + _svc_display_name_ = service_name # 显示名称 + _svc_description_ = service_description # 服务描述 + + def __init__(self, args): + win32serviceutil.ServiceFramework.__init__(self, args) + self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) + self.process = None + self.service_ready = False + + def SvcDoRun(self): + try: + # 报告服务正在启动 + self.ReportServiceStatus(win32service.SERVICE_START_PENDING, waitHint=10000) + formatted_time = datetime.now().strftime("%Y%m%d%H%M%S") + cmd = [ + java_exe, + f"-jar", + jar_path, + f"--logging.file.path={app_log_dir}", + f"--logging.file.name={app_log_dir}/app_{formatted_time}.log", + f"--winPrefix={current_dir}", + f"--spring.datasource.username={mariadb_user}", + f"--spring.datasource.password={mariadb_passowrd}", + f"--server.port={comac_db_port}", + f"--spring.datasource.url=jdbc:mariadb://127.0.0.1:{mariadb_port}/{mariadb_init_schema}?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&sslModel=true&serverTimezone=GMT%2B8&allowMultiQueries=true&rewriteBatchedStatements=true" + ] + # 添加完整的错误处理 + env = os.environ.copy() + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startupinfo.wShowWindow = 0 # 隐藏窗口 + + # 启动Java进程(使用subprocess.Popen捕获输出) + try: + self.process = subprocess.Popen( + cmd, + cwd=app_log_dir, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW, + env=env, + startupinfo=startupinfo + ) + logger.info(f"Java process started with PID: {self.process.pid}") + + # 检查进程是否存活 + time.sleep(2) # 给Java启动时间 + if self.process.poll() is not None: + # 读取输出以诊断问题 + output, _ = self.process.communicate(timeout=2) + output = output.decode('gbk', errors='ignore') + logger.error(f"Java process exited prematurely. Output: {output}") + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + return + except Exception as e: + logger.error(f"Failed to start Java process: {str(e)}") + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + return + + # 服务完全启动 + self.ReportServiceStatus(win32service.SERVICE_RUNNING) + self.service_ready = True + logger.info("Service fully started and running") + + # 启动线程监控Java进程 + self.monitor_java_process() + + # 等待停止信号 + win32event.WaitForSingleObject(self.hWaitStop, win32event.INFINITE) + + except Exception as e: + logger.error(f"Service failed: {str(e)}", exc_info=True) + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + finally: + # 确保清理资源 + if self.process and self.process.poll() is None: + self.process.terminate() + self.process = None + + def monitor_java_process(self): + """在后台线程中监控Java进程""" + import threading + + def monitor(): + try: + if self.process: + # 持续读取输出 + while self.process.poll() is None and self.service_ready: + line = self.process.stdout.readline() + if line: + line = line.decode('gbk', errors='ignore').strip() + logger.info(f"JAVA: {line}") + else: + time.sleep(0.5) + + # 检查进程退出原因 + if self.process.poll() is not None: + logger.warning(f"Java process exited with code: {self.process.poll()}") + except Exception as e: + logger.error(f"Monitor thread error: {str(e)}") + + # 启动监控线程 + threading.Thread(target=monitor, daemon=True).start() + + def SvcStop(self): + # 报告服务正在停止 + self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) + + # 停止Java进程(如果存在) + if self.process: + try: + # 更优雅的关闭方式 + logger.info("Terminating Java process...") + self.process.terminate() + + # 等待最多10秒 + for _ in range(10): + if self.process.poll() is not None: + break + time.sleep(1) + else: + logger.warning("Java process did not exit, using force kill") + self.process.kill() + except Exception as e: + logger.error(f"Error stopping Java process: {str(e)}") + + # 通知主线程停止 + win32event.SetEvent(self.hWaitStop) + self.service_ready = False + + # 报告服务已停止 + self.ReportServiceStatus(win32service.SERVICE_STOPPED) + logger.info("Service stopped successfully") + pass + +if __name__ == '__main__': + + ensure_dir(app_log_dir) + logger.add( + sink=os.path.join(app_log_dir, "ManagerService.log"), # 文件路径模板 + rotation="10 MB", # 文件大小达到10MB时轮转 + retention="1 days", # 保留最近1天的日志 + compression="zip", # 压缩旧日志节省空间 + enqueue=True, # 线程安全写入 + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" # 自定义格式 + ) + + if len(sys.argv) == 1: + servicemanager.Initialize() + servicemanager.PrepareToHostSingle(ComacDBService) + servicemanager.StartServiceCtrlDispatcher() + else: + err = win32serviceutil.HandleCommandLine(ComacDBService) \ No newline at end of file