11 Commits

Author SHA1 Message Date
caozehui
72838462ad 微调 2026-04-22 10:02:21 +08:00
caozehui
327addf625 微调 2026-04-22 09:58:03 +08:00
caozehui
7fd3b6fdff 归档 2026-04-13 16:31:45 +08:00
4bfab6518e 微调 2026-04-08 20:33:01 +08:00
caozehui
4655259153 归档 2026-04-07 13:28:04 +08:00
caozehui
cdb23726f8 归档 2026-04-07 13:22:57 +08:00
caozehui
68a1c9d28d 归档 2026-04-07 11:25:33 +08:00
caozehui
30e815c027 归档 2026-04-07 11:14:34 +08:00
caozehui
ce10f91b5b 归档 2026-04-07 11:07:18 +08:00
65ac4b1dde 修复C端打包后的程序图标不显示logo的问题 2026-04-02 21:00:12 +08:00
926b85bf8d C端打包修复不能在中文路径下启动的问题 2026-04-02 20:51:19 +08:00
93 changed files with 492 additions and 386 deletions

View File

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

View File

@@ -4,27 +4,34 @@ NPQS-9100 升级与回滚说明
一、升级步骤(超简单!) 一、升级步骤(超简单!)
---------------------------------------- ----------------------------------------
1. 双击 upgrade.bat首次会自动创建 upgrade 文件夹) 1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
2. 将升级文件放入 upgrade/ 目录: 2. 双击 upgrade.bat首次会自动创建 upgrade 文件夹)
- app.asar (前端升级包 - 文件) 3. 将升级文件放入 upgrade/ 目录:
- app.asar.unpacked\ (前端升级包 - 文件夹 - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
- entrance.jar (后端升级包) - entrance.jar (后端升级包)
3. 再次双击 upgrade.bat 开始升级 4. 再次双击 upgrade.bat 开始升级
4. 等待完成后重启应用 5. 等待完成后重启应用
补充说明:
- 可以只升级后端
- 可以只升级前端,但前端升级时必须同时提供:
app.asar
app.asar.unpacked\
- 数据库不由 upgrade.bat 自动备份,请务必提前手动导出 SQL
二、回滚步骤 二、回滚步骤
---------------------------------------- ----------------------------------------
如果升级后出现问题: 如果升级后出现问题:
1. 双击 rollback.bat 1. 双击 rollback.bat(仅回滚前后端程序文件)
2. 选择是否回滚数据库(谨慎!) 2. 等待完成后重启应用
3. 等待完成后重启应用 3. 如需恢复数据库,请手动执行升级前导出的 SQL
三、重要提示 三、重要提示
---------------------------------------- ----------------------------------------
✓ 升级前会自动备份到 backup/ 目录 ✓ 升级前会自动备份前后端程序文件到 backup/ 目录
✓ 数据库会自动备份到 mysql/data_backup/
✓ 升级日志保存在 logs/upgrade.log ✓ 升级日志保存在 logs/upgrade.log
✓ 多次升级时backup/ 保存最后一次升级前的版本 ✓ 多次升级时backup/ 保存最后一次升级前的版本
✓ 数据库备份与恢复由人工处理,不再由脚本自动执行
四、紧急情况 四、紧急情况
---------------------------------------- ----------------------------------------
@@ -32,15 +39,15 @@ NPQS-9100 升级与回滚说明
【恢复前端】 【恢复前端】
copy /Y backup\app.asar resources\app.asar 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 xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
【恢复后端】 【恢复后端】
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
【恢复数据库】(慎用!) 【恢复数据库】
xcopy mysql\data_backup mysql\data\ /E /I /Y 请使用 Navicat 或其他工具执行升级前导出的 SQL 备份
======================================== ========================================
详细文档请参考doc/绿色包升级指南.md 如需完整技术文档,请联系交付方提供开发文档。
======================================== ========================================

View File

@@ -1,29 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="*"
name="NPQS9100"
type="win32"
/>
<description>NPQS-9100自动检测平台</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
</application>
</compatibility>
</assembly>

View File

@@ -52,7 +52,8 @@ echo.
echo ======================================== echo ========================================
echo 打包完成! echo 打包完成!
echo 输出目录: out\win-unpacked\ echo 最终交付目录: out\NPQS-9100\
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ======================================== echo ========================================
echo. echo.
pause pause

View File

@@ -63,7 +63,8 @@ echo.
echo ======================================== echo ========================================
echo ✓ 打包完成! echo ✓ 打包完成!
echo 输出目录: out\win-unpacked\ echo 最终交付目录: out\NPQS-9100\
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ======================================== echo ========================================
echo. echo.
pause pause

View File

@@ -53,13 +53,13 @@ if exist "%LOGDIR%" (
) )
echo. echo.
echo [4] 检查 MySQL 服务... echo [4] 检查当前运行模式...
sc query mysql9100 >nul 2>&1 echo 当前版本为绿色包进程模式,不使用 MySQL Windows 服务
if %errorlevel% equ 0 ( if exist "mysql\my.ini" (
echo MySQL 服务存在 echo 检测到 MySQL 配置文件:
sc query mysql9100 | findstr "STATE" findstr /i "^port=" "mysql\my.ini"
) else ( ) else (
echo MySQL 服务不存在 echo 尚未检测到 mysql\my.ini可能应用还未完整启动
) )
echo. echo.

View File

@@ -1 +1 @@
74476 53820

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -7,3 +7,8 @@
.\binlog.000029 .\binlog.000029
.\binlog.000030 .\binlog.000030
.\binlog.000031 .\binlog.000031
.\binlog.000032
.\binlog.000033
.\binlog.000034
.\binlog.000035
.\binlog.000036

View File

@@ -1,17 +1,19 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
setlocal
echo ======================================== echo ========================================
echo NPQS9100 回滚工具 echo NPQS9100 回滚工具
echo ======================================== echo ========================================
echo. echo.
echo 【重要提示】 echo 【重要提示】
echo 本工具用于回滚上次升级的内容 echo 本工具仅回滚前后端程序文件
echo 回滚后将恢复到升级前的状态 echo 数据库不会由本脚本自动恢复
echo 如需恢复数据库,请手动执行之前导出的 SQL 备份
echo. echo.
pause pause
echo. echo.
echo [1/5] 停止 NPQS9100 进程... echo [1/4] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo NPQS9100 已停止 echo NPQS9100 已停止
@@ -21,7 +23,7 @@ if %errorlevel% equ 0 (
) )
echo. echo.
echo [2/5] 检查备份文件... echo [2/4] 检查备份文件...
set hasBackup=0 set hasBackup=0
if exist backup\app.asar ( if exist backup\app.asar (
@@ -39,23 +41,17 @@ if exist backup\entrance.jar (
set hasBackup=1 set hasBackup=1
) )
if exist mysql\data_backup (
echo 发现数据库备份
set hasBackup=1
)
if %hasBackup%==0 ( if %hasBackup%==0 (
echo 未发现任何备份文件! echo 未发现任何前后端备份文件!
echo 无法执行回滚操作 echo 无法执行回滚操作
pause pause
exit /b 1 exit /b 1
) )
echo. echo.
echo [3/5] 回滚前端... echo [3/4] 回滚前端...
set frontendRollback=0 set frontendRollback=0
REM 回滚 app.asar
if exist backup\app.asar ( if exist backup\app.asar (
echo 正在恢复 app.asar... echo 正在恢复 app.asar...
copy /Y backup\app.asar resources\app.asar >nul 2>&1 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 ( if exist backup\app.asar.unpacked (
echo 正在恢复 app.asar.unpacked... echo 正在恢复 app.asar.unpacked...
if exist resources\app.asar.unpacked ( if exist resources\app.asar.unpacked (
@@ -91,7 +86,7 @@ if %frontendRollback%==0 (
) )
echo. echo.
echo [4/5] 回滚后端... echo [4/4] 回滚后端...
if exist backup\entrance.jar ( if exist backup\entrance.jar (
echo 正在恢复 JAR 文件... echo 正在恢复 JAR 文件...
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
@@ -107,37 +102,11 @@ if exist backup\entrance.jar (
) )
echo. 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 ======================================== echo ========================================
echo. echo.
echo 如需恢复数据库,请手动执行之前导出的 SQL 备份。
echo 您现在可以启动 NPQS9100 了 echo 您现在可以启动 NPQS9100 了
echo. echo.
pause pause

View File

@@ -1,5 +1,6 @@
@echo off @echo off
chcp 65001 >nul chcp 65001 >nul
setlocal
echo ======================================== echo ========================================
echo NPQS9100 升级工具 echo NPQS9100 升级工具
echo ======================================== echo ========================================
@@ -11,9 +12,8 @@ if not exist upgrade (
echo 【首次使用】已自动创建 upgrade 目录 echo 【首次使用】已自动创建 upgrade 目录
echo. echo.
echo 请将升级文件放入 upgrade 目录: echo 请将升级文件放入 upgrade 目录:
echo - app.asar (前端升级包 - 文件 echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - app.asar.unpacked\ (前端升级包 - 文件夹 echo - entrance.jar (后端升级包
echo - entrance.jar (后端升级包)
echo. echo.
echo 放置完成后,重新运行本脚本即可升级 echo 放置完成后,重新运行本脚本即可升级
echo. echo.
@@ -21,19 +21,51 @@ if not exist upgrade (
exit /b 0 exit /b 0
) )
REM 检查是否有升级文件 REM 检查升级文件状态
set hasUpgrade=0 set "hasFrontendAsar=0"
if exist upgrade\app.asar set hasUpgrade=1 set "hasFrontendUnpacked=0"
if exist upgrade\app.asar.unpacked set hasUpgrade=1 set "upgradeFrontend=0"
if exist upgrade\entrance.jar set hasUpgrade=1 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 【提示】upgrade 目录为空!
echo. echo.
echo 请将升级文件放入 upgrade 目录: echo 请将升级文件放入 upgrade 目录:
echo - app.asar (前端升级包 - 文件 echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - app.asar.unpacked\ (前端升级包 - 文件夹 echo - entrance.jar (后端升级包
echo - entrance.jar (后端升级包)
echo. echo.
echo 放置完成后,重新运行本脚本即可升级 echo 放置完成后,重新运行本脚本即可升级
echo. echo.
@@ -42,19 +74,22 @@ if %hasUpgrade%==0 (
) )
echo 【检测到升级文件】 echo 【检测到升级文件】
if exist upgrade\app.asar echo - 前端升级asar文件 upgrade\app.asar if %upgradeFrontend%==1 (
if exist upgrade\app.asar.unpacked echo - 前端升级unpacked文件夹 upgrade\app.asar.unpacked\ echo - 前端升级: upgrade\app.asar + upgrade\app.asar.unpacked\
if exist upgrade\entrance.jar echo - 后端升级: upgrade\entrance.jar )
if %upgradeBackend%==1 (
echo - 后端升级: upgrade\entrance.jar
)
echo. echo.
echo 【重要提示】 echo 【重要提示】
echo 1. 升级前会自动备份当前版本 echo 1. 升级前会自动备份当前前后端程序文件
echo 2. 如升级失败可运行 rollback.bat 回滚 echo 2. 如升级失败可运行 rollback.bat 回滚前后端程序文件
echo 3. 数据库会自动备份到 mysql\data_backup\ echo 3. 数据库请先使用 Navicat 或其他工具手动导出 SQL 备份
echo. echo.
pause pause
echo. echo.
echo [1/6] 停止 NPQS9100 进程... echo [1/5] 停止 NPQS9100 进程...
taskkill /F /IM NPQS9100.exe 2>nul taskkill /F /IM NPQS9100.exe 2>nul
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo NPQS9100 已停止 echo NPQS9100 已停止
@@ -64,63 +99,54 @@ if %errorlevel% equ 0 (
) )
echo. echo.
echo [2/6] 备份当前版本(用于回滚)... echo [2/5] 备份当前版本(用于回滚)...
if not exist backup mkdir backup if not exist backup mkdir backup
REM 备份前端(app.asar 和 app.asar.unpacked REM 备份前端(仅在执行前端升级时
if exist resources\app.asar ( if %upgradeFrontend%==1 (
echo 正在备份前端 app.asar... if exist resources\app.asar (
if not exist backup mkdir backup echo 正在备份前端 app.asar...
copy /Y resources\app.asar backup\app.asar >nul 2>&1 copy /Y resources\app.asar backup\app.asar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar 备份完成 echo app.asar 备份完成
) else ( ) else (
echo app.asar 备份失败 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 ( REM 备份后端(仅在执行后端升级时)
echo 正在备份前端 app.asar.unpacked... if %upgradeBackend%==1 if exist resources\extraResources\java\entrance.jar (
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 (
echo 正在备份后端... echo 正在备份后端...
copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1 copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo 后端备份完成 echo 后端备份完成
) else ( ) else (
echo 后端备份失败 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 pause
exit /b 1 exit /b 1
) )
) )
echo. echo.
echo [3/6] 记录版本信息... echo [3/5] 记录版本信息...
if not exist backup\version.txt ( if not exist backup\version.txt (
echo 备份时间: %date% %time% > backup\version.txt echo 备份时间: %date% %time% > backup\version.txt
echo 升级前版本备份 >> backup\version.txt echo 升级前版本备份 >> backup\version.txt
@@ -130,29 +156,22 @@ if not exist backup\version.txt (
echo 版本信息已记录 echo 版本信息已记录
echo. echo.
echo [4/6] 升级前端... echo [4/5] 升级前端...
set frontendUpgraded=0 if %upgradeFrontend%==1 (
REM 升级 app.asar
if exist upgrade\app.asar (
echo 正在替换 app.asar... echo 正在替换 app.asar...
copy /Y upgrade\app.asar resources\app.asar >nul 2>&1 copy /Y upgrade\app.asar resources\app.asar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar 升级完成 echo app.asar 升级完成
set frontendUpgraded=1
) else ( ) else (
echo app.asar 升级失败,正在回滚... echo app.asar 升级失败,正在回滚...
if exist backup\app.asar ( if exist backup\app.asar (
copy /Y backup\app.asar resources\app.asar >nul 2>&1 copy /Y backup\app.asar resources\app.asar >nul 2>&1
echo 已回滚到升级前版本
) )
echo 已回滚到升级前版本
pause pause
exit /b 1 exit /b 1
) )
)
REM 升级 app.asar.unpacked
if exist upgrade\app.asar.unpacked (
echo 正在替换 app.asar.unpacked... echo 正在替换 app.asar.unpacked...
if exist resources\app.asar.unpacked ( if exist resources\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul 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 xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
echo app.asar.unpacked 升级完成 echo app.asar.unpacked 升级完成
set frontendUpgraded=1 set "frontendUpgraded=1"
) else ( ) 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 ( if exist backup\app.asar.unpacked (
rmdir /s /q resources\app.asar.unpacked 2>nul 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 xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
echo 已回滚到升级前版本
) )
echo 已回滚到升级前版本
pause pause
exit /b 1 exit /b 1
) )
) ) else (
if %frontendUpgraded%==0 (
echo 无前端升级包,跳过 echo 无前端升级包,跳过
) )
echo. echo.
echo [5/6] 升级后端... echo [5/5] 升级后端...
if exist upgrade\entrance.jar ( if %upgradeBackend%==1 (
echo 正在替换 JAR 文件... echo 正在替换 JAR 文件...
copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
if %errorlevel% equ 0 ( if %errorlevel% equ 0 (
@@ -188,8 +208,17 @@ if exist upgrade\entrance.jar (
echo 后端升级失败,正在回滚... echo 后端升级失败,正在回滚...
if exist backup\entrance.jar ( if exist backup\entrance.jar (
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1 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 pause
exit /b 1 exit /b 1
) )
@@ -198,19 +227,17 @@ if exist upgrade\entrance.jar (
) )
echo. echo.
echo [6/6] 记录升级日志...
if not exist logs mkdir logs if not exist logs mkdir logs
echo ========================================== >> logs\upgrade.log echo ========================================== >> logs\upgrade.log
echo 升级时间: %date% %time% >> logs\upgrade.log echo 升级时间: %date% %time% >> logs\upgrade.log
if exist upgrade\dist ( if %upgradeFrontend%==1 (
echo 升级内容: 前端 >> logs\upgrade.log echo 升级内容: 前端 >> logs\upgrade.log
) )
if exist upgrade\entrance.jar ( if %upgradeBackend%==1 (
echo 升级内容: 后端 >> logs\upgrade.log echo 升级内容: 后端 >> logs\upgrade.log
) )
echo 数据库处理: 请手动导出/导入 SQL >> logs\upgrade.log
echo ========================================== >> logs\upgrade.log echo ========================================== >> logs\upgrade.log
echo 升级日志已记录
echo.
echo ======================================== echo ========================================
echo 升级完成! echo 升级完成!
@@ -218,10 +245,9 @@ echo ========================================
echo. echo.
echo 【提示】 echo 【提示】
echo 1. 如需回滚,请运行 rollback.bat echo 1. 如需回滚,请运行 rollback.bat
echo 2. 升级文件已使用,可删除 upgrade 目录 echo 2. 数据库如需恢复,请手动执行之前导出的 SQL
echo 3. 升级文件已使用,可删除 upgrade 目录
echo. echo.
echo 您现在可以启动 NPQS9100 应用。 echo 您现在可以启动 NPQS9100 应用。
echo. echo.
pause pause

View File

@@ -3,18 +3,21 @@
## 使用方法 ## 使用方法
1. 将升级文件放入此目录: 1. 将升级文件放入此目录:
- app.asar (前端升级包 - 文件) - app.asar + app.asar.unpacked/ (前端升级包,必须成套放入)
- app.asar.unpacked/ (前端升级包 - 文件夹)
- entrance.jar (后端升级包) - entrance.jar (后端升级包)
2. 双击运行根目录的 upgrade.bat 脚本 2. 升级前先手动导出数据库 SQL 备份
3. 升级完成后重启应用 3. 双击运行根目录的 upgrade.bat 脚本
4. 升级完成后重启应用
## 注意事项 ## 注意事项
- 可以只放前端或只放后端,支持单独升级 - 可以只放后端,或放完整前端包,支持单独升级
- 如果升级前端,必须同时放入 app.asar 和 app.asar.unpacked/
- 升级前会自动备份到 backup/ 目录 - 升级前会自动备份到 backup/ 目录
- 如果升级失败,运行 rollback.bat 可回滚 - 数据库不由脚本自动备份,请手动导出/导入 SQL
- 如果升级失败,运行 rollback.bat 可回滚前后端程序文件
详细说明请参考README-升级回滚.txt 详细说明请参考根目录 README-升级回滚.txt

View File

@@ -72,9 +72,9 @@
} }
], ],
"win": { "win": {
"icon": "build/icons/icon.ico", "icon": "public/images/icon.ico",
"requestedExecutionLevel": "requireAdministrator", "requestedExecutionLevel": "asInvoker",
"signAndEditExecutable": false, "signAndEditExecutable": true,
"verifyUpdateCodeSignature": false, "verifyUpdateCodeSignature": false,
"artifactName": "${productName}-${os}-${version}-${arch}.${ext}", "artifactName": "${productName}-${os}-${version}-${arch}.${ext}",
"target": [ "target": [
@@ -88,9 +88,9 @@
"oneClick": false, "oneClick": false,
"allowElevation": true, "allowElevation": true,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"installerIcon": "build/icons/icon.ico", "installerIcon": "public/images/icon.ico",
"uninstallerIcon": "build/icons/icon.ico", "uninstallerIcon": "public/images/icon.ico",
"installerHeaderIcon": "build/icons/icon.ico", "installerHeaderIcon": "public/images/icon.ico",
"createDesktopShortcut": true, "createDesktopShortcut": true,
"createStartMenuShortcut": true, "createStartMenuShortcut": true,
"shortcutName": "NPQS9100" "shortcutName": "NPQS9100"

View File

@@ -40,8 +40,8 @@
- 自动更新配置文件 - 自动更新配置文件
#### **2. 盘符自动识别** #### **2. 盘符自动识别**
- 应用在 C 盘 → 数据目录:`C:\NPQS9100_Data\` - 程序目录为纯英文/ASCII路径时数据目录位于应用目录内的 `NPQS9100_Data\`
- 应用在 D 盘 → 数据目录:`D:\NPQS9100_Data\` - 程序目录包含中文或其他非 ASCII 字符时:自动切换到当前盘符根目录下的 `NPQS9100_Runtime\`
- 自动创建所有必要的子目录 - 自动创建所有必要的子目录
#### **3. Loading 界面** #### **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.exe # 主程序(双击运行)⭐
├── NPQS9100-启动器.bat # 管理员启动器(备用)
├── upgrade.bat # 升级工具 ⭐ ├── upgrade.bat # 升级工具 ⭐
├── rollback.bat # 回滚工具 ⭐ ├── rollback.bat # 回滚工具 ⭐
├── uninstall-mysql-service.bat # MySQL 服务卸载工具
├── 使用说明.txt # 使用手册 ├── 使用说明.txt # 使用手册
├── README-升级回滚.txt # 升级说明 ├── README-升级回滚.txt # 升级说明
├── upgrade/ # 升级包目录(首次使用自动创建) ├── upgrade/ # 升级包目录(首次使用自动创建)
├── backup/ # 自动备份目录 ├── backup/ # 自动备份目录
│ ├── dist/ # 前端备份 │ ├── app.asar # 前端主包备份
│ ├── app.asar.unpacked/ # 前端展开目录备份
│ ├── entrance.jar # 后端备份 │ ├── entrance.jar # 后端备份
│ └── version.txt # 版本记录 │ └── version.txt # 版本记录
├── resources/ ├── resources/
@@ -93,13 +96,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB
├── scripts/ # 启动管理脚本(⚠️ 不要修改) ├── scripts/ # 启动管理脚本(⚠️ 不要修改)
│ ├── config-generator.js │ ├── config-generator.js
│ ├── java-runner.js │ ├── java-runner.js
│ ├── mysql-service-manager.js │ ├── mysql-process-manager.js
│ ├── port-checker.js │ ├── port-checker.js
│ └── startup-manager.js │ └── startup-manager.js
├── mysql/ # MySQL 数据库 ├── mysql/ # MySQL 数据库
│ ├── bin/ │ ├── bin/
│ ├── data/ # 数据库数据 ⚠️ 不要动 │ ├── data/ # 数据库数据 ⚠️ 不要动
│ ├── data_backup/ # 自动备份
│ ├── my.ini # 运行时生成 │ ├── my.ini # 运行时生成
│ └── README.txt │ └── README.txt
├── jre/ # Java 运行环境(⚠️ 不要修改) ├── jre/ # Java 运行环境(⚠️ 不要修改)
@@ -110,7 +112,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB
└── upgrade.log └── upgrade.log
用户数据目录(首次运行自动创建): 用户数据目录(首次运行自动创建):
X:\NPQS9100_Data\ (X 是应用所在盘符) - 程序目录为纯英文/ASCII路径时
`应用目录\NPQS9100_Data\`
- 程序目录包含中文或其他非 ASCII 字符时:
`X:\NPQS9100_Runtime\data\` (X 是应用所在盘符)
用户数据目录内包含:
├── logs/ # Spring Boot 日志 ├── logs/ # Spring Boot 日志
├── template/ # 报告模板 ├── template/ # 报告模板
├── report/ # 生成的报告 ├── report/ # 生成的报告
@@ -126,10 +133,8 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 文件 | 功能 | 使用场景 | | 文件 | 功能 | 使用场景 |
|------|------|---------| |------|------|---------|
| **NPQS9100.exe** | 主程序 | 日常启动 | | **NPQS9100.exe** | 主程序 | 日常启动 |
| **NPQS9100-启动器.bat** | 管理员启动器 | 首次运行或权限问题时 |
| **upgrade.bat** | 升级工具 | 收到升级包时使用 | | **upgrade.bat** | 升级工具 | 收到升级包时使用 |
| **rollback.bat** | 回滚工具 | 升级后出问题时使用 | | **rollback.bat** | 回滚工具 | 升级后出问题时使用 |
| **uninstall-mysql-service.bat** | MySQL 服务卸载 | 完全卸载应用时使用 |
| **使用说明.txt** | 使用手册 | 遇到问题时参考 | | **使用说明.txt** | 使用手册 | 遇到问题时参考 |
| **README-升级回滚.txt** | 升级说明 | 升级前阅读 | | **README-升级回滚.txt** | 升级说明 | 升级前阅读 |
@@ -151,9 +156,9 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
| 路径 | 说明 | 备份建议 | | 路径 | 说明 | 备份建议 |
|------|------|---------| |------|------|---------|
| **mysql/data/** | 数据库数据 | ⚠️ 定期备份! | | **应用目录\\mysql\\data/****X:\\NPQS9100_Runtime\\mysql\\data/** | 数据库数据 | ⚠️ 定期备份! |
| **backup/** | 升级备份 | 自动管理,无需手动操作 | | **backup/** | 升级备份 | 自动管理,无需手动操作 |
| **X:\NPQS9100_Data/** | 用户数据目录 | 包含日志、模板、报告 | | **应用目录\\NPQS9100_Data/****X:\\NPQS9100_Runtime\\data/** | 用户数据目录 | 包含日志、模板、报告 |
--- ---
@@ -174,7 +179,7 @@ clean-and-build.bat
3. 构建前端代码Vue 3 3. 构建前端代码Vue 3
4. 编译 Electron 主进程代码 4. 编译 Electron 主进程代码
5. 复制 MySQL、JRE、Java 资源 5. 复制 MySQL、JRE、Java 资源
6. 生成 win-unpacked 绿色版目录 6. 生成 `out/win-unpacked`,再自动重命名为最终交付目录 `out/NPQS-9100`
**等待时间**:约 5-15 分钟 **等待时间**:约 5-15 分钟
@@ -183,7 +188,7 @@ clean-and-build.bat
#### **步骤 2测试运行** #### **步骤 2测试运行**
```bash ```bash
cd out\win-unpacked cd out\NPQS-9100
NPQS9100.exe NPQS9100.exe
``` ```
@@ -200,7 +205,7 @@ NPQS9100.exe
```powershell ```powershell
cd out 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 **压缩后大小**:约 350-450 MB
@@ -237,7 +242,8 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
├─ MySQL 连接localhost:3307 ├─ MySQL 连接localhost:3307
├─ MySQL 密码njcnpqs ⭐ 自动写入 ├─ MySQL 密码njcnpqs ⭐ 自动写入
├─ Java 端口18093 ├─ Java 端口18093
├─ 数据路径:C:\NPQS9100_Data\ ├─ 数据路径:纯英文路径时为 `应用目录\NPQS9100_Data\`
├─ 数据路径:中文路径时为 `X:\NPQS9100_Runtime\data\`
├─ 创建所有必要目录logs、template、report、data ├─ 创建所有必要目录logs、template、report、data
[80%] 启动 Spring Boot 后端 [80%] 启动 Spring Boot 后端
@@ -274,7 +280,7 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
停止 Spring Boot 进程通过PID和端口精确清理 停止 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/ 目录) 1. 先手动导出数据库 SQL 备份
2. 将升级包放入 upgrade/ 目录 2. 双击 upgrade.bat首次自动创建 upgrade/ 目录
3. 再次运行 upgrade.bat自动备份 + 升级) 3. 将升级包放入 upgrade/ 目录
4. 重启应用测试 4. 再次运行 upgrade.bat自动备份前后端程序文件 + 升级)
5. 重启应用测试
``` ```
#### **回滚流程** #### **回滚流程**
``` ```
1. 双击 rollback.bat 1. 双击 rollback.bat
2. 自动从 backup/ 恢复旧版本 2. 自动从 backup/ 恢复前后端程序文件
3. 重启应用 3. 如需恢复数据库,手动执行之前导出的 SQL
4. 重启应用
``` ```
--- ---
@@ -310,23 +318,27 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**第 1 步:准备升级包** **第 1 步:准备升级包**
1. 双击 `upgrade.bat` 1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
2. 脚本自动创建 `upgrade/` 文件夹并提示 2. 双击 `upgrade.bat`
3. 将升级文件放入 `upgrade/` 目录: 3. 脚本自动创建 `upgrade/` 文件夹并提示
- `dist/` - 前端升级包(可选) 4. 将升级文件放入 `upgrade/` 目录:
- `app.asar` + `app.asar.unpacked/` - 前端升级包(可选,必须成套)
- `entrance.jar` - 后端升级包(可选) - `entrance.jar` - 后端升级包(可选)
4. 再次双击 `upgrade.bat` 开始升级 5. 再次双击 `upgrade.bat` 开始升级
**第 2 步:自动升级** **第 2 步:自动升级**
脚本会自动: 脚本会自动:
- ✅ 停止 NPQS9100 进程 - ✅ 停止 NPQS9100 进程
-**自动备份当前版本到 `backup/` 目录** -**自动备份当前前后端程序文件到 `backup/` 目录**
- ✅ 备份数据库到 `mysql/data_backup/`
- ✅ 替换前端文件(如果有) - ✅ 替换前端文件(如果有)
- ✅ 替换后端 JAR如果有 - ✅ 替换后端 JAR如果有
- ✅ 记录升级日志到 `logs/upgrade.log` - ✅ 记录升级日志到 `logs/upgrade.log`
数据库说明:
- ⚠️ 数据库不再由 `upgrade.bat` 自动备份
- ⚠️ 升级前请务必手动导出 SQL 备份
**第 3 步:重启应用** **第 3 步:重启应用**
升级完成后,双击 `NPQS9100.exe` 启动应用。 升级完成后,双击 `NPQS9100.exe` 启动应用。
@@ -340,8 +352,9 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
**升级前端** **升级前端**
```batch ```batch
taskkill /F /IM NPQS9100.exe taskkill /F /IM NPQS9100.exe
rmdir /s /q resources\app.asar.unpacked\public\dist copy /Y upgrade\app.asar resources\app.asar
xcopy upgrade\dist resources\app.asar.unpacked\public\dist\ /E /I /Y 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 - ✅ 停止 NPQS9100
- ✅ 恢复前端(从 `backup/dist/` - ✅ 恢复前端(从 `backup/app.asar``backup/app.asar.unpacked/`
- ✅ 恢复后端(从 `backup/entrance.jar` - ✅ 恢复后端(从 `backup/entrance.jar`
-询问是否恢复数据库(⚠️ 会丢失升级后的数据) -数据库由人工恢复,不再由脚本自动处理
#### **方法 2手动回滚** #### **方法 2手动回滚**
```batch ```batch
# 恢复前端 # 恢复前端
rmdir /s /q resources\app.asar.unpacked\public\dist copy /Y backup\app.asar resources\app.asar
xcopy backup\dist resources\app.asar.unpacked\public\dist\ /E /I /Y 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 copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
# 恢复数据库(⚠️ 谨慎!) # 恢复数据库
rmdir /s /q mysql\data # 使用 Navicat 或其他工具执行升级前导出的 SQL 备份
xcopy mysql\data_backup mysql\data\ /E /I /Y
``` ```
--- ---
@@ -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/NPQS-9100/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` | | **后端** | `out/NPQS-9100/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
| **数据库** | ⚠️ **不升级,自动保留** | - | `mysql/data/` | | **数据库** | 手动导出的 SQL 备份 | 不放入升级包 | 需要时人工导入 |
--- ---
@@ -412,10 +425,11 @@ npm run build-frontend
# 4⃣ 打包应用 # 4⃣ 打包应用
npm run build-w npm run build-w
# 输出: out/win-unpacked/ # 最终交付目录: out/NPQS-9100/
# 原始输出目录: out/win-unpacked/(脚本会自动重命名)
# 5⃣ 准备升级包 # 5⃣ 准备升级包
# 从 out/win-unpacked/resources/ 复制以下文件: # 从 out/NPQS-9100/resources/ 复制以下文件:
# - app.asar (文件) # - app.asar (文件)
# - app.asar.unpacked/ (整个文件夹) # - app.asar.unpacked/ (整个文件夹)
# #
@@ -435,7 +449,7 @@ npm run build-w
# - app.asar.unpacked/ # - app.asar.unpacked/
# 2⃣ 复制到升级目录 # 2⃣ 复制到升级目录
# 将两个文件都放到:win-unpacked/upgrade/ # 将两个文件都放到:NPQS-9100/upgrade/
# - upgrade/app.asar # - upgrade/app.asar
# - upgrade/app.asar.unpacked/ # - upgrade/app.asar.unpacked/
@@ -478,7 +492,7 @@ npm run build-w
# 2⃣ 复制到升级目录 # 2⃣ 复制到升级目录
# 将 entrance.jar 复制到: # 将 entrance.jar 复制到:
# win-unpacked/upgrade/entrance.jar # NPQS-9100/upgrade/entrance.jar
# 3⃣ 运行升级脚本 # 3⃣ 运行升级脚本
# 双击: upgrade.bat # 双击: upgrade.bat
@@ -497,7 +511,7 @@ NPQS9100-升级-v1.1.0.zip
└── entrance.jar # 后端升级包 └── entrance.jar # 后端升级包
# 客户侧:复制到 # 客户侧:复制到
win-unpacked/upgrade/ NPQS-9100/upgrade/
├── app.asar ├── app.asar
├── app.asar.unpacked/ ├── app.asar.unpacked/
└── entrance.jar └── entrance.jar
@@ -511,13 +525,11 @@ win-unpacked/upgrade/
### ✅ 打包验证 ### ✅ 打包验证
- [ ] `out/win-unpacked` 目录存在 - [ ] `out/NPQS-9100` 目录存在
- [ ] 目录大小约 650-800 MB - [ ] 目录大小约 650-800 MB
- [ ] NPQS9100.exe 存在 - [ ] NPQS9100.exe 存在
- [ ] NPQS9100-启动器.bat 存在
- [ ] upgrade.bat 存在 - [ ] upgrade.bat 存在
- [ ] rollback.bat 存在 - [ ] rollback.bat 存在
- [ ] uninstall-mysql-service.bat 存在
- [ ] 使用说明.txt 存在 - [ ] 使用说明.txt 存在
- [ ] README-升级回滚.txt 存在 - [ ] README-升级回滚.txt 存在
- [ ] mysql/ 目录完整 - [ ] mysql/ 目录完整
@@ -602,10 +614,12 @@ win-unpacked/upgrade/
- [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录 - [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录
- [ ] upgrade.bat 检测到升级文件后正常升级 - [ ] upgrade.bat 检测到升级文件后正常升级
- [ ] 升级前自动备份到 backup/ 目录 - [ ] 升级前自动备份到 backup/ 目录
- [ ] 升级前已手动导出数据库 SQL
- [ ] 升级后应用正常运行 - [ ] 升级后应用正常运行
- [ ] 升级后数据库数据保留 - [ ] 升级后数据库数据保留
- [ ] rollback.bat 能正确回滚前端 - [ ] rollback.bat 能正确回滚前端
- [ ] rollback.bat 能正确回滚后端 - [ ] rollback.bat 能正确回滚后端
- [ ] 如需恢复数据库,能手动导入 SQL
- [ ] 回滚后应用恢复正常 - [ ] 回滚后应用恢复正常
--- ---
@@ -710,7 +724,7 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### Q8: 升级后数据丢失? ### Q8: 升级后数据丢失?
**A**: 检查 `mysql/data/` 目录是否完整有备份 `mysql/data_backup/` 恢复 **A**: 检查升级前是否已手动导出数据库 SQL 备份需恢复数据库请使用 Navicat 或其他工具执行该 SQL 备份
--- ---
@@ -728,7 +742,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
**A** **A**
1. 检查 `backup/` 目录是否有备份文件 1. 检查 `backup/` 目录是否有备份文件
2. 查看 `backup/version.txt` 确认备份版本 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**: **A**:
**方法 1使用卸载脚本推荐**
```batch
双击: uninstall-mysql-service.bat
```
**方法 2手动卸载** **当前版本不需要。**
```batch
# 停止服务
net stop mysql9100
# 删除服务 当前版本已经放弃首次使用时把 MySQL 安装成 Windows 服务并设置开机自启的方案改为**绿色包 + 进程模式**
sc delete mysql9100
```
**注意** - 双击 `NPQS9100.exe` 应用会自动启动 `mysqld.exe`
- 卸载服务不会删除数据库数据 - 退出应用时MySQL 进程会自动停止
- 数据保存在 `mysql/data/` 目录 - **无需管理员权限**
- 下次启动应用会重新安装服务 - **不会注册 Windows 服务**
- **不会依赖开机自启**
**为什么改成这样?**
- 旧方案需要管理员权限
- 用户可能直接双击使用导致服务安装失败后应用启动失败
- 也可能出现授权失败赋权不完整等问题
- C 端用户来说不稳定因此改为随应用启停的绿色包进程模式
**数据说明**
- 程序目录为纯英文/ASCII路径时MySQL 数据位于 `应用目录\mysql\data\`
- 程序目录包含中文或其他非 ASCII 字符时MySQL 数据位于 `X:\NPQS9100_Runtime\mysql\data\`
- 当前版本不依赖任何 MySQL Windows 服务
--- ---
@@ -800,8 +820,7 @@ sc delete mysql9100
**开发者文档**doc/ 目录 **开发者文档**doc/ 目录
- `doc/NPQS-9100绿色包完整指南.md` - 本文档完整指南 - `doc/NPQS-9100绿色包完整指南.md` - 本文档完整指南
- `doc/打包前检查清单.md` - 逐项检查 - `doc/打包前检查清单.md` - 逐项检查
- `doc/管理员权限说明.md` - 权限问题处理 - `doc/管理员权限说明.md` - 历史服务模式与当前进程模式说明
- `doc/MySQL服务化方案说明.md` - MySQL 服务管理
--- ---
@@ -812,7 +831,7 @@ sc delete mysql9100
- `scripts/port-checker.js` - 端口检测工具 - `scripts/port-checker.js` - 端口检测工具
- `scripts/startup-manager.js` - 启动状态管理 - `scripts/startup-manager.js` - 启动状态管理
- `scripts/config-generator.js` - 配置文件生成 - `scripts/config-generator.js` - 配置文件生成
- `scripts/mysql-service-manager.js` - MySQL 服务管理器 - `scripts/mysql-process-manager.js` - MySQL 进程管理器
- `scripts/java-runner.js` - Java 运行器 - `scripts/java-runner.js` - Java 运行器
- `scripts/log-window-manager.js` - 日志窗口管理 - `scripts/log-window-manager.js` - 日志窗口管理
- `electron/preload/lifecycle.js` - 生命周期管理 - `electron/preload/lifecycle.js` - 生命周期管理
@@ -827,7 +846,7 @@ StartupManager (显示 Loading)
↓ 调用 ↓ 调用
PortChecker (检测端口) PortChecker (检测端口)
↓ 调用 ↓ 调用
MySQLServiceManager (管理 MySQL 服务) MySQLProcessManager (管理 MySQL 进程)
↓ 调用 ↓ 调用
ConfigGenerator (生成配置) ConfigGenerator (生成配置)
↓ 调用 ↓ 调用
@@ -840,14 +859,14 @@ JavaRunner (启动 Spring Boot)
### 用户体验提升 ### 用户体验提升
- **任务栏只显示1个图标** - Loading 窗口不在任务栏显示 - **任务栏只显示1个图标** - Loading 窗口不在任务栏显示
- **纯绿色版** - 只生成 win-unpacked 目录压缩成 zip 即可发布 - **纯绿色版** - 最终交付目录统一为 `out/NPQS-9100`压缩成 zip 即可发布
- **一键解压即用** - 用户解压后双击即可运行 - **一键解压即用** - 用户解压后双击即可运行
- **热更新机制** - 前后端可独立升级支持一键回滚 - **热更新机制** - 前后端可独立升级支持一键回滚
### 技术改进 ### 技术改进
- **MySQL 密码自动配置** - 配置生成器自动写入密码 - **MySQL 密码自动配置** - 配置生成器自动写入密码
- **权限自动授权** - 支持 localhost 127.0.0.1 访问 - **权限自动授权** - 支持 localhost 127.0.0.1 访问
- **MySQL 服务化** - 使用 Windows 服务管理开机自启 - **MySQL 进程模式** - 随应用启动和退出自动管理无需管理员权限
- **窗口管理优化** - 使用 destroy() 确保窗口完全释放 - **窗口管理优化** - 使用 destroy() 确保窗口完全释放
- **精确进程清理** - 不会误杀其他 Java 进程 - **精确进程清理** - 不会误杀其他 Java 进程
- **自动备份机制** - 升级前自动备份支持回滚 - **自动备份机制** - 升级前自动备份支持回滚

View File

@@ -96,7 +96,7 @@ gen.generateConfig({ mysqlPort: 3307 }).then(console.log);
--- ---
#### `scripts/mysql-service-manager.js` - MySQL 进程管理器(绿色包 - 进程模式) #### `scripts/mysql-process-manager.js` - MySQL 进程管理器(绿色包 - 进程模式)
**作用**:以进程模式管理 MySQL**无需管理员权限**,随应用启动/关闭 **作用**:以进程模式管理 MySQL**无需管理员权限**,随应用启动/关闭
**核心方法** **核心方法**
```javascript ```javascript
@@ -141,8 +141,8 @@ this.mysqlProcess = spawn(mysqldPath, [
**调试方法** **调试方法**
```javascript ```javascript
// 单独测试 // 单独测试
const MySQLServiceManager = require('./scripts/mysql-service-manager'); const MySQLProcessManager = require('./scripts/mysql-process-manager');
const mysql = new MySQLServiceManager(); const mysql = new MySQLProcessManager();
mysql.ensureServiceRunning( mysql.ensureServiceRunning(
(startPort, maxAttempts) => startPort, // Mock port checker (startPort, maxAttempts) => startPort, // Mock port checker
(port, timeout) => Promise.resolve(true) (port, timeout) => Promise.resolve(true)
@@ -363,7 +363,7 @@ await this.checkEnvironment(); // 你的自定义方法
### 场景4修改MySQL进程配置绿色包 - 进程模式) ### 场景4修改MySQL进程配置绿色包 - 进程模式)
**文件**`scripts/mysql-service-manager.js` **文件**`scripts/mysql-process-manager.js`
**修改 my.ini 配置**(推荐方式): **修改 my.ini 配置**(推荐方式):
```javascript ```javascript
@@ -502,7 +502,7 @@ electron/preload/lifecycle.js (启动入口)
├── scripts/port-checker.js (检测端口) ├── scripts/port-checker.js (检测端口)
├── scripts/mysql-service-manager.js (MySQL服务管理) ├── scripts/mysql-process-manager.js (MySQL进程管理)
├── scripts/config-generator.js (生成配置) ├── scripts/config-generator.js (生成配置)
│ ↓ 读取 │ ↓ 读取
@@ -555,7 +555,7 @@ electron/preload/lifecycle.js (启动入口)
|------|------|---------| |------|------|---------|
| 端口检测 | `scripts/port-checker.js` | 全文 | | 端口检测 | `scripts/port-checker.js` | 全文 |
| 启动管理 | `scripts/startup-manager.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 | | Java启动 | `scripts/java-runner.js` | 152-198 |
| 配置生成 | `scripts/config-generator.js` | 38-83 | | 配置生成 | `scripts/config-generator.js` | 38-83 |
| 启动流程 | `electron/preload/lifecycle.js` | 27-159 | | 启动流程 | `electron/preload/lifecycle.js` | 27-159 |

View File

@@ -21,7 +21,7 @@
- [ ] `scripts/port-checker.js` 存在 - [ ] `scripts/port-checker.js` 存在
- [ ] `scripts/startup-manager.js` 存在 - [ ] `scripts/startup-manager.js` 存在
- [ ] `scripts/config-generator.js` 存在 - [ ] `scripts/config-generator.js` 存在
- [ ] `scripts/mysql-service-manager.js` 存在 - [ ] `scripts/mysql-process-manager.js` 存在
- [ ] `scripts/log-window-manager.js` 存在 - [ ] `scripts/log-window-manager.js` 存在
- [ ] `scripts/java-runner.js` 存在 - [ ] `scripts/java-runner.js` 存在
@@ -45,7 +45,7 @@
### MySQL 进程(绿色包 - 进程模式) ### MySQL 进程(绿色包 - 进程模式)
- [ ] MySQL 进程能正常启动(通过 spawn mysqld.exe - [ ] MySQL 进程能正常启动(通过 spawn mysqld.exe
- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,使用进程模式) - [ ] 确认当前版本为进程模式,不依赖 MySQL Windows 服务
- [ ] MySQL 数据库首次启动时自动初始化 - [ ] MySQL 数据库首次启动时自动初始化
- [ ] MySQL 连接密码正确njcnpqs - [ ] MySQL 连接密码正确njcnpqs
- [ ] MySQL 进程随应用退出而自动关闭 - [ ] MySQL 进程随应用退出而自动关闭
@@ -98,9 +98,9 @@ npm run build-w # 打包 Windows 便携版
### 基础测试(绿色包 - 进程模式) ### 基础测试(绿色包 - 进程模式)
- [ ] **无需管理员权限**,普通用户双击 exe 能正常启动 ✅ - [ ] **无需管理员权限**,普通用户双击 exe 能正常启动 ✅
- [ ] Loading 界面正常显示,显示启动步骤 - [ ] Loading 界面正常显示,显示启动步骤
- [ ] ~~MySQL 服务自动安装并启动~~(已废弃) - [ ] 不会尝试安装 MySQL Windows 服务
- [ ] **MySQL 进程自动启动**(任务管理器可见 mysqld.exe - [ ] **MySQL 进程自动启动**(任务管理器可见 mysqld.exe
- [ ] ~~MySQL 服务配置为开机自启~~(已废弃,进程模式随应用启动) - [ ] MySQL 随应用启动,退出后自动停止,不依赖开机自启
- [ ] Spring Boot 自动启动(任务管理器可见 java.exe - [ ] Spring Boot 自动启动(任务管理器可见 java.exe
- [ ] 主界面正常显示 - [ ] 主界面正常显示
- [ ] 应用退出后MySQL 和 Java 进程自动关闭 ✅ - [ ] 应用退出后MySQL 和 Java 进程自动关闭 ✅
@@ -147,8 +147,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
### 检查内容 ### 检查内容
- [ ] 端口检测日志正确 - [ ] 端口检测日志正确
- [ ] MySQL 服务安装/启动日志正常 - [ ] MySQL 进程启动日志正常
- [ ] 日志中显示"服务已配置为开机自启" - [ ] 日志中显示 mysqld.exe 启动与退出清理信息
- [ ] Spring Boot 启动日志正常 - [ ] Spring Boot 启动日志正常
- [ ] 无严重错误 - [ ] 无严重错误
@@ -185,7 +185,6 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
- 检查端口是否被占用应自动切换到其他端口 - 检查端口是否被占用应自动切换到其他端口
- 查看应用日志`%APPDATA%/NQPS9100/logs/9100.log` - 查看应用日志`%APPDATA%/NQPS9100/logs/9100.log`
- 查看 MySQL 错误日志`mysql/data/*.err` - 查看 MySQL 错误日志`mysql/data/*.err`
- ~~使用 sc query mysql9100 检查服务状态~~已废弃不再使用服务
- 检查是否有残留的 mysqld.exe 进程任务管理器 - 检查是否有残留的 mysqld.exe 进程任务管理器
### Spring Boot 启动失败 ### Spring Boot 启动失败

View File

@@ -1,58 +1,27 @@
# 管理员权限说明 # 管理员权限说明
> ⚠️ **文档已过期** - 本文档描述的是旧版服务模式,自 2025-12-01 起已改用**进程模式(绿色包)**。 当前版本采用**绿色包 + MySQL 进程模式****不需要管理员权限**。
>
> 🎉 **新版本不再需要管理员权限!**
>
> 参考最新文档:`MySQL进程模式改造方案.md` 和 `NPQS-9100绿色包完整指南.md`
--- ## 当前结论
## ~~🔐 为什么需要管理员权限?~~(已废弃) 1. 双击 `NPQS9100.exe` 即可启动
2. 无需管理员权限
3. 无 UAC 弹窗
4. 解压即用,完全绿色
5. 仅在应用运行期间启动 MySQL
~~NPQS9100 需要管理 MySQL Windows 服务,执行以下操作:~~ ## 说明
- ~~✅ 安装 MySQL 服务 (`mysql9100`)~~
- ~~✅ 启动/停止服务~~
- ~~✅ 删除服务(重新安装时)~~
~~这些操作都需要 **Windows 管理员权限**。~~ - MySQL 和 Spring Boot 会在应用启动时自动启动
- 退出应用时自动清理
- 不注册 Windows 服务
- 不依赖开机自启
**现状**:已改用进程模式,直接启动 `mysqld.exe` 进程,无需注册 Windows 服务,**完全不需要管理员权限**。 ## 为什么这样设计
## 🎉 新版启动方式(进程模式) - 需要管理员权限的启动方式对 C 端用户不稳定
- 用户直接双击使用时,提权、授权或服务安装失败会导致启动失败
**当前版本启动非常简单** - 因此当前版本统一采用无需授权的绿色包进程模式
1. ✅ 双击 `NPQS9100.exe` 即可启动
2. ✅ 无需管理员权限
3. ✅ 无 UAC 弹窗
4. ✅ 解压即用,完全绿色
**MySQL 和 Spring Boot 会在应用启动时自动启动,退出时自动清理。**
---
## ~~以下内容已废弃(保留作为历史参考)~~
<details>
<summary>点击展开查看旧版(服务模式)说明</summary>
### ~~两种启动方式(已废弃)~~
#### ~~方式 1使用启动器推荐~~
~~打包后的目录中有一个 `NPQS9100-启动器.bat` 文件。~~
#### ~~方式 2手动以管理员身份运行~~
~~右键点击 `NPQS9100.exe` 选择"以管理员身份运行"~~
### ~~常见问题(已废弃)~~
~~Q: MySQL 服务安装后还需要管理员权限吗?~~
A: **新版本不使用 Windows 服务,因此完全不需要管理员权限。**
</details>
--- ---

View File

@@ -45,11 +45,11 @@ function getScriptsPath(scriptName) {
} }
// 延迟加载 scripts // 延迟加载 scripts
let MySQLServiceManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager; let MySQLProcessManager, JavaRunner, ConfigGenerator, PortChecker, StartupManager, LogWindowManager;
function loadScripts() { function loadScripts() {
if (!MySQLServiceManager) { if (!MySQLProcessManager) {
MySQLServiceManager = require(getScriptsPath('mysql-service-manager')); MySQLProcessManager = require(getScriptsPath('mysql-process-manager'));
JavaRunner = require(getScriptsPath('java-runner')); JavaRunner = require(getScriptsPath('java-runner'));
ConfigGenerator = require(getScriptsPath('config-generator')); ConfigGenerator = require(getScriptsPath('config-generator'));
PortChecker = require(getScriptsPath('port-checker')); PortChecker = require(getScriptsPath('port-checker'));
@@ -60,7 +60,7 @@ function loadScripts() {
class Lifecycle { class Lifecycle {
constructor() { constructor() {
this.mysqlServiceManager = null; this.mysqlProcessManager = null;
this.javaRunner = null; this.javaRunner = null;
this.startupManager = null; this.startupManager = null;
this.logWindowManager = null; this.logWindowManager = null;
@@ -94,26 +94,26 @@ class Lifecycle {
this.startupManager.updateProgress('init'); this.startupManager.updateProgress('init');
await this.sleep(500); await this.sleep(500);
// 步骤2-4: 确保 MySQL 服务运行 // 步骤2-4: 确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...'); this.logWindowManager.addLog('system', '▶ 步骤4: 检查 MySQL 配置...');
logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart); logger.info('[lifecycle] MySQL config check - enable:', config.mysql?.enable, 'autoStart:', config.mysql?.autoStart);
if (config.mysql && config.mysql.enable && config.mysql.autoStart) { if (config.mysql && config.mysql.enable && config.mysql.autoStart) {
this.startupManager.updateProgress('check-mysql-port'); 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.mysqlProcessManager = new MySQLProcessManager(this.logWindowManager);
this.logWindowManager.addLog('system', '正在检查 MySQL 服务状态...'); this.logWindowManager.addLog('system', '正在检查 MySQL 进程状态...');
try { try {
// 使用服务管理器确保MySQL服务运行 // 使用进程管理器确保 MySQL 进程运行
this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 服务运行中...'); this.logWindowManager.addLog('system', '▶ 步骤6: 确保 MySQL 进程运行中...');
this.mysqlPort = await this.mysqlServiceManager.ensureServiceRunning( this.mysqlPort = await this.mysqlProcessManager.ensureServiceRunning(
PortChecker.findAvailablePort.bind(PortChecker), PortChecker.findAvailablePort.bind(PortChecker),
PortChecker.waitForPort.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.logWindowManager.addLog('success', `✓ MySQL 服务已就绪,端口: ${this.mysqlPort}`);
this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort }); this.startupManager.updateProgress('wait-mysql', { mysqlPort: this.mysqlPort });
await this.sleep(500); await this.sleep(500);
@@ -201,7 +201,7 @@ class Lifecycle {
dataPath: dataPath dataPath: dataPath
}); });
await this.startSpringBoot(configPath); await this.startSpringBoot(configPath, dataPath);
// 步骤8: 等待 Spring Boot 就绪 // 步骤8: 等待 Spring Boot 就绪
this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪最多60秒...'); this.logWindowManager.addLog('system', '▶ 步骤11: 等待 Spring Boot 就绪最多60秒...');
@@ -376,14 +376,14 @@ class Lifecycle {
} }
// 停止 MySQL 进程(进程模式) // 停止 MySQL 进程(进程模式)
if (this.mysqlServiceManager) { if (this.mysqlProcessManager) {
try { try {
logger.info('[lifecycle] Stopping MySQL process...'); logger.info('[lifecycle] Stopping MySQL process...');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
this.logWindowManager.addLog('system', '正在停止 MySQL...'); this.logWindowManager.addLog('system', '正在停止 MySQL...');
} }
await this.mysqlServiceManager.stopMySQLProcess(); await this.mysqlProcessManager.stopMySQLProcess();
logger.info('[lifecycle] MySQL process stopped'); logger.info('[lifecycle] MySQL process stopped');
if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) { if (this.logWindowManager && this.logWindowManager.logWindow && !this.logWindowManager.logWindow.isDestroyed()) {
@@ -419,7 +419,7 @@ class Lifecycle {
/** /**
* 启动 Spring Boot 应用 * 启动 Spring Boot 应用
*/ */
async startSpringBoot(configPath) { async startSpringBoot(configPath, dataPath) {
try { try {
logger.info('[lifecycle] Starting Spring Boot application...'); logger.info('[lifecycle] Starting Spring Boot application...');
this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...'); this.logWindowManager.addLog('java', '正在启动 Spring Boot 应用...');
@@ -434,12 +434,6 @@ class Lifecycle {
? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar') ? path.join(__dirname, '..', 'build', 'extraResources', 'java', 'entrance.jar')
: path.join(process.resourcesPath, '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 logPath = path.join(dataPath, 'logs');
const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, { const javaProcess = this.javaRunner.runSpringBoot(jarPath, configPath, {

View File

@@ -19,9 +19,9 @@ VITE_API_URL=/api
# 开发环境跨域代理,支持配置多个 # 开发环境跨域代理,支持配置多个
VITE_PROXY=[["/api","http://127.0.0.1:18092/"]] VITE_PROXY=[["/api","http://127.0.0.1:18093/"]]
#VITE_PROXY=[["/api","http://192.168.1.124:18092/"]] #VITE_PROXY=[["/api","http://192.168.1.124:18092/"]]
#VITE_PROXY=[["/api","http://192.168.2.125:18092/"]] #VITE_PROXY=[["/api","http://192.168.2.125:18092/"]]
# VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文 # VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文
# 开启激活验证 # 开启激活验证
VITE_ACTIVATE_OPEN=false VITE_ACTIVATE_OPEN=true

View File

@@ -23,6 +23,6 @@ VITE_PWA=true
# 线上环境接口地址 # 线上环境接口地址
#VITE_API_URL="/api" # 打包时用 #VITE_API_URL="/api" # 打包时用
VITE_API_URL="http://127.0.0.1:18092/" VITE_API_URL="http://127.0.0.1:18093/"
# 开启激活验证 # 开启激活验证
VITE_ACTIVATE_OPEN=false VITE_ACTIVATE_OPEN=true

View File

@@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "0.0.1", "version": "2.0.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -261,7 +261,7 @@ const dictStore = useDictStore()
const activeName = ref('') const activeName = ref('')
const childActiveName = ref('') const childActiveName = ref('')
const childActiveIndex = ref(0) const childActiveIndex = ref(0)
const firstName = 'first' const firstName = ref('first')
const viewRowRef = ref() const viewRowRef = ref()
const communicationList = ref<[]>([]) const communicationList = ref<[]>([])
const testProjectPopupRef = ref() const testProjectPopupRef = ref()

View File

@@ -260,7 +260,7 @@ const viewDialog = ref(false)
const dictStore = useDictStore() const dictStore = useDictStore()
const activeName = ref('') const activeName = ref('')
const childActiveName = ref('') const childActiveName = ref('')
const firstName = 'first' const firstName = ref('first')
const viewRowRef = ref() const viewRowRef = ref()
const communicationList = ref([]) const communicationList = ref([])
const testProjectPopupRef = ref() const testProjectPopupRef = ref()

View File

@@ -1,5 +1,6 @@
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const { resolveRuntimeStrategy } = require('./path-utils');
/** /**
* 配置文件生成器 * 配置文件生成器
@@ -8,11 +9,12 @@ const path = require('path');
class ConfigGenerator { class ConfigGenerator {
constructor() { constructor() {
// 开发环境:项目根目录 // 开发环境:项目根目录
// 打包后:应用根目录(win-unpacked // 打包后:应用根目录(最终交付目录
const isDev = !process.resourcesPath; const isDev = !process.resourcesPath;
const baseDir = isDev const baseDir = isDev
? path.join(__dirname, '..') ? path.join(__dirname, '..')
: path.dirname(process.resourcesPath); : path.dirname(process.resourcesPath);
const pathStrategy = resolveRuntimeStrategy(baseDir);
// 开发环境build/extraResources/java // 开发环境build/extraResources/java
// 打包后resources/extraResources/java // 打包后resources/extraResources/java
@@ -21,9 +23,12 @@ class ConfigGenerator {
: path.join(process.resourcesPath, 'extraResources', 'java'); : path.join(process.resourcesPath, 'extraResources', 'java');
this.templatePath = path.join(this.javaPath, 'application.yml.template'); this.templatePath = path.join(this.javaPath, 'application.yml.template');
this.configPath = path.join(this.javaPath, 'application.yml'); 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 应用基础目录 * @param {string} baseDir 应用基础目录
* @returns {string} 数据目录路径 * @returns {string} 数据目录路径
*/ */
getDataPath(baseDir) { getDataPath(baseDir, pathStrategy = resolveRuntimeStrategy(baseDir)) {
// 数据目录设置在应用目录内的 NPQS9100_Data 文件夹 if (pathStrategy.usesSafePaths) {
return path.join(pathStrategy.safeRuntimeRoot, 'data');
}
return path.join(baseDir, 'NPQS9100_Data'); return path.join(baseDir, 'NPQS9100_Data');
} }
@@ -85,6 +93,7 @@ class ConfigGenerator {
this.createDirectories(); this.createDirectories();
console.log('[ConfigGenerator] Configuration file generated successfully'); 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] Data path:', this.dataPath);
console.log('[ConfigGenerator] MySQL port:', options.mysqlPort || 3306); console.log('[ConfigGenerator] MySQL port:', options.mysqlPort || 3306);
console.log('[ConfigGenerator] MySQL password:', options.mysqlPassword || 'njcnpqs'); console.log('[ConfigGenerator] MySQL password:', options.mysqlPassword || 'njcnpqs');

View File

@@ -9,7 +9,7 @@ class JavaRunner {
constructor() { constructor() {
// 在开发与打包后均可解析到应用根目录下的 jre 目录 // 在开发与打包后均可解析到应用根目录下的 jre 目录
// 开发环境:项目根目录 // 开发环境:项目根目录
// 打包后:应用根目录(win-unpacked // 打包后:应用根目录(最终交付目录
const isDev = !process.resourcesPath; const isDev = !process.resourcesPath;
const baseDir = isDev const baseDir = isDev
? path.join(__dirname, '..') ? path.join(__dirname, '..')

View File

@@ -1,19 +1,26 @@
const { spawn, exec } = require('child_process'); const { spawn, exec } = require('child_process');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const { resolveRuntimeStrategy } = require('./path-utils');
/** /**
* MySQL 进程管理器 * MySQL 进程管理器
* 使用进程模式启动 MySQL无需管理员权限 * 使用进程模式启动 MySQL无需管理员权限
*/ */
class MySQLServiceManager { class MySQLProcessManager {
constructor(logWindowManager = null) { constructor(logWindowManager = null) {
const isDev = !process.resourcesPath; const isDev = !process.resourcesPath;
const baseDir = isDev const baseDir = isDev
? path.join(__dirname, '..') ? path.join(__dirname, '..')
: path.dirname(process.resourcesPath); : 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.binPath = path.join(this.mysqlPath, 'bin');
this.dataPath = path.join(this.mysqlPath, 'data'); this.dataPath = path.join(this.mysqlPath, 'data');
this.configFile = path.join(this.mysqlPath, 'my.ini'); 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 * 执行命令并返回Promise
*/ */
@@ -352,7 +459,10 @@ default-character-set=utf8mb4
* @returns {Promise<number>} 返回 MySQL 运行的端口 * @returns {Promise<number>} 返回 MySQL 运行的端口
*/ */
async ensureServiceRunning(findAvailablePort, waitForPort) { async ensureServiceRunning(findAvailablePort, waitForPort) {
this.ensureRuntimeEnvironment();
this.log('system', '开始 MySQL 进程检查流程(进程模式)'); this.log('system', '开始 MySQL 进程检查流程(进程模式)');
this.log('system', `路径策略: ${this.pathStrategy.usesSafePaths ? '英文安全路径模式' : '应用目录直启模式'}`);
this.log('system', `MySQL 路径: ${this.mysqlPath}`); this.log('system', `MySQL 路径: ${this.mysqlPath}`);
this.log('system', `配置文件: ${this.configFile}`); this.log('system', `配置文件: ${this.configFile}`);
@@ -401,4 +511,4 @@ default-character-set=utf8mb4
} }
} }
module.exports = MySQLServiceManager; module.exports = MySQLProcessManager;

41
scripts/path-utils.js Normal file
View File

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