diff --git a/build.txt b/build.txt index f2523af..f06dd26 100644 --- a/build.txt +++ b/build.txt @@ -1,6 +1,9 @@ cd src -pyinstaller --onefile --add-data "install/Common.py:install" .\install\InstallMariaDB.py -pyinstaller --onefile --add-data "install/Common.py:install" --add-data "datas/electromagnetic.jar:datas" --add-data "datas/init.sql:datas" --add-data "datas/ManagerService.exe:datas" --hidden-import win32timezone .\install\InstallOrUpgradeComacDB.py +pyinstaller --onefile --add-data "common/Common.py:common" --hidden-import loguru .\install\InstallMariaDB.py +pyinstaller --onefile --add-data "common/Common.py:common" --add-data "datas/electromagnetic.jar:datas" --add-data "datas/init.sql:datas" --add-data "datas/ManagerService.exe:datas" --hidden-import loguru --hidden-import win32timezone .\install\InstallOrUpgradeComacDB.py pyinstaller --onefile .\install\Uninstall.py -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 +pyinstaller --onefile --add-data "common/Common.py:common" --hidden-import loguru --hidden-import win32timezone .\install\ManagerService.py + +数据库备份 +pyinstaller --onefile --add-data "common/Common.py:common" --add-data "datas/ComacDBBackupService.exe:datas" --add-data "datas/electromagnetic-backup.jar:datas" --hidden-import win32timezone --hidden-import loguru .\backup\InstallOrUpgradeComacDBBackup.py +pyinstaller --onefile --add-data "common/Common.py:common" --hidden-import win32timezone --hidden-import loguru .\backup\ComacDBBackupService.py diff --git a/src/backup/ComacDBBackupService.py b/src/backup/ComacDBBackupService.py new file mode 100644 index 0000000..d40c058 --- /dev/null +++ b/src/backup/ComacDBBackupService.py @@ -0,0 +1,192 @@ +import json +import os +import sys +from datetime import datetime +import subprocess +import servicemanager +import win32event +import win32service +import win32serviceutil +import threading +import time + +from loguru import logger + +from common.Common import ensure_dir, create_file_if_not_exist + +current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( + os.path.abspath(__file__)) +service_name = "ComacDatabaseBackup" +service_description = "Comac数据库备份组件服务" +java_exe = os.path.join(current_dir, "jdk", "bin", "java.exe") +jar_path = os.path.join(current_dir, "electromagnetic-backup.jar") +app_log_dir = os.path.join(current_dir, "logs") +application_properties_path = current_dir + "/application.json" + +class ComacDBBackupService(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: + + # 解析配置文件 + with open(application_properties_path, 'r', encoding='utf-8') as f: + config = json.load(f) + + # 报告服务正在启动 + 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"--ele.backup.winPrefix={current_dir}", + f"--file.enc.passwd=adknfhkj87654knd", + f"--logging.file.path={app_log_dir}", + f"--logging.file.name={app_log_dir}/app_{formatted_time}.log", + f"--server.port={config.get('app.run.port', 12491)}", + f"--winPrefix={current_dir}", + ] + # 添加完整的错误处理 + 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进程""" + 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) + create_file_if_not_exist(application_properties_path, content="{}") + logger.add( + sink=os.path.join(app_log_dir, "ManagerService.log"), + rotation="10 MB", + retention="1 days", + compression="zip", + enqueue=True, + format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}" + ) + + if len(sys.argv) == 1: + servicemanager.Initialize() + servicemanager.PrepareToHostSingle(ComacDBBackupService) + servicemanager.StartServiceCtrlDispatcher() + else: + # 处理 install/update/remove 等命令 + win32serviceutil.HandleCommandLine(ComacDBBackupService) + if "install" in sys.argv: + win32serviceutil.ChangeServiceConfig( + pythonClassString=win32serviceutil.GetServiceClassString(ComacDBBackupService), + serviceName=service_name, + startType=win32service.SERVICE_AUTO_START, + ) diff --git a/src/backup/InstallOrUpgradeComacDBBackup.py b/src/backup/InstallOrUpgradeComacDBBackup.py new file mode 100644 index 0000000..590e3d3 --- /dev/null +++ b/src/backup/InstallOrUpgradeComacDBBackup.py @@ -0,0 +1,67 @@ +import os +import shutil +import subprocess +import sys + +from loguru import logger + +from common.Common import get_resource_path, ensure_dir + +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, "ComacDBBackupService.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-backup.jar")) +manage_service_path = get_resource_path(os.path.join("datas", "ComacDBBackupService.exe")) + + +def set_jar(): + dest_jar_path = os.path.join(current_dir, "electromagnetic-backup.jar") + shutil.copy(jar_path, dest_jar_path) + logger.info("jar设置成功") + pass + + +def update_service(cmd): + + if not os.path.exists(manage_service_exe): + logger.warning("manage service path not exist") + return + + command = [manage_service_exe, cmd] + result = subprocess.run(command, capture_output=True, text=True) + + if result.returncode != 0: + logger.info(result.stdout) + logger.warning(f"操作失败,cmd is {cmd}") + logger.error(result.stderr) + else: + logger.info(f"操作成功,cmd is {cmd}") + pass + +def upgrade_service(): + shutil.copy(manage_service_path, manage_service_exe) + logger.info("service 设置成功") + pass + + +if __name__ == '__main__': + ensure_dir(app_log_dir) + logger.add( + 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}" # 自定义格式 + ) + + update_service("stop") + update_service("remove") + set_jar() + upgrade_service() + update_service("install") + update_service("start") + + pass diff --git a/src/install/Common.py b/src/common/Common.py similarity index 95% rename from src/install/Common.py rename to src/common/Common.py index 7984150..8bf1f33 100644 --- a/src/install/Common.py +++ b/src/common/Common.py @@ -2,6 +2,7 @@ import os import subprocess import sys import time +from pathlib import Path from loguru import logger @@ -119,3 +120,8 @@ def delete_old_files(directory, days=2): logger.info(f"\n操作完成!共删除 {deleted_count} 个日志文件。") except Exception as e: logger.info(f"遍历目录时出错: {str(e)}", file=sys.stderr) + +def create_file_if_not_exist(path : str, content=''): + file_path = Path(path) + if not file_path.exists(): + file_path.write_text(content) \ No newline at end of file diff --git a/src/datas/ComacDBBackupService.exe b/src/datas/ComacDBBackupService.exe new file mode 100644 index 0000000..3232c29 Binary files /dev/null and b/src/datas/ComacDBBackupService.exe differ diff --git a/src/datas/ManagerService.exe b/src/datas/ManagerService.exe index 38b8aed..d189a64 100644 Binary files a/src/datas/ManagerService.exe and b/src/datas/ManagerService.exe differ diff --git a/src/datas/electromagnetic-backup.jar b/src/datas/electromagnetic-backup.jar new file mode 100644 index 0000000..fd8de9d Binary files /dev/null and b/src/datas/electromagnetic-backup.jar differ diff --git a/src/datas/electromagnetic.jar b/src/datas/electromagnetic.jar index 4724d11..a118145 100644 Binary files a/src/datas/electromagnetic.jar and b/src/datas/electromagnetic.jar differ diff --git a/src/dist/InstallOrUpgradeComacDBBackup.exe b/src/dist/InstallOrUpgradeComacDBBackup.exe new file mode 100644 index 0000000..2e3e885 Binary files /dev/null and b/src/dist/InstallOrUpgradeComacDBBackup.exe differ diff --git a/src/install/InstallMariaDB.py b/src/install/InstallMariaDB.py index 52d41e7..1a6d370 100644 --- a/src/install/InstallMariaDB.py +++ b/src/install/InstallMariaDB.py @@ -1,4 +1,4 @@ -from Common import * +from common.Common import * current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( os.path.abspath(__file__)) diff --git a/src/install/InstallOrUpgradeComacDB.py b/src/install/InstallOrUpgradeComacDB.py index d345b7f..702dfb0 100644 --- a/src/install/InstallOrUpgradeComacDB.py +++ b/src/install/InstallOrUpgradeComacDB.py @@ -1,6 +1,6 @@ import shutil -from Common import * +from common.Common import * current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( os.path.abspath(__file__)) diff --git a/src/install/ManagerService.py b/src/install/ManagerService.py index 6281ac3..c0e30a1 100644 --- a/src/install/ManagerService.py +++ b/src/install/ManagerService.py @@ -1,3 +1,4 @@ +import json from datetime import datetime import servicemanager @@ -5,7 +6,7 @@ import win32event import win32service import win32serviceutil import threading -from Common import * +from common.Common import * current_dir = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname( os.path.abspath(__file__)) @@ -14,7 +15,7 @@ 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") - +application_properties_path = current_dir + "/application.json" class ComacDBService(win32serviceutil.ServiceFramework): _svc_name_ = service_name # 服务名称 @@ -29,6 +30,9 @@ class ComacDBService(win32serviceutil.ServiceFramework): def SvcDoRun(self): try: + #解析配置文件 + with open(application_properties_path, 'r', encoding='utf-8') as f: + config = json.load(f) # 报告服务正在启动 self.ReportServiceStatus(win32service.SERVICE_START_PENDING, waitHint=10000) formatted_time = datetime.now().strftime("%Y%m%d%H%M%S") @@ -44,6 +48,8 @@ class ComacDBService(win32serviceutil.ServiceFramework): f"--spring.datasource.username={mariadb_user}", f"--spring.datasource.password={mariadb_passowrd}", f"--server.port={comac_db_port}", + f"--backup.remote.host={config.get('backup.remote.host', '127.0.0.1')}", + f"--backup.remote.port={config.get('backup.remote.port', 12491)}", 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" ] # 添加完整的错误处理 @@ -158,6 +164,7 @@ class ComacDBService(win32serviceutil.ServiceFramework): if __name__ == '__main__': ensure_dir(app_log_dir) + create_file_if_not_exist(application_properties_path, content="{}") logger.add( sink=os.path.join(app_log_dir, "ManagerService.log"), rotation="10 MB",