Skip to content

Commit a2544f2

Browse files
committed
fix(installer+tray): 开始菜单卸载指向uninstall.exe + 停止后启动/重启 + 启动后浏览器时序
1 parent 864324b commit a2544f2

3 files changed

Lines changed: 62 additions & 7 deletions

File tree

installer/create-shortcuts.ps1

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,19 @@ if ($Actions -contains 'Start') {
169169
}
170170

171171
# 2. 卸载:托盘菜单接管了"停止"/"重启",开始菜单不再列停止入口
172+
# NSIS 的 WriteUninstaller 生成的是 uninstall.exe(见 installer.nsi -Post
173+
# 段),而不是 uninstall.bat。早期版本误指向 uninstall.bat,导致开始菜单
174+
# 的 Uninstall.lnk 点击后提示找不到文件(控制面板走的是注册表
175+
# UninstallString,指向 uninstall.exe,所以控制面板卸载正常)。
172176
if ($Actions -contains 'Uninstall') {
173177
New-Shortcut `
174178
-Path (Join-Path $smAppDir '卸载.lnk') `
175-
-Target (Join-Path $InstallDir 'uninstall.bat') `
179+
-Target (Join-Path $InstallDir 'uninstall.exe') `
176180
-WorkingDirectory $InstallDir `
177181
-Description '卸载 QMT Gateway'
178182
New-Shortcut `
179183
-Path (Join-Path $smAppDir 'Uninstall.lnk') `
180-
-Target (Join-Path $InstallDir 'uninstall.bat') `
184+
-Target (Join-Path $InstallDir 'uninstall.exe') `
181185
-WorkingDirectory $InstallDir `
182186
-Description 'Uninstall QMT Gateway'
183187
}

installer/start.bat

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,30 @@ if not errorlevel 1 (
2121
exit /b 0
2222
)
2323

24-
start "" "http://localhost:%GATEWAY_PORT%"
24+
:: 端口没响应——可能是 gateway 没启动(用户刚从托盘"停止"过,.port 已被
25+
:: atexit 删掉),也可能是上一个实例崩了。先在后台启动一个"等待就绪并打开
26+
:: 浏览器"的任务,再在前台启动 gateway——这样 gateway 的日志留在当前控制
27+
:: 台窗口,浏览器在 gateway 监听端口后自动弹出。旧实现是在启动 gateway
28+
:: 之前就 start 浏览器,结果浏览器先于 gateway 就绪弹出,显示"无法连接"。
29+
::
30+
:: .port 可能因为 8130 被占用而指向 8131-8139,所以等待结束后重新读取,
31+
:: 而不是沿用启动前的值。
32+
start "" /B powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^
33+
"$portFile='%QMT_GATEWAY_HOME%\.port';" ^
34+
"$w=0;" ^
35+
"while($w -lt 15){" ^
36+
"if(Test-Path -LiteralPath $portFile){$r=Get-Content -LiteralPath $portFile -ErrorAction SilentlyContinue; if($r -match '^\d+$'){break}}" ^
37+
"Start-Sleep -Seconds 1; $w++" ^
38+
"};" ^
39+
"$port=8130; if(Test-Path -LiteralPath $portFile){$r=Get-Content -LiteralPath $portFile -ErrorAction SilentlyContinue; if($r -match '^\d+$'){$port=[int]$r}};" ^
40+
"$w=0;" ^
41+
"while($w -lt 30){" ^
42+
"try{$c=New-Object Net.Sockets.TcpClient; $c.Connect('127.0.0.1',$port); $c.Close(); start \"\" \"http://localhost:$port\"; exit 0}catch{}; Start-Sleep -Seconds 1; $w++" ^
43+
"}"
2544
python\python.exe -m qmt_gateway
2645
set "RC=%ERRORLEVEL%"
27-
:: 退出码 2 = 另一个实例已在跑(被单实例锁拒绝)。浏览器已经打开,
28-
:: 不需要提示用户出错。
46+
:: 退出码 2 = 另一个实例已在跑(被单实例锁拒绝)。浏览器由上面的等待逻辑
47+
:: 打开,不需要提示用户出错。
2948
if not "%RC%"=="2" (
3049
if not "%RC%"=="0" (
3150
echo qmt-gateway 启动失败,退出码 %RC%

qmt_gateway/tray.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
右键菜单:
55
66
- 打开管理界面 → 浏览器打开 http://localhost:<port>(.port 文件)
7+
- 启动 Gateway → 停止后重新拉起 gateway(若已在跑则打开浏览器)
78
- 重启 Gateway → 杀掉旧 PID,等端口空闲,再启动新的 gateway
8-
- 停止 Gateway → 杀掉 gateway 进程(托盘自身保留,可手动重启
9+
- 停止 Gateway → 杀掉 gateway 进程(托盘自身保留,可手动启动/重启
910
- 退出托盘 → 关闭托盘(gateway 继续运行)
1011
1112
入口由 ``__main__`` 在 lifespan startup 阶段以独立子进程拉起,确保
@@ -234,10 +235,21 @@ def _open_browser(icon: pystray.Icon, item: pystray.MenuItem) -> None:
234235

235236

236237
def _restart_gateway(icon: pystray.Icon, item: pystray.MenuItem) -> None:
237-
port = _read_port()
238238
logger.info("用户触发重启")
239239
# 先清过期锁(gateway 已被外部 kill -9 / 任务管理器结束的场景)
240240
_clear_stale_lock()
241+
# "停止"之后 gateway 已死、.lock 已被 atexit 删掉,_read_pid() 返回 None。
242+
# 此时 _kill_gateway() 返回 False,旧实现会短路掉 _spawn_gateway()——
243+
# 结果用户点"重启"什么也没发生。这里改成:没有正在跑的进程就直接拉起。
244+
# 注意不调 _wait_for_port_free:停止后 .port 已被删,_read_port() 退回
245+
# 8130,但 8130 可能被别的应用占用——此时等它空闲会误判为"端口占用"。
246+
# gateway 内部的 find_available_port 会自己探测可用端口,托盘不需要干预。
247+
pid = _read_pid()
248+
if pid is None:
249+
logger.info("未发现正在运行的 gateway,直接启动新实例")
250+
_spawn_gateway()
251+
return
252+
port = _read_port()
241253
if _kill_gateway() and _wait_for_port_free(port, timeout=10.0):
242254
_spawn_gateway()
243255
else:
@@ -255,9 +267,29 @@ def _quit_tray(icon: pystray.Icon, item: pystray.MenuItem) -> None:
255267
icon.stop()
256268

257269

270+
def _start_gateway(icon: pystray.Icon, item: pystray.MenuItem) -> None:
271+
"""启动 gateway(停止后用来重新拉起服务)。
272+
273+
与 _restart_gateway 的区别:这里假定当前没有正在运行的 gateway,
274+
不做 kill,直接 spawn。gateway 内部的 find_available_port 会自己
275+
探测可用端口,托盘不需要预先等端口空闲(否则 8130 被别的应用占用时
276+
会误判为"无法启动")。
277+
"""
278+
logger.info("用户触发启动")
279+
_clear_stale_lock()
280+
pid = _read_pid()
281+
if pid is not None and _is_pid_alive_windows(pid):
282+
# 已经在跑——直接打开浏览器,不重复拉起
283+
logger.info("gateway 已在运行,打开浏览器")
284+
_open_browser(icon, item)
285+
return
286+
_spawn_gateway()
287+
288+
258289
def _build_menu() -> pystray.Menu:
259290
return pystray.Menu(
260291
pystray.MenuItem("打开管理界面", _open_browser, default=True),
292+
pystray.MenuItem("启动 QMT Gateway", _start_gateway),
261293
pystray.MenuItem("重启 QMT Gateway", _restart_gateway),
262294
pystray.MenuItem("停止 QMT Gateway", _stop_gateway),
263295
pystray.Menu.SEPARATOR,

0 commit comments

Comments
 (0)