润达医疗 AI electron & startup 重构实践

作者:korey zhao
背景介绍
当前的良医启动器 startup 更新机制和守护机制不够完善,随着良医助手 electron 版本迭代,医院客户的需求愈发增多,所以需要稳定、可靠且智能易扩展的启动机制,以确保用户体验的连贯性和应用服务的持续性。在这一需求背景下,重构了startup启动器,通过模块化设计实现了自动更新、自我更新、进程守护、消息通信等核心功能,有效解决了良医助手 electron 中面临的版本管理、稳定性保障等问题,提供了可靠的运行环境。
旧版架构的技术痛点
在重构之前,良医助手采用了混合技术栈(nodejs+python)实现,特别是将更新功能集成在基于 Electron 的主应用内部,导致了一系列架构和技术问题:
- 技术栈混杂与架构松散:混合使用
Node.js和Python,技术栈割裂,功能模块间高耦合低内聚,导致维护成本高、扩展困难 - 模块化严重不足:更新模块深度依赖
Electron环境,违反单一职责原则,形成功能耦合,既无法独立运行/测试,也不能实现独立部署和跨项目复用 - 系统健壮性缺陷:缺乏有效的异常处理机制和日志监控体系,局部故障易引发系统级崩溃,问题排查困难
- 用户体验不完善:更新过程缺乏必要的交互设计和状态可视化,导致用户感知度和过程可控性不足
- 通信机制不稳定:进程模型与网络服务的特殊耦合固有冲突,操作系统限制后台进程的网络活动等,导致通信不稳定
- 功能逻辑缺失导致效率极低:
startup只实现了良医electron客户端的更新功能,并未实现自我更新,导致每次发版,实施团队依旧需要人肉部署到医院每一台机子上
架构重构的意义
针对旧版架构的问题,新版设计做出了重要的调整:将更新功能从 Electron 应用中完全解耦,移至独立的 Python 启动器中,进行统一的技术栈整合,并进行模块化设计,升级优化了自我更新、进程守护、消息通信、用户界面交互、日志记录等核心功能。

新版启动器主要功能与技术解析
启动器提供以下核心功能:
1. 软件自动更新
新版特点:
- 核心功能:支持启动检查&定时检查、静默更新&用户交互式更新版本
- 统一技术栈:从混合
JavaScript/Python转为纯Python实现,便于维护 - 可视化进度:新版添加了详细的进度条展示,让用户清楚了解更新进度
- 错误处理:从简单的
try-catch转为多层次错误处理和恢复机制及日志记录 - 效率提升:无需操作医生每台电脑,实施团队只需更新服务端最新版本即可
更新功能不再依赖于 Electron 应用,可以在应用关闭的情况下完成更新;资源占用更低,避免了 Electron 应用的内存开销。
# updater.py - 更新流程核心代码
def execute_update(self, silent: bool = False) -> None:
"""执行更新"""
if not self.check_version():
return
if not silent:
user_confirmed = self._ui_manager.show_confirm_dialog(
title="软件更新",
message="已检测到新版本,是否立即更新?\n更新完成后系统会自动启动",
icon='info'
)
if not user_confirmed:
self.logger.info("用户取消更新")
self._set_app_flag('appUpdate')
return
progress_window = None if silent else self._ui_manager.create_progress_window()
try:
# 步骤1:下载更新文件
if progress_window:
def update_step1():
progress_window.update_progress(1, 7, "正在下载更新文件...")
self._ui_manager.execute_in_ui_thread(update_step1)
self.download_updates()
# ... 更多步骤如解压、替换文件等 ...
except Exception as e:
self.logger.error(f"更新过程失败: {e}")
# ... 错误处理 ...1.1 启动器自我更新机制
除了对良医助手主程序的更新外,新版启动器还增加了自身的更新能力,实现完整的自我迭代机制。
新版特点:
- 核心功能:双版本检查机制,支持自我静默自动更新
- 自举更新:启动器能够完成自身替换,解决了"谁来更新
startup"的循环依赖问题 - 脚本机制:通过生成批处理脚本实现正在运行程序的替换
- 独立版本控制:主程序与启动器版本分离,支持独立的发布周期

# updater.py - 启动器自我更新核心代码
def execute_startup_updates(self):
"""执行启动器和更新器自身的更新"""
self.execute_update(silent=True, startup=True)
return False
def execute_update(self, silent: bool = False, startup: bool = False) -> None:
"""执行更新"""
if not self.check_version(startup):
if not startup:
self.execute_startup_updates()
return
# ... 更新流程代码 ...
# 步骤3:如果是startup,则走另一个流程
if startup:
self._update_updater()
return启动器自我更新的核心在于通过生成外部脚本来实现对正在运行的程序的替换。这解决了无法替换正在运行文件的操作系统限制问题:
def _update_updater(self):
"""更新startup"""
try:
self.logger.info("更新startup...")
script_path = self._generate_script()
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 启动外部批处理脚本执行更新
subprocess.Popen(
f"\"{script_path}\"",
shell=True,
startupinfo=startupinfo,
creationflags=subprocess.CREATE_NO_WINDOW,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
except Exception as e:
self.logger.error(f"更新startup失败: {e}")
raise批处理脚本完成以下步骤:
- 终止正在运行的启动器进程
- 复制新的配置文件
- 替换启动器可执行文件
- 清理临时文件
- 启动新版本
- 清理自身(脚本自删除)
这种机制确保了启动器可以在不依赖外部程序的情况下完成自我更新,提高了整个系统的自主性和完整性。
2. 进程守护与自动重启
新版特点:
- 核心功能:基于线程的异步监控,支持检查间隔可配置,支持自动重启,持续检测良医进程状态
- 架构升级:从简单循环检查到基于类的面向对象设计,更适合复杂的良医进程管理
- 资源利用优化:使用
Event对象进行线程控制,减少CPU占用 - 正确性提升 从监控进程固定数量到只监控
electron主进程+electron内部容错处理,保障正确性
# guardian.py - 进程守护核心代码
class ProcessGuardian:
"""进程守护类"""
def __init__(self, process_name: str, start_func: Callable):
"""初始化进程守护"""
self.process_name = process_name.lower()
self.start_func = start_func
self.logger = get_logger()
self.stop_event = threading.Event()
self._monitor_thread = None
def _monitor(self):
"""监控进程并在需要时重启"""
self.logger.info(f"开始监控进程: {self.process_name}")
while not self.stop_event.is_set():
# 检查进程状态
if not is_process_running(self.process_name):
self.logger.warning(f"进程 {self.process_name} 已停止,正在重启...")
try:
self.start_func()
except Exception as e:
self.logger.error(f"重启失败: {e}")
# 等待下一次检查或停止信号
if self.stop_event.wait(timeout=CHECK_INTERVAL):
break # 收到停止信号,立即退出循环进程简介

- 主进程(
Main Process):通常只有一个,名称是electron.exe或你的应用名。 - 渲染进程(
Renderer Process):多个同名进程,内存占用较高。 GPU进程(GPU Process):名称可能包含--type=gpu-process。- 其他辅助进程:如
--type=utility(插件进程)。
3. Windows 开机自启动
新版特点:
- 独立的注册表操作模块,代码结构清晰
- 支持旧版启动项清理,确保升级兼容性
- 配置驱动的开关控制,灵活满足用户需求
# registry.py - Windows开机自启动核心代码
def setup_autostart():
"""设置开机自启动"""
try:
# 读取配置文件
with open(CONFIG_PATH, 'r', encoding='utf-8') as file:
content = json.load(file)
# 设置注册表
key = winreg.OpenKey(REGISTRY_KEY, REGISTRY_SUB_KEY, 0, winreg.KEY_WRITE)
_clean_registry_entries(key)
if content['autostart']:
winreg.SetValueEx(key, APP_REGISTRY_NAME, 0, winreg.REG_SZ, str(EXE_PATH))
logger.info("自启动设置成功")
winreg.CloseKey(key)
except Exception as e:
logger.error(f"设置自启动失败: {e}")4. 消息监控与进程间通信
新版特点:
- 核心功能:由
http通信机制升级为基于文件通信机制,让python启动器可以与任何应用,任何语言通信 - 事件注册:采用事件注册机制,实现模块间松耦合通信,方便统一管理回调事件的处理
- 性能优化:独立线程处理,不影响主程序性能;使用
st_mtime检测文件变化,比轮询读取内容更高效地捕获Electron应用发送的信号

# message.py - 进程间通信核心代码
class MessageMonitor:
"""消息监控类"""
def __init__(self):
"""初始化消息监控器"""
self.logger = get_logger()
self.cache_path = Path(PY_CACHE_PATH)
self.stop_event = threading.Event()
self._monitor_thread = None
self._last_modified_time = 0
# 事件处理器
self._handlers = {}
self._init_cache_file()
def _monitor(self):
"""监控缓存文件并处理变化"""
while not self.stop_event.is_set():
try:
current_mtime = self.cache_path.stat().st_mtime
if current_mtime > self._last_modified_time:
self._last_modified_time = current_mtime
self._process_cache_file()
except Exception as e:
self.logger.error(f"监控py-cache.json缓存文件过程中出错: {e}")
# 等待下一次检查或停止信号
if self.stop_event.wait(timeout=CHECK_INTERVAL):
break
def register_handler(self, event_type: str, handler: Callable[[Dict[str, Any]], None]) -> None:
"""注册事件处理函数"""
self._handlers[event_type] = handler
self.logger.info(f"已注册事件处理器: {event_type}")5. 用户界面交互
在旧版中几乎没有用户界面交互,只依赖于 Electron 的简单对话框。
新版特点:
- 核心功能:添加了更新进度条及多种提示弹窗类型等视觉反馈元素,大幅提升用户体验
- 任务队列:支持从非
UI线程安全调用UI操作 - 线程管理:基于线程和队列的
UI设计,解决了Python中tkinter的线程安全问题

# ui.py - 用户界面交互核心代码
class UIThreadManager:
"""UI线程管理器
负责创建和管理UI线程、处理UI任务队列
提供在UI线程中执行任务的接口
"""
def __init__(self):
self._stop_event = Event()
self._ui_queue = queue.Queue()
self._ui_thread = Thread(target=self._ui_thread_main, daemon=True)
self._ui_ready = Event()
self._root = None
self._dialog_manager = None
# 启动UI线程
self._ui_thread.start()
def execute_in_ui_thread(self, task, *args, **kwargs):
"""在UI线程中执行任务"""
if self._stop_event.is_set():
logger.warning("UI线程已停止,无法执行任务")
return None
result_container = [None]
result_event = Event()
# 将任务放入队列
self._ui_queue.put((task, args, kwargs, result_event, result_container))
# 等待任务完成
if not result_event.wait(timeout=300):
logger.error("UI任务执行超时")
# ... 错误处理 ...
return result_container[0]6. 日志记录
新版特点:
- 核心功能:基于单例模式的日志类,支持文件和控制台双输出,支持滚动写入
- 功能增强:格式化日志,支持多种输出方式和详细的日志级别
- 集成度高:支持
nodejs、python、shell的日志统一,便于全局管理和问题定位
# logger.py - 日志记录核心代码
def init_logger():
"""初始化日志系统"""
global _logger
if _logger is not None:
return _logger
os.makedirs(os.path.dirname(_log_path), exist_ok=True)
_logger = logging.getLogger('ma')
_logger.setLevel(logging.INFO)
for handler in _logger.handlers[:]:
handler.close()
_logger.removeHandler(handler)
formatter = logging.Formatter('P: %(asctime)s - %(levelname)s - %(message)s')
# 使用限制行数的日志处理器
file_handler = _create_rotating_handler(_log_path, max_lines=_MAX_LINES, encoding='utf-8')
file_handler.setFormatter(formatter)
_logger.addHandler(file_handler)
# 控制台处理器
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
_logger.addHandler(console_handler)
_logger.info('日志系统初始化成功,日志文件路径:%s(最多保存%d行)', _log_path, _MAX_LINES)
return _logger技术亮点与性能优化
新版启动器在架构设计和实现细节上采用了多项现代化软件工程实践,带来了显著的性能提升和稳定性改进。
1. 模块化设计与架构优化
采用高度模块化的设计思路,将核心功能拆分为多个独立模块,每个模块职责明确。
# 新版模块化设计示例
class ProcessGuardian:
"""进程守护类,负责监控和管理指定进程"""
def __init__(self, process_name, start_func):
self.process_name = process_name
self.start_func = start_func
self.monitor_thread = None
self.stop_event = Event()
self.logger = get_logger()
def start(self):
"""启动监控线程"""
if self.monitor_thread and self.monitor_thread.is_alive():
return
self.stop_event.clear()
self.monitor_thread = Thread(target=self._monitor, daemon=True)
self.monitor_thread.start()
self.logger.info(f"进程守护已启动: {self.process_name}")相比旧版代码的扁平结构,新版实现带来以下优势:
- 代码结构清晰,便于维护和扩展
- 模块之间通过接口解耦,降低耦合度
- 便于单元测试,提高代码质量
- 支持按需加载功能,优化资源使用
2. 线程管理优化
新版设计在线程管理上进行了全面优化:
- 采用线程池和事件驱动模式,替代简单的循环和睡眠操作
- 实现了基于队列的线程间通信,避免线程竞争问题
- 优雅的线程终止机制,防止资源泄漏
def execute_in_ui_thread(self, task, *args, **kwargs):
"""在UI线程中执行任务,确保线程安全"""
if self._stop_event.is_set():
logger.warning("UI线程已停止,无法执行任务")
return None
result_container = [None]
result_event = Event()
# 将任务放入队列,由UI线程安全执行
self._ui_queue.put((task, args, kwargs, result_event, result_container))
# 等待任务完成
if not result_event.wait(timeout=300):
logger.error("UI任务执行超时")
# 清理队列中的超时任务
try:
self._ui_queue.get_nowait()
self._ui_queue.task_done()
except queue.Empty:
pass
raise TimeoutError("UI任务执行超时")
return result_container[0]3. 全面的错误处理与稳定性保障
新版启动器实现了多层次的错误处理机制,显著提高了系统稳定性。
异常处理机制
- 使用装饰器统一处理异常,简化错误处理代码
- 实现自动恢复流程,从错误状态恢复正常运行
- 用户友好的错误通知,提高使用体验
def safe_call(error_msg: str = "操作失败"):
"""装饰器:安全调用函数,捕获并记录异常"""
def decorator(func: Callable) -> Callable:
def wrapper(*args, **kwargs):
try:
raise_exception = kwargs.pop('raise_exception', False)
result = func(*args, **kwargs)
return result
except Exception as e:
logger.error(f"{error_msg}: {e}")
if raise_exception:
raise
return None
return wrapper
return decorator稳定性增强措施
- 守护进程监控:互为监控的双向守护机制,确保系统可靠运行
- 超时处理:所有网络和
I/O操作设置合理超时,避免程序挂起 - 日志系统:完善的多级别日志记录,便于问题分析
- 优雅降级:支持部分功能失效时的系统降级运行
4. 统一的路径管理与配置热加载
新版启动器通过统一的路径管理和配置热加载机制,提高了系统的灵活性和健壮性。
路径管理
def get_cache_dir() -> Path:
"""获取缓存目录,确保跨平台兼容"""
if sys.platform == 'win32':
base_path = Path(os.environ.get('LOCALAPPDATA', '')) / 'liangyi'
else:
base_path = Path.home() / 'Library/Caches/liangyi'
# 确保缓存目录存在
base_path.mkdir(parents=True, exist_ok=True)
return base_path配置热加载
def _check_reload(self):
"""检查配置文件是否已修改,如有则重新加载"""
try:
mtime = self.config_path.stat().st_mtime
if mtime > self.last_modified:
self._load_config()
self.last_modified = mtime
self.logger.info("检测到配置文件变更,已重新加载")
except Exception as e:
self.logger.error(f"检查配置文件变更时出错: {e}")优势对比:
- 跨平台兼容:自动适配不同操作系统的路径规范
- 动态配置:支持运行时更新配置,无需重启应用
- 错误预防:自动创建必要目录,避免路径相关错误
Electron 应用重构成果
在 Python 启动器重构的同时,也对 Electron 应用进行了全面优化,使其更加专注于业务功能,同时提高了稳定性和可维护性。新的 Electron 应用结构如下:
electron/
├── src/ # 源代码目录
│ ├── config.js # 统一配置管理
│ ├── logger.js # 日志系统
│ ├── main.js # 主进程入口
│ ├── monitor.js # 进程监控模块
│ ├── preload.js # 预加载脚本
│ ├── utils.js # 工具函数集合
│ └── watcher.js # 文件监听与事件处理
├── scripts/ # 构建和部署脚本
├── server/ # 本地开发服务器
└── static/ # 静态资源文件主要改进包括:
1. 基于文件系统的进程间通信
从旧版基于 HTTP 服务的通信方式转向基于文件系统的通信机制,实现了与 Python 启动器的高效、稳定通信。
// watcher.js - 文件监听与事件处理
const initWatcher = async (windowHandlers) => {
handlerCache = windowHandlers;
try {
await createOrClearCacheFile();
const watcher = chokidar.watch(cacheFilePath, {
persistent: true,
ignoreInitial: true,
});
logger.info('node-watcher监听器已创建');
watcher.on('change', (path) => {
logger.info(`node缓存文件已更改: ${path}`);
handleCacheFileChange();
});
watcher.on('error', (error) => {
logger.error('node监听文件时发生错误:', error);
});
} catch (error) {
logger.error(`初始化监听器时发生错误: ${error}`);
}
};新版特点:
- 规避网络层风险,移除了
HTTP服务依赖,无端口冲突、跨域限制 - 提供了统一的事件处理机制,便于扩展新功能,容错能力提升
- 符合
Electron进程模型,更低的资源占用和更高的通信可靠性
2. 双向进程守护架构
实现了 Electron 应用与 Python 启动器互相监控的双向守护机制,显著提高了系统稳定性。
// monitor.js - 进程监控模块
const initMonitor = (renderer_send) => {
rendererSend = renderer_send;
start();
return {
start,
stop,
restart,
isRunning: () => isRunning,
};
};
const checkProcess = async () =>
new Promise((resolve) => {
const command = 'tasklist /FI "IMAGENAME eq liangyi_startup.exe" /NH';
exec(command, (error, stdout) => {
if (error) {
logger.error('[Monitor] 进程检查错误:', error);
resolve(false);
return;
}
const isProcessRunning = stdout.toLowerCase().includes('liangyi_startup.exe');
resolve(isProcessRunning);
});
});
const start = () => {
if (monitorInterval) {
clearInterval(monitorInterval);
}
isRunning = true;
logger.info('[Monitor] 进程监控已启动');
monitorInterval = setInterval(async () => {
try {
const isProcessRunning = await checkProcess();
if (!isProcessRunning) {
logger.warn('警告:liangyi_startup.exe 已停止运行,请手动启动!');
rendererSend('警告:liangyi_startup.exe 已停止运行,请手动启动!', 'notice');
}
} catch (error) {
logger.error('[Monitor] 监控进程出错:', error);
restart();
}
}, checkInterval);
};新版特点:
- 形成"互为守护"的架构:
Python监控Electron进程,Electron监控Python进程 - 提高了系统整体稳定性,任何一方崩溃都能被另一方检测并处理
- 用户友好的错误提示,在
Python启动器异常时通知用户

3. 全面的错误监听与恢复机制
增加了对各种可能的异常情况的监听和处理机制,大幅提高了应用的健壮性。
// main.js - 错误处理部分
const gracefulRestart = async (error, type) => {
try {
logger.error(`应用错误,即将重启 [${type}]:`, error);
if (mainWindow && !mainWindow.isDestroyed()) {
renderer_send(
{
type,
message: '应用即将重启以确保稳定运行',
},
'app-error',
);
}
setTimeout(() => {
app.relaunch();
app.exit(0);
}, 1000);
} catch (restartError) {
logger.error('重启失败,强制重启', restartError);
app.relaunch();
app.exit(1);
}
};
// 渲染进程异常监听
app.on('render-process-gone', (event, webContents, details) => {
gracefulRestart(new Error(`渲染进程崩溃: ${details.reason}`), 'renderer_crashed');
});新版特点:
- 捕获并处理各种级别的异常,包括渲染进程、
GPU进程和主进程异常 - 实现优雅重启机制,确保数据不丢失
- 使用类型化的错误处理,便于问题定位和分析
4. 统一的配置管理
增加了配置管理模块,实现了统一读取和多客户端配置支持。
// config.js - 配置管理
const BASE_CONFIG = {
url: configParse.url || 'https://test.huihaohealth.com/ma/doctor/pc2/#/desktop-welcome',
dev: configParse.dev || false,
logger: configParse.logger || false,
autostart: configParse.autostart || false,
interface_width: configParse.interface_width || 630,
};
// 慧检配置
const HUIJIAN_CONFIG = {
width: 800,
height: 600,
skipTaskbar: false,
resizable: true,
x: null,
y: null,
needCreateTray: true,
trayTips: '慧检系统',
trayIcon: path.join(__dirname, '../icon.ico'),
trayMenuLabel: '退出慧检',
needSetAlwaysOnTop: false,
};
// 齐鲁配置
const QILU_CONFIG = {};
const coustomConfig = (() => {
if (configParse.is_huijian) {
return HUIJIAN_CONFIG;
}
if (configParse.is_qilu) {
return QILU_CONFIG;
}
})();
const CONFIG = {
...BASE_CONFIG,
...coustomConfig,
};新版特点:
- 配置从应用逻辑中解耦,便于维护和修改
- 支持多客户定制,可根据客户类型加载不同配置
- 提供默认值保障,增强代码健壮性
5. 增强的日志系统与自动化构建
实现了统一的日志记录机制和更强大的构建系统。
// logger.js - 日志系统
const logger = require('electron-log');
const path = require('path');
const util = require('./utils');
logger.transports.file.resolvePathFn = () => path.join(util.getRootPath(), 'log.log');
logger.transports.file.level = 'debug';
logger.transports.file.maxSize = 1002430;
logger.transports.file.format = 'N: [{y}-{m}-{d} {h}:{i}:{s}.{ms}] - {level} - {scope} {text}';新版特点:
- 双向输出:同时记录到文件和控制台
- 与
Python启动器日志共享,便于整体问题排查 - 格式化日志内容,增强可读性
6. 构建系统
总结与展望
良医启动器 startup 通过全面重构,实现了从混合技术栈的松散架构向统一 Python 技术栈的模块化设计转变,成功打造了一个高效、稳定的应用系统,同时大幅度提高实施团队部署效率。
架构升级
- 模块化设计:代码结构清晰,扩展性强,维护成本低
- 资源优化:降低
CPU和内存占用,提升运行效率 - 功能完善:更新、守护、通信等核心功能显著增强
- 稳定可靠:全面的错误处理机制确保多线程系统稳定运行
- 完整生命周期:从启动、运行到更新全过程实现专业化管理
- 效率提升:实施团队仅仅只需将新版本安装包部署到服务端即可
架构解耦价值
- 责任分离:
Electron专注业务UI,Python负责系统服务 - 双向守护:互为监控的保障机制大幅提高系统稳定性
- 独立升级:启动器可独立更新,提供灵活的版本管理策略
- 统一通信:基于文件系统的通信方式简单可靠,易于扩展
- 共享日志:全链路日志记录,便于问题定位与分析

展望
虽然当前版本的启动器已经能够满足基本需求,但仍有改进空间:
- 更完善的更新机制:支持基于补丁的增量更新,减少网络流量和更新时间,仅下载变更文件。
- 远程控制能力:添加简单的
HTTP接口,支持远程触发更新、查看日志等操作,实现远程管理和监控。 - 数据统计分析:添加匿名的使用统计功能,收集系统运行状态和错误信息,为产品迭代提供数据依据。
- 扩展插件系统:设计插件接口和加载机制,支持第三方功能扩展和功能模块的动态加载。
- 渲染进程心跳:实现主进程与渲染进程的双向通信机制,确保渲染进程正常运行。
未来将通过持续优化,为医院和医生提供更加稳定、便捷的 MA 良医客户端使用体验。