From 926b85bf8daa0ab6ae99dfd7f8751223e82387d6 Mon Sep 17 00:00:00 2001
From: hongawen <83944980@qq.com>
Date: Thu, 2 Apr 2026 20:51:19 +0800
Subject: [PATCH] =?UTF-8?q?C=E7=AB=AF=E6=89=93=E5=8C=85=E4=BF=AE=E5=A4=8D?=
=?UTF-8?q?=E4=B8=8D=E8=83=BD=E5=9C=A8=E4=B8=AD=E6=96=87=E8=B7=AF=E5=BE=84?=
=?UTF-8?q?=E4=B8=8B=E5=90=AF=E5=8A=A8=E7=9A=84=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build/NPQS9100.bat | 18 --
build/README-升级回滚.txt | 39 ++--
build/app.manifest | 29 ---
build/build.bat | 3 +-
build/clean-and-build.bat | 3 +-
build/diagnose-startup.bat | 12 +-
build/rollback.bat | 51 +----
build/upgrade.bat | 198 ++++++++++--------
build/upgrade/README.txt | 17 +-
cmd/builder.json | 2 +-
doc/NPQS-9100绿色包完整指南.md | 171 ++++++++-------
doc/开发者指南_自己动手修改.md | 12 +-
doc/打包前检查清单.md | 13 +-
doc/管理员权限说明.md | 63 ++----
electron/preload/lifecycle.js | 38 ++--
scripts/config-generator.js | 19 +-
scripts/java-runner.js | 2 +-
...ce-manager.js => mysql-process-manager.js} | 116 +++++++++-
scripts/path-utils.js | 41 ++++
19 files changed, 474 insertions(+), 373 deletions(-)
delete mode 100644 build/NPQS9100.bat
delete mode 100644 build/app.manifest
rename scripts/{mysql-service-manager.js => mysql-process-manager.js} (74%)
create mode 100644 scripts/path-utils.js
diff --git a/build/NPQS9100.bat b/build/NPQS9100.bat
deleted file mode 100644
index 7ee6675..0000000
--- a/build/NPQS9100.bat
+++ /dev/null
@@ -1,18 +0,0 @@
-@echo off
-chcp 65001 >nul
-
-REM 获取当前批处理文件所在目录
-cd /d "%~dp0"
-
-REM 检查是否有管理员权限
-net session >nul 2>&1
-if %errorlevel% == 0 (
- REM 已有管理员权限,直接启动
- start "" "NPQS9100.exe"
- exit
-) else (
- REM 没有管理员权限,使用 PowerShell 以管理员身份启动(隐藏窗口)
- powershell -WindowStyle Hidden -Command "Start-Process '%~dp0NPQS9100.exe' -Verb RunAs"
- exit
-)
-
diff --git a/build/README-升级回滚.txt b/build/README-升级回滚.txt
index c2dfac0..337e7d3 100644
--- a/build/README-升级回滚.txt
+++ b/build/README-升级回滚.txt
@@ -4,27 +4,34 @@ NPQS-9100 升级与回滚说明
一、升级步骤(超简单!)
----------------------------------------
-1. 双击 upgrade.bat(首次会自动创建 upgrade 文件夹)
-2. 将升级文件放入 upgrade/ 目录:
- - app.asar (前端升级包 - 文件)
- - app.asar.unpacked\ (前端升级包 - 文件夹)
- - entrance.jar (后端升级包)
-3. 再次双击 upgrade.bat 开始升级
-4. 等待完成后重启应用
+1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
+2. 双击 upgrade.bat(首次会自动创建 upgrade 文件夹)
+3. 将升级文件放入 upgrade/ 目录:
+ - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
+ - entrance.jar (后端升级包)
+4. 再次双击 upgrade.bat 开始升级
+5. 等待完成后重启应用
+
+补充说明:
+- 可以只升级后端
+- 可以只升级前端,但前端升级时必须同时提供:
+ app.asar
+ app.asar.unpacked\
+- 数据库不由 upgrade.bat 自动备份,请务必提前手动导出 SQL
二、回滚步骤
----------------------------------------
如果升级后出现问题:
-1. 双击 rollback.bat
-2. 选择是否回滚数据库(谨慎!)
-3. 等待完成后重启应用
+1. 双击 rollback.bat(仅回滚前后端程序文件)
+2. 等待完成后重启应用
+3. 如需恢复数据库,请手动执行升级前导出的 SQL
三、重要提示
----------------------------------------
-✓ 升级前会自动备份到 backup/ 目录
-✓ 数据库会自动备份到 mysql/data_backup/
+✓ 升级前会自动备份前后端程序文件到 backup/ 目录
✓ 升级日志保存在 logs/upgrade.log
✓ 多次升级时,backup/ 保存最后一次升级前的版本
+✓ 数据库备份与恢复由人工处理,不再由脚本自动执行
四、紧急情况
----------------------------------------
@@ -32,15 +39,15 @@ NPQS-9100 升级与回滚说明
【恢复前端】
copy /Y backup\app.asar resources\app.asar
+rmdir /s /q resources\app.asar.unpacked
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
【恢复后端】
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
-【恢复数据库】(慎用!)
-xcopy mysql\data_backup mysql\data\ /E /I /Y
+【恢复数据库】
+请使用 Navicat 或其他工具执行升级前导出的 SQL 备份
========================================
-详细文档请参考:doc/绿色包升级指南.md
+如需完整技术文档,请联系交付方提供开发文档。
========================================
-
diff --git a/build/app.manifest b/build/app.manifest
deleted file mode 100644
index d45cca9..0000000
--- a/build/app.manifest
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
- NPQS-9100自动检测平台
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/build/build.bat b/build/build.bat
index 3556ad8..932924f 100644
--- a/build/build.bat
+++ b/build/build.bat
@@ -52,7 +52,8 @@ echo.
echo ========================================
echo 打包完成!
-echo 输出目录: out\win-unpacked\
+echo 最终交付目录: out\NPQS-9100\
+echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ========================================
echo.
pause
diff --git a/build/clean-and-build.bat b/build/clean-and-build.bat
index 39a3020..3c1eaf7 100644
--- a/build/clean-and-build.bat
+++ b/build/clean-and-build.bat
@@ -63,7 +63,8 @@ echo.
echo ========================================
echo ✓ 打包完成!
-echo 输出目录: out\win-unpacked\
+echo 最终交付目录: out\NPQS-9100\
+echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ========================================
echo.
pause
diff --git a/build/diagnose-startup.bat b/build/diagnose-startup.bat
index f02c786..cc70c84 100644
--- a/build/diagnose-startup.bat
+++ b/build/diagnose-startup.bat
@@ -53,13 +53,13 @@ if exist "%LOGDIR%" (
)
echo.
-echo [4] 检查 MySQL 服务...
-sc query mysql9100 >nul 2>&1
-if %errorlevel% equ 0 (
- echo MySQL 服务存在
- sc query mysql9100 | findstr "STATE"
+echo [4] 检查当前运行模式...
+echo 当前版本为绿色包进程模式,不使用 MySQL Windows 服务
+if exist "mysql\my.ini" (
+ echo 检测到 MySQL 配置文件:
+ findstr /i "^port=" "mysql\my.ini"
) else (
- echo MySQL 服务不存在
+ echo 尚未检测到 mysql\my.ini(可能应用还未完整启动)
)
echo.
diff --git a/build/rollback.bat b/build/rollback.bat
index b164571..f966f6f 100644
--- a/build/rollback.bat
+++ b/build/rollback.bat
@@ -1,17 +1,19 @@
@echo off
chcp 65001 >nul
+setlocal
echo ========================================
echo NPQS9100 回滚工具
echo ========================================
echo.
echo 【重要提示】
-echo 本工具用于回滚上次升级的内容
-echo 回滚后将恢复到升级前的状态
+echo 本工具仅回滚前后端程序文件
+echo 数据库不会由本脚本自动恢复
+echo 如需恢复数据库,请手动执行之前导出的 SQL 备份
echo.
pause
echo.
-echo [1/5] 停止 NPQS9100 进程...
+echo [1/4] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 (
echo NPQS9100 已停止
@@ -21,7 +23,7 @@ if %errorlevel% equ 0 (
)
echo.
-echo [2/5] 检查备份文件...
+echo [2/4] 检查备份文件...
set hasBackup=0
if exist backup\app.asar (
@@ -39,23 +41,17 @@ if exist backup\entrance.jar (
set hasBackup=1
)
-if exist mysql\data_backup (
- echo 发现数据库备份
- set hasBackup=1
-)
-
if %hasBackup%==0 (
- echo 未发现任何备份文件!
+ echo 未发现任何前后端备份文件!
echo 无法执行回滚操作
pause
exit /b 1
)
echo.
-echo [3/5] 回滚前端...
+echo [3/4] 回滚前端...
set frontendRollback=0
-REM 回滚 app.asar
if exist backup\app.asar (
echo 正在恢复 app.asar...
copy /Y backup\app.asar resources\app.asar >nul 2>&1
@@ -69,7 +65,6 @@ if exist backup\app.asar (
)
)
-REM 回滚 app.asar.unpacked
if exist backup\app.asar.unpacked (
echo 正在恢复 app.asar.unpacked...
if exist resources\app.asar.unpacked (
@@ -91,7 +86,7 @@ if %frontendRollback%==0 (
)
echo.
-echo [4/5] 回滚后端...
+echo [4/4] 回滚后端...
if exist backup\entrance.jar (
echo 正在恢复 JAR 文件...
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
@@ -107,37 +102,11 @@ if exist backup\entrance.jar (
)
echo.
-echo [5/5] 回滚数据库...
-if exist mysql\data_backup (
- echo 是否回滚数据库?(数据库回滚会丢失升级后的数据!)
- echo [Y] 是 [N] 否
- choice /C YN /N /M "请选择:"
- if errorlevel 2 (
- echo 已跳过数据库回滚
- ) else (
- echo 正在回滚数据库...
- if exist mysql\data (
- rmdir /s /q mysql\data 2>nul
- )
- xcopy mysql\data_backup mysql\data\ /E /I /Y /Q >nul 2>&1
- if %errorlevel% equ 0 (
- echo 数据库已回滚
- ) else (
- echo 数据库回滚失败
- pause
- exit /b 1
- )
- )
-) else (
- echo 无数据库备份,跳过
-)
-echo.
-
echo ========================================
echo 回滚完成!
echo ========================================
echo.
+echo 如需恢复数据库,请手动执行之前导出的 SQL 备份。
echo 您现在可以启动 NPQS9100 了
echo.
pause
-
diff --git a/build/upgrade.bat b/build/upgrade.bat
index ebe79c4..be3301b 100644
--- a/build/upgrade.bat
+++ b/build/upgrade.bat
@@ -1,5 +1,6 @@
@echo off
chcp 65001 >nul
+setlocal
echo ========================================
echo NPQS9100 升级工具
echo ========================================
@@ -11,9 +12,8 @@ if not exist upgrade (
echo 【首次使用】已自动创建 upgrade 目录
echo.
echo 请将升级文件放入 upgrade 目录:
- echo - app.asar (前端升级包 - 文件)
- echo - app.asar.unpacked\ (前端升级包 - 文件夹)
- echo - entrance.jar (后端升级包)
+ echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
+ echo - entrance.jar (后端升级包)
echo.
echo 放置完成后,重新运行本脚本即可升级
echo.
@@ -21,19 +21,51 @@ if not exist upgrade (
exit /b 0
)
-REM 检查是否有升级文件
-set hasUpgrade=0
-if exist upgrade\app.asar set hasUpgrade=1
-if exist upgrade\app.asar.unpacked set hasUpgrade=1
-if exist upgrade\entrance.jar set hasUpgrade=1
+REM 检查升级文件状态
+set "hasFrontendAsar=0"
+set "hasFrontendUnpacked=0"
+set "upgradeFrontend=0"
+set "upgradeBackend=0"
+set "frontendUpgraded=0"
-if %hasUpgrade%==0 (
+if exist upgrade\app.asar set "hasFrontendAsar=1"
+if exist upgrade\app.asar.unpacked set "hasFrontendUnpacked=1"
+if exist upgrade\entrance.jar set "upgradeBackend=1"
+
+if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==1 (
+ set "upgradeFrontend=1"
+)
+
+if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==0 (
+ echo 【错误】前端升级包不完整!
+ echo.
+ echo 当前仅检测到:upgrade\app.asar
+ echo 前端升级必须同时提供以下两个内容:
+ echo - upgrade\app.asar
+ echo - upgrade\app.asar.unpacked\
+ echo.
+ pause
+ exit /b 1
+)
+
+if %hasFrontendAsar%==0 if %hasFrontendUnpacked%==1 (
+ echo 【错误】前端升级包不完整!
+ echo.
+ echo 当前仅检测到:upgrade\app.asar.unpacked\
+ echo 前端升级必须同时提供以下两个内容:
+ echo - upgrade\app.asar
+ echo - upgrade\app.asar.unpacked\
+ echo.
+ pause
+ exit /b 1
+)
+
+if %upgradeFrontend%==0 if %upgradeBackend%==0 (
echo 【提示】upgrade 目录为空!
echo.
echo 请将升级文件放入 upgrade 目录:
- echo - app.asar (前端升级包 - 文件)
- echo - app.asar.unpacked\ (前端升级包 - 文件夹)
- echo - entrance.jar (后端升级包)
+ echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
+ echo - entrance.jar (后端升级包)
echo.
echo 放置完成后,重新运行本脚本即可升级
echo.
@@ -42,19 +74,22 @@ if %hasUpgrade%==0 (
)
echo 【检测到升级文件】
-if exist upgrade\app.asar echo - 前端升级(asar文件): upgrade\app.asar
-if exist upgrade\app.asar.unpacked echo - 前端升级(unpacked文件夹): upgrade\app.asar.unpacked\
-if exist upgrade\entrance.jar echo - 后端升级: upgrade\entrance.jar
+if %upgradeFrontend%==1 (
+ echo - 前端升级: upgrade\app.asar + upgrade\app.asar.unpacked\
+)
+if %upgradeBackend%==1 (
+ echo - 后端升级: upgrade\entrance.jar
+)
echo.
echo 【重要提示】
-echo 1. 升级前会自动备份当前版本
-echo 2. 如升级失败可运行 rollback.bat 回滚
-echo 3. 数据库会自动备份到 mysql\data_backup\
+echo 1. 升级前会自动备份当前前后端程序文件
+echo 2. 如升级失败可运行 rollback.bat 回滚前后端程序文件
+echo 3. 数据库请先使用 Navicat 或其他工具手动导出 SQL 备份
echo.
pause
echo.
-echo [1/6] 停止 NPQS9100 进程...
+echo [1/5] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 (
echo NPQS9100 已停止
@@ -64,63 +99,54 @@ if %errorlevel% equ 0 (
)
echo.
-echo [2/6] 备份当前版本(用于回滚)...
+echo [2/5] 备份当前版本(用于回滚)...
if not exist backup mkdir backup
-REM 备份前端(app.asar 和 app.asar.unpacked)
-if exist resources\app.asar (
- echo 正在备份前端 app.asar...
- if not exist backup mkdir backup
- copy /Y resources\app.asar backup\app.asar >nul 2>&1
- if %errorlevel% equ 0 (
- echo app.asar 备份完成
- ) else (
- echo app.asar 备份失败
+REM 备份前端(仅在执行前端升级时)
+if %upgradeFrontend%==1 (
+ if exist resources\app.asar (
+ echo 正在备份前端 app.asar...
+ copy /Y resources\app.asar backup\app.asar >nul 2>&1
+ if %errorlevel% equ 0 (
+ echo app.asar 备份完成
+ ) else (
+ echo app.asar 备份失败
+ pause
+ exit /b 1
+ )
+ )
+
+ if exist resources\app.asar.unpacked (
+ echo 正在备份前端 app.asar.unpacked...
+ if exist backup\app.asar.unpacked (
+ rmdir /s /q backup\app.asar.unpacked 2>nul
+ )
+ xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
+ if %errorlevel% equ 0 (
+ echo app.asar.unpacked 备份完成
+ ) else (
+ echo app.asar.unpacked 备份失败
+ pause
+ exit /b 1
+ )
)
)
-if exist resources\app.asar.unpacked (
- echo 正在备份前端 app.asar.unpacked...
- if exist backup\app.asar.unpacked (
- rmdir /s /q backup\app.asar.unpacked 2>nul
- )
- xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
- if %errorlevel% equ 0 (
- echo app.asar.unpacked 备份完成
- ) else (
- echo app.asar.unpacked 备份失败
- )
-)
-
-REM 备份后端
-if exist resources\extraResources\java\entrance.jar (
+REM 备份后端(仅在执行后端升级时)
+if %upgradeBackend%==1 if exist resources\extraResources\java\entrance.jar (
echo 正在备份后端...
copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1
if %errorlevel% equ 0 (
echo 后端备份完成
) else (
echo 后端备份失败
- )
-)
-
-REM 备份数据库
-if exist mysql\data (
- echo 正在备份数据库...
- if exist mysql\data_backup (
- rmdir /s /q mysql\data_backup 2>nul
- )
- xcopy mysql\data mysql\data_backup\ /E /I /Y /Q >nul 2>&1
- if %errorlevel% equ 0 (
- echo 数据库备份完成
- ) else (
- echo 数据库备份失败,请手动备份后继续
pause
exit /b 1
)
)
echo.
-echo [3/6] 记录版本信息...
+echo [3/5] 记录版本信息...
if not exist backup\version.txt (
echo 备份时间: %date% %time% > backup\version.txt
echo 升级前版本备份 >> backup\version.txt
@@ -130,29 +156,22 @@ if not exist backup\version.txt (
echo 版本信息已记录
echo.
-echo [4/6] 升级前端...
-set frontendUpgraded=0
-
-REM 升级 app.asar
-if exist upgrade\app.asar (
+echo [4/5] 升级前端...
+if %upgradeFrontend%==1 (
echo 正在替换 app.asar...
copy /Y upgrade\app.asar resources\app.asar >nul 2>&1
if %errorlevel% equ 0 (
echo app.asar 升级完成
- set frontendUpgraded=1
) else (
echo app.asar 升级失败,正在回滚...
if exist backup\app.asar (
copy /Y backup\app.asar resources\app.asar >nul 2>&1
- echo 已回滚到升级前版本
)
+ echo 已回滚到升级前版本
pause
exit /b 1
)
-)
-REM 升级 app.asar.unpacked
-if exist upgrade\app.asar.unpacked (
echo 正在替换 app.asar.unpacked...
if exist resources\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul
@@ -160,26 +179,27 @@ if exist upgrade\app.asar.unpacked (
xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 (
echo app.asar.unpacked 升级完成
- set frontendUpgraded=1
+ set "frontendUpgraded=1"
) else (
- echo app.asar.unpacked 升级失败,正在回滚...
+ echo app.asar.unpacked 升级失败,正在回滚整个前端...
+ if exist backup\app.asar (
+ copy /Y backup\app.asar resources\app.asar >nul 2>&1
+ )
if exist backup\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
- echo 已回滚到升级前版本
)
+ echo 已回滚到升级前版本
pause
exit /b 1
)
-)
-
-if %frontendUpgraded%==0 (
+) else (
echo 无前端升级包,跳过
)
echo.
-echo [5/6] 升级后端...
-if exist upgrade\entrance.jar (
+echo [5/5] 升级后端...
+if %upgradeBackend%==1 (
echo 正在替换 JAR 文件...
copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
if %errorlevel% equ 0 (
@@ -188,8 +208,17 @@ if exist upgrade\entrance.jar (
echo 后端升级失败,正在回滚...
if exist backup\entrance.jar (
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
- echo 已回滚到升级前版本
)
+ if %frontendUpgraded%==1 (
+ if exist backup\app.asar (
+ copy /Y backup\app.asar resources\app.asar >nul 2>&1
+ )
+ if exist backup\app.asar.unpacked (
+ rmdir /s /q resources\app.asar.unpacked 2>nul
+ xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
+ )
+ )
+ echo 已回滚到升级前版本
pause
exit /b 1
)
@@ -198,19 +227,17 @@ if exist upgrade\entrance.jar (
)
echo.
-echo [6/6] 记录升级日志...
if not exist logs mkdir logs
echo ========================================== >> logs\upgrade.log
echo 升级时间: %date% %time% >> logs\upgrade.log
-if exist upgrade\dist (
+if %upgradeFrontend%==1 (
echo 升级内容: 前端 >> logs\upgrade.log
)
-if exist upgrade\entrance.jar (
+if %upgradeBackend%==1 (
echo 升级内容: 后端 >> logs\upgrade.log
)
+echo 数据库处理: 请手动导出/导入 SQL >> logs\upgrade.log
echo ========================================== >> logs\upgrade.log
-echo 升级日志已记录
-echo.
echo ========================================
echo 升级完成!
@@ -218,10 +245,9 @@ echo ========================================
echo.
echo 【提示】
echo 1. 如需回滚,请运行 rollback.bat
-echo 2. 升级文件已使用,可删除 upgrade 目录
+echo 2. 数据库如需恢复,请手动执行之前导出的 SQL
+echo 3. 升级文件已使用,可删除 upgrade 目录
echo.
echo 您现在可以启动 NPQS9100 应用。
echo.
pause
-
-
diff --git a/build/upgrade/README.txt b/build/upgrade/README.txt
index bc1a646..d960f01 100644
--- a/build/upgrade/README.txt
+++ b/build/upgrade/README.txt
@@ -3,18 +3,21 @@
## 使用方法
1. 将升级文件放入此目录:
- - app.asar (前端升级包 - 文件)
- - app.asar.unpacked/ (前端升级包 - 文件夹)
+ - app.asar + app.asar.unpacked/ (前端升级包,必须成套放入)
- entrance.jar (后端升级包)
-2. 双击运行根目录的 upgrade.bat 脚本
+2. 升级前先手动导出数据库 SQL 备份
-3. 升级完成后重启应用
+3. 双击运行根目录的 upgrade.bat 脚本
+
+4. 升级完成后重启应用
## 注意事项
-- 可以只放前端或只放后端,支持单独升级
+- 可以只放后端,或放完整前端包,支持单独升级
+- 如果升级前端,必须同时放入 app.asar 和 app.asar.unpacked/
- 升级前会自动备份到 backup/ 目录
-- 如果升级失败,运行 rollback.bat 可回滚
+- 数据库不由脚本自动备份,请手动导出/导入 SQL
+- 如果升级失败,运行 rollback.bat 可回滚前后端程序文件
-详细说明请参考:README-升级回滚.txt
+详细说明请参考根目录 README-升级回滚.txt
diff --git a/cmd/builder.json b/cmd/builder.json
index 7d2d08c..fdbf057 100644
--- a/cmd/builder.json
+++ b/cmd/builder.json
@@ -73,7 +73,7 @@
],
"win": {
"icon": "build/icons/icon.ico",
- "requestedExecutionLevel": "requireAdministrator",
+ "requestedExecutionLevel": "asInvoker",
"signAndEditExecutable": false,
"verifyUpdateCodeSignature": false,
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
diff --git a/doc/NPQS-9100绿色包完整指南.md b/doc/NPQS-9100绿色包完整指南.md
index 5a246ee..456c4bc 100644
--- a/doc/NPQS-9100绿色包完整指南.md
+++ b/doc/NPQS-9100绿色包完整指南.md
@@ -40,8 +40,8 @@
- 自动更新配置文件
#### **2. 盘符自动识别**
-- 应用在 C 盘 → 数据目录:`C:\NPQS9100_Data\`
-- 应用在 D 盘 → 数据目录:`D:\NPQS9100_Data\`
+- 程序目录为纯英文/ASCII路径时:数据目录位于应用目录内的 `NPQS9100_Data\`
+- 程序目录包含中文或其他非 ASCII 字符时:自动切换到当前盘符根目录下的 `NPQS9100_Runtime\`
- 自动创建所有必要的子目录
#### **3. Loading 界面**
@@ -65,18 +65,21 @@
### 📁 目录结构
+交付口径说明:
+- `electron-builder` 原始输出目录:`out/win-unpacked`
+- `npm run build-w` 脚本最终交付目录:`out/NPQS-9100`
+
```
-win-unpacked/ # 绿色版目录(约 650-800 MB)
+NPQS-9100/ # 绿色版目录(约 650-800 MB)
├── NPQS9100.exe # 主程序(双击运行)⭐
-├── NPQS9100-启动器.bat # 管理员启动器(备用)
├── upgrade.bat # 升级工具 ⭐
├── rollback.bat # 回滚工具 ⭐
-├── uninstall-mysql-service.bat # MySQL 服务卸载工具
├── 使用说明.txt # 使用手册
├── README-升级回滚.txt # 升级说明
├── upgrade/ # 升级包目录(首次使用自动创建)
├── backup/ # 自动备份目录
-│ ├── dist/ # 前端备份
+│ ├── app.asar # 前端主包备份
+│ ├── app.asar.unpacked/ # 前端展开目录备份
│ ├── entrance.jar # 后端备份
│ └── version.txt # 版本记录
├── resources/
@@ -93,13 +96,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB)
├── scripts/ # 启动管理脚本(⚠️ 不要修改)
│ ├── config-generator.js
│ ├── java-runner.js
-│ ├── mysql-service-manager.js
+│ ├── mysql-process-manager.js
│ ├── port-checker.js
│ └── startup-manager.js
├── mysql/ # MySQL 数据库
│ ├── bin/
│ ├── data/ # 数据库数据 ⚠️ 不要动
-│ ├── data_backup/ # 自动备份
│ ├── my.ini # 运行时生成
│ └── README.txt
├── jre/ # Java 运行环境(⚠️ 不要修改)
@@ -110,7 +112,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB)
└── upgrade.log
用户数据目录(首次运行自动创建):
-X:\NPQS9100_Data\ (X 是应用所在盘符)
+- 程序目录为纯英文/ASCII路径时:
+ `应用目录\NPQS9100_Data\`
+- 程序目录包含中文或其他非 ASCII 字符时:
+ `X:\NPQS9100_Runtime\data\` (X 是应用所在盘符)
+
+用户数据目录内包含:
├── logs/ # Spring Boot 日志
├── template/ # 报告模板
├── report/ # 生成的报告
@@ -126,10 +133,8 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 文件 | 功能 | 使用场景 |
|------|------|---------|
| **NPQS9100.exe** | 主程序 | 日常启动 |
-| **NPQS9100-启动器.bat** | 管理员启动器 | 首次运行或权限问题时 |
| **upgrade.bat** | 升级工具 | 收到升级包时使用 |
| **rollback.bat** | 回滚工具 | 升级后出问题时使用 |
-| **uninstall-mysql-service.bat** | MySQL 服务卸载 | 完全卸载应用时使用 |
| **使用说明.txt** | 使用手册 | 遇到问题时参考 |
| **README-升级回滚.txt** | 升级说明 | 升级前阅读 |
@@ -151,9 +156,9 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 路径 | 说明 | 备份建议 |
|------|------|---------|
-| **mysql/data/** | 数据库数据 | ⚠️ 定期备份! |
+| **应用目录\\mysql\\data/** 或 **X:\\NPQS9100_Runtime\\mysql\\data/** | 数据库数据 | ⚠️ 定期备份! |
| **backup/** | 升级备份 | 自动管理,无需手动操作 |
-| **X:\NPQS9100_Data/** | 用户数据目录 | 包含日志、模板、报告 |
+| **应用目录\\NPQS9100_Data/** 或 **X:\\NPQS9100_Runtime\\data/** | 用户数据目录 | 包含日志、模板、报告 |
---
@@ -174,7 +179,7 @@ clean-and-build.bat
3. 构建前端代码(Vue 3)
4. 编译 Electron 主进程代码
5. 复制 MySQL、JRE、Java 资源
-6. 生成 win-unpacked 绿色版目录
+6. 先生成 `out/win-unpacked`,再自动重命名为最终交付目录 `out/NPQS-9100`
**等待时间**:约 5-15 分钟
@@ -183,7 +188,7 @@ clean-and-build.bat
#### **步骤 2:测试运行**
```bash
-cd out\win-unpacked
+cd out\NPQS-9100
NPQS9100.exe
```
@@ -200,7 +205,7 @@ NPQS9100.exe
```powershell
cd out
-Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force
+Compress-Archive -Path NPQS-9100 -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force
```
**压缩后大小**:约 350-450 MB
@@ -237,7 +242,8 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
├─ MySQL 连接:localhost:3307
├─ MySQL 密码:njcnpqs ⭐ 自动写入
├─ Java 端口:18093
- ├─ 数据路径:C:\NPQS9100_Data\
+ ├─ 数据路径:纯英文路径时为 `应用目录\NPQS9100_Data\`
+ ├─ 数据路径:中文路径时为 `X:\NPQS9100_Runtime\data\`
├─ 创建所有必要目录(logs、template、report、data)
↓
[80%] 启动 Spring Boot 后端
@@ -274,7 +280,7 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
↓
停止 Spring Boot 进程(通过PID和端口精确清理)
↓
-停止 MySQL 服务(如果是服务模式)
+停止 MySQL 进程(随应用启动的 mysqld.exe)
↓
清理资源
↓
@@ -289,17 +295,19 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
#### **升级流程**
```
-1. 双击 upgrade.bat(首次自动创建 upgrade/ 目录)
-2. 将升级包放入 upgrade/ 目录
-3. 再次运行 upgrade.bat(自动备份 + 升级)
-4. 重启应用测试
+1. 先手动导出数据库 SQL 备份
+2. 双击 upgrade.bat(首次自动创建 upgrade/ 目录)
+3. 将升级包放入 upgrade/ 目录
+4. 再次运行 upgrade.bat(自动备份前后端程序文件 + 升级)
+5. 重启应用测试
```
#### **回滚流程**
```
1. 双击 rollback.bat
-2. 自动从 backup/ 恢复旧版本
-3. 重启应用
+2. 自动从 backup/ 恢复前后端程序文件
+3. 如需恢复数据库,手动执行之前导出的 SQL
+4. 重启应用
```
---
@@ -310,23 +318,27 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**第 1 步:准备升级包**
-1. 双击 `upgrade.bat`
-2. 脚本自动创建 `upgrade/` 文件夹并提示
-3. 将升级文件放入 `upgrade/` 目录:
- - `dist/` - 前端升级包(可选)
+1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
+2. 双击 `upgrade.bat`
+3. 脚本自动创建 `upgrade/` 文件夹并提示
+4. 将升级文件放入 `upgrade/` 目录:
+ - `app.asar` + `app.asar.unpacked/` - 前端升级包(可选,必须成套)
- `entrance.jar` - 后端升级包(可选)
-4. 再次双击 `upgrade.bat` 开始升级
+5. 再次双击 `upgrade.bat` 开始升级
**第 2 步:自动升级**
脚本会自动:
- ✅ 停止 NPQS9100 进程
-- ✅ **自动备份当前版本到 `backup/` 目录**
-- ✅ 备份数据库到 `mysql/data_backup/`
+- ✅ **自动备份当前前后端程序文件到 `backup/` 目录**
- ✅ 替换前端文件(如果有)
- ✅ 替换后端 JAR(如果有)
- ✅ 记录升级日志到 `logs/upgrade.log`
+数据库说明:
+- ⚠️ 数据库不再由 `upgrade.bat` 自动备份
+- ⚠️ 升级前请务必手动导出 SQL 备份
+
**第 3 步:重启应用**
升级完成后,双击 `NPQS9100.exe` 启动应用。
@@ -340,8 +352,9 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**升级前端**:
```batch
taskkill /F /IM NPQS9100.exe
-rmdir /s /q resources\app.asar.unpacked\public\dist
-xcopy upgrade\dist resources\app.asar.unpacked\public\dist\ /E /I /Y
+copy /Y upgrade\app.asar resources\app.asar
+rmdir /s /q resources\app.asar.unpacked
+xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
```
**升级后端**:
@@ -360,23 +373,23 @@ copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar
脚本会自动:
- ✅ 停止 NPQS9100
-- ✅ 恢复前端(从 `backup/dist/`)
+- ✅ 恢复前端(从 `backup/app.asar` 和 `backup/app.asar.unpacked/`)
- ✅ 恢复后端(从 `backup/entrance.jar`)
-- ✅ 询问是否恢复数据库(⚠️ 会丢失升级后的数据)
+- ✅ 数据库由人工恢复,不再由脚本自动处理
#### **方法 2:手动回滚**
```batch
# 恢复前端
-rmdir /s /q resources\app.asar.unpacked\public\dist
-xcopy backup\dist resources\app.asar.unpacked\public\dist\ /E /I /Y
+copy /Y backup\app.asar resources\app.asar
+rmdir /s /q resources\app.asar.unpacked
+xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
# 恢复后端
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
-# 恢复数据库(⚠️ 谨慎!)
-rmdir /s /q mysql\data
-xcopy mysql\data_backup mysql\data\ /E /I /Y
+# 恢复数据库
+# 使用 Navicat 或其他工具执行升级前导出的 SQL 备份
```
---
@@ -385,9 +398,9 @@ xcopy mysql\data_backup mysql\data\ /E /I /Y
| 升级类型 | 升级包来源 | 客户放置位置 | 最终替换位置 |
|---------|-----------|-------------|-------------|
-| **前端** | `out/win-unpacked/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` |
-| **后端** | `out/win-unpacked/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
-| **数据库** | ⚠️ **不升级,自动保留** | - | `mysql/data/` |
+| **前端** | `out/NPQS-9100/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` |
+| **后端** | `out/NPQS-9100/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
+| **数据库** | 手动导出的 SQL 备份 | 不放入升级包 | 需要时人工导入 |
---
@@ -412,10 +425,11 @@ npm run build-frontend
# 4️⃣ 打包应用
npm run build-w
-# 输出: out/win-unpacked/
+# 最终交付目录: out/NPQS-9100/
+# 原始输出目录: out/win-unpacked/(脚本会自动重命名)
# 5️⃣ 准备升级包
-# 从 out/win-unpacked/resources/ 复制以下文件:
+# 从 out/NPQS-9100/resources/ 复制以下文件:
# - app.asar (文件)
# - app.asar.unpacked/ (整个文件夹)
#
@@ -435,7 +449,7 @@ npm run build-w
# - app.asar.unpacked/
# 2️⃣ 复制到升级目录
-# 将两个文件都放到:win-unpacked/upgrade/
+# 将两个文件都放到:NPQS-9100/upgrade/
# - upgrade/app.asar
# - upgrade/app.asar.unpacked/
@@ -478,7 +492,7 @@ npm run build-w
# 2️⃣ 复制到升级目录
# 将 entrance.jar 复制到:
-# win-unpacked/upgrade/entrance.jar
+# NPQS-9100/upgrade/entrance.jar
# 3️⃣ 运行升级脚本
# 双击: upgrade.bat
@@ -497,7 +511,7 @@ NPQS9100-升级-v1.1.0.zip
└── entrance.jar # 后端升级包
# 客户侧:复制到
-win-unpacked/upgrade/
+NPQS-9100/upgrade/
├── app.asar
├── app.asar.unpacked/
└── entrance.jar
@@ -511,13 +525,11 @@ win-unpacked/upgrade/
### ✅ 打包验证
-- [ ] `out/win-unpacked` 目录存在
+- [ ] `out/NPQS-9100` 目录存在
- [ ] 目录大小约 650-800 MB
- [ ] NPQS9100.exe 存在
-- [ ] NPQS9100-启动器.bat 存在
- [ ] upgrade.bat 存在
- [ ] rollback.bat 存在
-- [ ] uninstall-mysql-service.bat 存在
- [ ] 使用说明.txt 存在
- [ ] README-升级回滚.txt 存在
- [ ] mysql/ 目录完整
@@ -602,10 +614,12 @@ win-unpacked/upgrade/
- [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录
- [ ] upgrade.bat 检测到升级文件后正常升级
- [ ] 升级前自动备份到 backup/ 目录
+- [ ] 升级前已手动导出数据库 SQL
- [ ] 升级后应用正常运行
- [ ] 升级后数据库数据保留
- [ ] rollback.bat 能正确回滚前端
- [ ] rollback.bat 能正确回滚后端
+- [ ] 如需恢复数据库,能手动导入 SQL
- [ ] 回滚后应用恢复正常
---
@@ -710,7 +724,7 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### Q8: 升级后数据丢失?
-**A**: 检查 `mysql/data/` 目录是否完整,如有备份,从 `mysql/data_backup/` 恢复
+**A**: 检查升级前是否已手动导出数据库 SQL 备份。如需恢复数据库,请使用 Navicat 或其他工具执行该 SQL 备份。
---
@@ -728,7 +742,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
**A**:
1. 检查 `backup/` 目录是否有备份文件
2. 查看 `backup/version.txt` 确认备份版本
-3. 手动执行回滚步骤(参考本文档)
+3. 手动执行前后端回滚步骤(参考本文档)
+4. 如问题与数据库有关,手动导入升级前导出的 SQL
---
@@ -767,27 +782,32 @@ VITE_API_URL=http://192.168.1.100:18092
---
-### Q14: MySQL 服务如何卸载?
+### Q14: 现在还需要安装或卸载 MySQL 服务吗?
-**A**:
-**方法 1:使用卸载脚本(推荐)**
-```batch
-双击: uninstall-mysql-service.bat
-```
+**A**:
-**方法 2:手动卸载**
-```batch
-# 停止服务
-net stop mysql9100
+**当前版本不需要。**
-# 删除服务
-sc delete mysql9100
-```
+当前版本已经放弃“首次使用时把 MySQL 安装成 Windows 服务并设置开机自启”的方案,改为**绿色包 + 进程模式**:
-**注意**:
-- 卸载服务不会删除数据库数据
-- 数据保存在 `mysql/data/` 目录
-- 下次启动应用会重新安装服务
+- 双击 `NPQS9100.exe` 时,应用会自动启动 `mysqld.exe`
+- 退出应用时,MySQL 进程会自动停止
+- **无需管理员权限**
+- **不会注册 Windows 服务**
+- **不会依赖开机自启**
+
+**为什么改成这样?**
+
+- 旧方案需要管理员权限
+- 用户可能直接双击使用,导致服务安装失败后应用启动失败
+- 也可能出现授权失败、赋权不完整等问题
+- 对 C 端用户来说不稳定,因此改为随应用启停的绿色包进程模式
+
+**数据说明**:
+
+- 程序目录为纯英文/ASCII路径时,MySQL 数据位于 `应用目录\mysql\data\`
+- 程序目录包含中文或其他非 ASCII 字符时,MySQL 数据位于 `X:\NPQS9100_Runtime\mysql\data\`
+- 当前版本不依赖任何 MySQL Windows 服务
---
@@ -800,8 +820,7 @@ sc delete mysql9100
**开发者文档**(doc/ 目录):
- `doc/NPQS-9100绿色包完整指南.md` - 本文档(完整指南)
- `doc/打包前检查清单.md` - 逐项检查
-- `doc/管理员权限说明.md` - 权限问题处理
-- `doc/MySQL服务化方案说明.md` - MySQL 服务管理
+- `doc/管理员权限说明.md` - 历史服务模式与当前进程模式说明
---
@@ -812,7 +831,7 @@ sc delete mysql9100
- `scripts/port-checker.js` - 端口检测工具
- `scripts/startup-manager.js` - 启动状态管理
- `scripts/config-generator.js` - 配置文件生成
-- `scripts/mysql-service-manager.js` - MySQL 服务管理器
+- `scripts/mysql-process-manager.js` - MySQL 进程管理器
- `scripts/java-runner.js` - Java 运行器
- `scripts/log-window-manager.js` - 日志窗口管理
- `electron/preload/lifecycle.js` - 生命周期管理
@@ -827,7 +846,7 @@ StartupManager (显示 Loading)
↓ 调用
PortChecker (检测端口)
↓ 调用
-MySQLServiceManager (管理 MySQL 服务)
+MySQLProcessManager (管理 MySQL 进程)
↓ 调用
ConfigGenerator (生成配置)
↓ 调用
@@ -840,14 +859,14 @@ JavaRunner (启动 Spring Boot)
### 用户体验提升
- ✅ **任务栏只显示1个图标** - Loading 窗口不在任务栏显示
-- ✅ **纯绿色版** - 只生成 win-unpacked 目录,压缩成 zip 即可发布
+- ✅ **纯绿色版** - 最终交付目录统一为 `out/NPQS-9100`,压缩成 zip 即可发布
- ✅ **一键解压即用** - 用户解压后双击即可运行
- ✅ **热更新机制** - 前后端可独立升级,支持一键回滚
### 技术改进
- ✅ **MySQL 密码自动配置** - 配置生成器自动写入密码
- ✅ **权限自动授权** - 支持 localhost 和 127.0.0.1 访问
-- ✅ **MySQL 服务化** - 使用 Windows 服务管理,开机自启
+- ✅ **MySQL 进程模式** - 随应用启动和退出自动管理,无需管理员权限
- ✅ **窗口管理优化** - 使用 destroy() 确保窗口完全释放
- ✅ **精确进程清理** - 不会误杀其他 Java 进程
- ✅ **自动备份机制** - 升级前自动备份,支持回滚
diff --git a/doc/开发者指南_自己动手修改.md b/doc/开发者指南_自己动手修改.md
index 01f24ee..934bfc2 100644
--- a/doc/开发者指南_自己动手修改.md
+++ b/doc/开发者指南_自己动手修改.md
@@ -96,7 +96,7 @@ gen.generateConfig({ mysqlPort: 3307 }).then(console.log);
---
-#### `scripts/mysql-service-manager.js` - MySQL 进程管理器(绿色包 - 进程模式)
+#### `scripts/mysql-process-manager.js` - MySQL 进程管理器(绿色包 - 进程模式)
**作用**:以进程模式管理 MySQL,**无需管理员权限**,随应用启动/关闭
**核心方法**:
```javascript
@@ -141,8 +141,8 @@ this.mysqlProcess = spawn(mysqldPath, [
**调试方法**:
```javascript
// 单独测试
-const MySQLServiceManager = require('./scripts/mysql-service-manager');
-const mysql = new MySQLServiceManager();
+const MySQLProcessManager = require('./scripts/mysql-process-manager');
+const mysql = new MySQLProcessManager();
mysql.ensureServiceRunning(
(startPort, maxAttempts) => startPort, // Mock port checker
(port, timeout) => Promise.resolve(true)
@@ -363,7 +363,7 @@ await this.checkEnvironment(); // 你的自定义方法
### 场景4:修改MySQL进程配置(绿色包 - 进程模式)
-**文件**:`scripts/mysql-service-manager.js`
+**文件**:`scripts/mysql-process-manager.js`
**修改 my.ini 配置**(推荐方式):
```javascript
@@ -502,7 +502,7 @@ electron/preload/lifecycle.js (启动入口)
│
├── scripts/port-checker.js (检测端口)
│
-├── scripts/mysql-service-manager.js (MySQL服务管理)
+├── scripts/mysql-process-manager.js (MySQL进程管理)
│
├── scripts/config-generator.js (生成配置)
│ ↓ 读取
@@ -555,7 +555,7 @@ electron/preload/lifecycle.js (启动入口)
|------|------|---------|
| 端口检测 | `scripts/port-checker.js` | 全文 |
| 启动管理 | `scripts/startup-manager.js` | 全文 |
-| MySQL服务管理 | `scripts/mysql-service-manager.js` | 核心方法 ensureServiceRunning |
+| MySQL进程管理 | `scripts/mysql-process-manager.js` | 核心方法 ensureServiceRunning |
| Java启动 | `scripts/java-runner.js` | 152-198 |
| 配置生成 | `scripts/config-generator.js` | 38-83 |
| 启动流程 | `electron/preload/lifecycle.js` | 27-159 |
diff --git a/doc/打包前检查清单.md b/doc/打包前检查清单.md
index 44b5d50..2dd2470 100644
--- a/doc/打包前检查清单.md
+++ b/doc/打包前检查清单.md
@@ -21,7 +21,7 @@
- [ ] `scripts/port-checker.js` 存在
- [ ] `scripts/startup-manager.js` 存在
- [ ] `scripts/config-generator.js` 存在
-- [ ] `scripts/mysql-service-manager.js` 存在
+- [ ] `scripts/mysql-process-manager.js` 存在
- [ ] `scripts/log-window-manager.js` 存在
- [ ] `scripts/java-runner.js` 存在
@@ -45,7 +45,7 @@
### MySQL 进程(绿色包 - 进程模式)
- [ ] MySQL 进程能正常启动(通过 spawn mysqld.exe)
-- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,使用进程模式)
+- [ ] 确认当前版本为进程模式,不依赖 MySQL Windows 服务
- [ ] MySQL 数据库首次启动时自动初始化
- [ ] MySQL 连接密码正确(njcnpqs)
- [ ] MySQL 进程随应用退出而自动关闭
@@ -98,9 +98,9 @@ npm run build-w # 打包 Windows 便携版
### 基础测试(绿色包 - 进程模式)
- [ ] **无需管理员权限**,普通用户双击 exe 能正常启动 ✅
- [ ] Loading 界面正常显示,显示启动步骤
-- [ ] ~~MySQL 服务自动安装并启动~~(已废弃)
+- [ ] 不会尝试安装 MySQL Windows 服务
- [ ] **MySQL 进程自动启动**(任务管理器可见 mysqld.exe)
-- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,进程模式随应用启动)
+- [ ] MySQL 随应用启动,退出后自动停止,不依赖开机自启
- [ ] Spring Boot 自动启动(任务管理器可见 java.exe)
- [ ] 主界面正常显示
- [ ] 应用退出后,MySQL 和 Java 进程自动关闭 ✅
@@ -147,8 +147,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### 检查内容
- [ ] 端口检测日志正确
-- [ ] MySQL 服务安装/启动日志正常
-- [ ] 日志中显示"服务已配置为开机自启"
+- [ ] MySQL 进程启动日志正常
+- [ ] 日志中显示 mysqld.exe 启动与退出清理信息
- [ ] Spring Boot 启动日志正常
- [ ] 无严重错误
@@ -185,7 +185,6 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
- 检查端口是否被占用(应自动切换到其他端口)
- 查看应用日志:`%APPDATA%/NQPS9100/logs/9100.log`
- 查看 MySQL 错误日志:`mysql/data/*.err`
-- ~~使用 sc query mysql9100 检查服务状态~~(已废弃,不再使用服务)
- 检查是否有残留的 mysqld.exe 进程(任务管理器)
### Spring Boot 启动失败
diff --git a/doc/管理员权限说明.md b/doc/管理员权限说明.md
index 2c2aae2..14a0d7a 100644
--- a/doc/管理员权限说明.md
+++ b/doc/管理员权限说明.md
@@ -1,58 +1,27 @@
# 管理员权限说明
-> ⚠️ **文档已过期** - 本文档描述的是旧版服务模式,自 2025-12-01 起已改用**进程模式(绿色包)**。
->
-> 🎉 **新版本不再需要管理员权限!**
->
-> 参考最新文档:`MySQL进程模式改造方案.md` 和 `NPQS-9100绿色包完整指南.md`
+当前版本采用**绿色包 + MySQL 进程模式**,**不需要管理员权限**。
----
+## 当前结论
-## ~~🔐 为什么需要管理员权限?~~(已废弃)
+1. 双击 `NPQS9100.exe` 即可启动
+2. 无需管理员权限
+3. 无 UAC 弹窗
+4. 解压即用,完全绿色
+5. 仅在应用运行期间启动 MySQL
-~~NPQS9100 需要管理 MySQL Windows 服务,执行以下操作:~~
-- ~~✅ 安装 MySQL 服务 (`mysql9100`)~~
-- ~~✅ 启动/停止服务~~
-- ~~✅ 删除服务(重新安装时)~~
+## 说明
-~~这些操作都需要 **Windows 管理员权限**。~~
+- MySQL 和 Spring Boot 会在应用启动时自动启动
+- 退出应用时自动清理
+- 不注册 Windows 服务
+- 不依赖开机自启
-**现状**:已改用进程模式,直接启动 `mysqld.exe` 进程,无需注册 Windows 服务,**完全不需要管理员权限**。
+## 为什么这样设计
-## 🎉 新版启动方式(进程模式)
-
-**当前版本启动非常简单**:
-
-1. ✅ 双击 `NPQS9100.exe` 即可启动
-2. ✅ 无需管理员权限
-3. ✅ 无 UAC 弹窗
-4. ✅ 解压即用,完全绿色
-
-**MySQL 和 Spring Boot 会在应用启动时自动启动,退出时自动清理。**
-
----
-
-## ~~以下内容已废弃(保留作为历史参考)~~
-
-
-点击展开查看旧版(服务模式)说明
-
-### ~~两种启动方式(已废弃)~~
-
-#### ~~方式 1:使用启动器(推荐)~~
-
-~~打包后的目录中有一个 `NPQS9100-启动器.bat` 文件。~~
-
-#### ~~方式 2:手动以管理员身份运行~~
-
-~~右键点击 `NPQS9100.exe` 选择"以管理员身份运行"~~
-
-### ~~常见问题(已废弃)~~
-
-~~Q: MySQL 服务安装后还需要管理员权限吗?~~
-A: **新版本不使用 Windows 服务,因此完全不需要管理员权限。**
-
-
+- 需要管理员权限的启动方式对 C 端用户不稳定
+- 用户直接双击使用时,提权、授权或服务安装失败会导致启动失败
+- 因此当前版本统一采用无需授权的绿色包进程模式
---
diff --git a/electron/preload/lifecycle.js b/electron/preload/lifecycle.js
index f5e8f81..6de7645 100644
--- a/electron/preload/lifecycle.js
+++ b/electron/preload/lifecycle.js
@@ -45,11 +45,11 @@ function getScriptsPath(scriptName) {
}
// 延迟加载 scripts
-let MySQLServiceManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
+let MySQLProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
function loadScripts() {
- if (!MySQLServiceManager) {
- MySQLServiceManager = require(getScriptsPath('mysql-service-manager'));
+ if (!MySQLProcessManager) {
+ MySQLProcessManager = require(getScriptsPath('mysql-process-manager'));
JavaRunner = require(getScriptsPath('java-runner'));
ConfigGenerator = require(getScriptsPath('config-generator'));
PortChecker = require(getScriptsPath('port-checker'));
@@ -60,7 +60,7 @@ function loadScripts() {
class Lifecycle {
constructor() {
- this.mysqlServiceManager = null;
+ this.mysqlProcessManager = null;
this.javaRunner = null;
this.startupManager = null;
this.logWindowManager = null;
@@ -94,26 +94,26 @@ class Lifecycle {
this.startupManager.updateProgress('init');
await this.sleep(500);
- // 步骤2-4: 确保 MySQL 服务运行
+ // 步骤2-4: 确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...');
logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart);
if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
this.startupManager.updateProgress('check-mysql-port');
- this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 服务管理器...');
+ this.logWindowManager.addLog('system', '▶ 步骤5: 启动 MySQL 进程管理器...');
- this.mysqlServiceManager = new MySQLServiceManager(this.logWindowManager);
- this.logWindowManager.addLog('system', '正在检查 MySQL 服务状态...');
+ this.mysqlProcessManager = new MySQLProcessManager(this.logWindowManager);
+ this.logWindowManager.addLog('system', '正在检查 MySQL 进程状态...');
try {
- // 使用服务管理器确保MySQL服务运行
- this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 服务运行中...');
- this.mysqlPort = await this.mysqlServiceManager.ensureServiceRunning(
+ // 使用进程管理器确保 MySQL 进程运行
+ this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 进程运行中...');
+ this.mysqlPort = await this.mysqlProcessManager.ensureServiceRunning(
PortChecker.findAvailablePort.bind(PortChecker),
PortChecker.waitForPort.bind(PortChecker)
);
- logger.info(`[lifecycle] MySQL service running on port: ${this.mysqlPort}`);
+ logger.info(`[lifecycle] MySQL process running on port: ${this.mysqlPort}`);
this.logWindowManager.addLog('success', `✓ MySQL 服务已就绪,端口: ${this.mysqlPort}`);
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
await this.sleep(500);
@@ -201,7 +201,7 @@ class Lifecycle {
dataPath: dataPath
});
- await this.startSpringBoot(configPath);
+ await this.startSpringBoot(configPath, dataPath);
// 步骤8: 等待 Spring Boot 就绪
this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪(最多60秒)...');
@@ -376,14 +376,14 @@ class Lifecycle {
}
// 停止 MySQL 进程(进程模式)
- if (this.mysqlServiceManager) {
+ if (this.mysqlProcessManager) {
try {
logger.info('[lifecycle] Stopping MySQL process...');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
this.logWindowManager.addLog('system', '正在停止 MySQL...');
}
- await this.mysqlServiceManager.stopMySQLProcess();
+ await this.mysqlProcessManager.stopMySQLProcess();
logger.info('[lifecycle] MySQL process stopped');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
@@ -419,7 +419,7 @@ class Lifecycle {
/**
* 启动 Spring Boot 应用
*/
- async startSpringBoot(configPath) {
+ async startSpringBoot(configPath, dataPath) {
try {
logger.info('[lifecycle] Starting Spring Boot application...');
this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...');
@@ -434,12 +434,6 @@ class Lifecycle {
? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar')
: path.join(process.resourcesPath, 'extraResources', 'java', 'entrance.jar');
- // 获取日志路径(与 config-generator.js 中的 dataPath 保持一致)
- const isDev2 = !process.resourcesPath;
- const baseDir = isDev2
- ? path.join(__dirname, '..', '..')
- : path.dirname(process.resourcesPath);
- const dataPath = path.join(baseDir, 'NPQS9100_Data');
const logPath = path.join(dataPath, 'logs');
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {
diff --git a/scripts/config-generator.js b/scripts/config-generator.js
index 02e7e7f..e1456ba 100644
--- a/scripts/config-generator.js
+++ b/scripts/config-generator.js
@@ -1,5 +1,6 @@
const fs = require('fs');
const path = require('path');
+const { resolveRuntimeStrategy } = require('./path-utils');
/**
* 配置文件生成器
@@ -8,11 +9,12 @@ const path = require('path');
class ConfigGenerator {
constructor() {
// 开发环境:项目根目录
- // 打包后:应用根目录(win-unpacked)
+ // 打包后:应用根目录(最终交付目录)
const isDev = !process.resourcesPath;
const baseDir = isDev
? path.join(__dirname, '..')
: path.dirname(process.resourcesPath);
+ const pathStrategy = resolveRuntimeStrategy(baseDir);
// 开发环境:build/extraResources/java
// 打包后:resources/extraResources/java
@@ -21,9 +23,12 @@ class ConfigGenerator {
: path.join(process.resourcesPath, 'extraResources', 'java');
this.templatePath = path.join(this.javaPath, 'application.yml.template');
this.configPath = path.join(this.javaPath, 'application.yml');
+ this.pathStrategy = pathStrategy;
- // 数据目录(使用应用所在盘符的根目录下的data文件夹)
- this.dataPath = this.getDataPath(baseDir);
+ // 数据目录:
+ // - 安全路径:使用应用目录内的 NPQS9100_Data
+ // - 非 ASCII 路径:切到英文安全运行根目录下,避免在盘符根目录创建多个文件夹
+ this.dataPath = this.getDataPath(baseDir, pathStrategy);
}
/**
@@ -31,8 +36,11 @@ class ConfigGenerator {
* @param {string} baseDir 应用基础目录
* @returns {string} 数据目录路径
*/
- getDataPath(baseDir) {
- // 数据目录设置在应用目录内的 NPQS9100_Data 文件夹
+ getDataPath(baseDir, pathStrategy = resolveRuntimeStrategy(baseDir)) {
+ if (pathStrategy.usesSafePaths) {
+ return path.join(pathStrategy.safeRuntimeRoot, 'data');
+ }
+
return path.join(baseDir, 'NPQS9100_Data');
}
@@ -85,6 +93,7 @@ class ConfigGenerator {
this.createDirectories();
console.log('[ConfigGenerator] Configuration file generated successfully');
+ console.log('[ConfigGenerator] Path mode:', this.pathStrategy.usesSafePaths ? 'safe-data-root' : 'app-local-data');
console.log('[ConfigGenerator] Data path:', this.dataPath);
console.log('[ConfigGenerator] MySQL port:', options.mysqlPort || 3306);
console.log('[ConfigGenerator] MySQL password:', options.mysqlPassword || 'njcnpqs');
diff --git a/scripts/java-runner.js b/scripts/java-runner.js
index 915464a..88b2f9e 100644
--- a/scripts/java-runner.js
+++ b/scripts/java-runner.js
@@ -9,7 +9,7 @@ class JavaRunner {
constructor() {
// 在开发与打包后均可解析到应用根目录下的 jre 目录
// 开发环境:项目根目录
- // 打包后:应用根目录(win-unpacked)
+ // 打包后:应用根目录(最终交付目录)
const isDev = !process.resourcesPath;
const baseDir = isDev
? path.join(__dirname, '..')
diff --git a/scripts/mysql-service-manager.js b/scripts/mysql-process-manager.js
similarity index 74%
rename from scripts/mysql-service-manager.js
rename to scripts/mysql-process-manager.js
index 22082ed..4020706 100644
--- a/scripts/mysql-service-manager.js
+++ b/scripts/mysql-process-manager.js
@@ -1,19 +1,26 @@
const { spawn, exec } = require('child_process');
const path = require('path');
const fs = require('fs');
+const { resolveRuntimeStrategy } = require('./path-utils');
/**
* MySQL 进程管理器
* 使用进程模式启动 MySQL,无需管理员权限
*/
-class MySQLServiceManager {
+class MySQLProcessManager {
constructor(logWindowManager = null) {
const isDev = !process.resourcesPath;
const baseDir = isDev
? path.join(__dirname, '..')
: path.dirname(process.resourcesPath);
+ const pathStrategy = resolveRuntimeStrategy(baseDir);
- this.mysqlPath = path.join(baseDir, 'mysql');
+ this.baseDir = baseDir;
+ this.pathStrategy = pathStrategy;
+ this.sourceMysqlPath = path.join(baseDir, 'mysql');
+ this.mysqlPath = pathStrategy.usesSafePaths
+ ? path.join(pathStrategy.safeRuntimeRoot, 'mysql')
+ : this.sourceMysqlPath;
this.binPath = path.join(this.mysqlPath, 'bin');
this.dataPath = path.join(this.mysqlPath, 'data');
this.configFile = path.join(this.mysqlPath, 'my.ini');
@@ -37,6 +44,106 @@ class MySQLServiceManager {
}
}
+ /**
+ * 递归复制目录。
+ * 仅当文件不存在、大小变化或源文件时间更新时才覆盖,避免每次启动全量重写。
+ */
+ copyDirectorySync(sourceDir, targetDir, options = {}) {
+ const {
+ excludeTopLevelDirs = new Set(),
+ excludeFileNames = new Set(),
+ excludeFileExtensions = new Set()
+ } = options;
+
+ if (!fs.existsSync(sourceDir)) {
+ return;
+ }
+
+ if (!fs.existsSync(targetDir)) {
+ fs.mkdirSync(targetDir, { recursive: true });
+ }
+
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
+
+ entries.forEach((entry) => {
+ const sourcePath = path.join(sourceDir, entry.name);
+ const targetPath = path.join(targetDir, entry.name);
+ const isTopLevelEntry = sourceDir === this.sourceMysqlPath;
+
+ if (entry.isDirectory()) {
+ if (isTopLevelEntry && excludeTopLevelDirs.has(entry.name)) {
+ return;
+ }
+
+ this.copyDirectorySync(sourcePath, targetPath, options);
+ return;
+ }
+
+ const extension = path.extname(entry.name).toLowerCase();
+ if (excludeFileNames.has(entry.name) || excludeFileExtensions.has(extension)) {
+ return;
+ }
+
+ let shouldCopy = !fs.existsSync(targetPath);
+ if (!shouldCopy) {
+ const sourceStat = fs.statSync(sourcePath);
+ const targetStat = fs.statSync(targetPath);
+ shouldCopy = sourceStat.size !== targetStat.size || sourceStat.mtimeMs > targetStat.mtimeMs;
+ }
+
+ if (shouldCopy) {
+ fs.copyFileSync(sourcePath, targetPath);
+ }
+ });
+ }
+
+ /**
+ * 非 ASCII 路径下,将 MySQL 运行环境同步到英文安全目录。
+ */
+ ensureRuntimeEnvironment() {
+ if (!this.pathStrategy.usesSafePaths) {
+ return;
+ }
+
+ this.log('system', '检测到应用路径包含非 ASCII 字符,启用 MySQL 英文安全运行目录');
+ this.log('system', `MySQL 源目录: ${this.sourceMysqlPath}`);
+ this.log('system', `MySQL 运行目录: ${this.mysqlPath}`);
+ this.log('system', `MySQL 数据目录: ${this.dataPath}`);
+
+ fs.mkdirSync(this.mysqlPath, { recursive: true });
+ fs.mkdirSync(path.dirname(this.dataPath), { recursive: true });
+
+ this.copyDirectorySync(this.sourceMysqlPath, this.mysqlPath, {
+ excludeTopLevelDirs: new Set(['data', 'data_backup']),
+ excludeFileNames: new Set(['my.ini', 'mysql_error.log', 'mysql_slow.log']),
+ excludeFileExtensions: new Set(['.pid', '.err'])
+ });
+
+ this.ensureSeedData();
+ }
+
+ /**
+ * 首次进入英文安全路径模式时,将打包内预置数据库复制到安全数据目录。
+ */
+ ensureSeedData() {
+ const hasTargetData = fs.existsSync(this.dataPath) && fs.readdirSync(this.dataPath).length > 0;
+ if (hasTargetData) {
+ this.log('system', '检测到已有 MySQL 安全数据目录,直接复用');
+ return;
+ }
+
+ const sourceDataPath = path.join(this.sourceMysqlPath, 'data');
+ if (fs.existsSync(sourceDataPath) && fs.readdirSync(sourceDataPath).length > 0) {
+ this.log('system', '首次启动,正在复制预置 MySQL 数据到安全数据目录...');
+ this.copyDirectorySync(sourceDataPath, this.dataPath);
+ this.log('success', '预置 MySQL 数据复制完成');
+ return;
+ }
+
+ fs.mkdirSync(this.dataPath, { recursive: true });
+ this.log('warn', '未找到预置 MySQL 数据目录,后续将按空库初始化');
+ }
+
/**
* 执行命令并返回Promise
*/
@@ -352,7 +459,10 @@ default-character-set=utf8mb4
* @returns {Promise} 返回 MySQL 运行的端口
*/
async ensureServiceRunning(findAvailablePort, waitForPort) {
+ this.ensureRuntimeEnvironment();
+
this.log('system', '开始 MySQL 进程检查流程(进程模式)');
+ this.log('system', `路径策略: ${this.pathStrategy.usesSafePaths ? '英文安全路径模式' : '应用目录直启模式'}`);
this.log('system', `MySQL 路径: ${this.mysqlPath}`);
this.log('system', `配置文件: ${this.configFile}`);
@@ -401,4 +511,4 @@ default-character-set=utf8mb4
}
}
-module.exports = MySQLServiceManager;
+module.exports = MySQLProcessManager;
diff --git a/scripts/path-utils.js b/scripts/path-utils.js
new file mode 100644
index 0000000..b60ba57
--- /dev/null
+++ b/scripts/path-utils.js
@@ -0,0 +1,41 @@
+const path = require('path');
+
+/**
+ * 判断路径中是否包含非 ASCII 字符。
+ * 这里不只判断中文,其他非 ASCII 字符同样视为不安全路径。
+ */
+function hasNonAscii(targetPath = '') {
+ return /[^\x00-\x7F]/.test(targetPath);
+}
+
+/**
+ * 获取当前路径所在盘符根目录,例如 D:\
+ */
+function getDriveRoot(targetPath = '') {
+ const resolvedPath = path.resolve(targetPath || process.cwd());
+ return path.parse(resolvedPath).root;
+}
+
+/**
+ * 解析运行期路径策略。
+ * - 安全路径:继续直接使用应用目录
+ * - 非 ASCII 路径:切到英文安全路径
+ */
+function resolveRuntimeStrategy(baseDir) {
+ const normalizedBaseDir = path.resolve(baseDir);
+ const driveRoot = getDriveRoot(normalizedBaseDir);
+ const usesSafePaths = hasNonAscii(normalizedBaseDir);
+
+ return {
+ baseDir: normalizedBaseDir,
+ driveRoot,
+ usesSafePaths,
+ safeRuntimeRoot: path.join(driveRoot, 'NPQS9100_Runtime')
+ };
+}
+
+module.exports = {
+ hasNonAscii,
+ getDriveRoot,
+ resolveRuntimeStrategy
+};