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 +};