数据库备份实现

This commit is contained in:
chenxudong 2025-11-03 16:59:13 +08:00
parent cefc65f4f8
commit bbb64e94bc
12 changed files with 283 additions and 8 deletions

View File

@ -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
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

View File

@ -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,
)

View File

@ -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

View File

@ -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)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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__))

View File

@ -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__))

View File

@ -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",