16 Commits

Author SHA1 Message Date
65ac4b1dde 修复C端打包后的程序图标不显示logo的问题 2026-04-02 21:00:12 +08:00
926b85bf8d C端打包修复不能在中文路径下启动的问题 2026-04-02 20:51:19 +08:00
caozehui
ad02fac4ff 比对模式根据配置文件动态展示原始数据、历史趋势图 2026-02-02 10:14:41 +08:00
caozehui
e4de4fe43c 微调 2026-01-27 11:06:31 +08:00
caozehui
bc7d6b24b7 微调 2026-01-23 11:27:42 +08:00
caozehui
3de7461960 微调 2026-01-23 11:24:47 +08:00
caozehui
bff94f4521 微调 2026-01-23 11:20:03 +08:00
caozehui
e63a3dfa92 数据库变更 2026-01-22 15:42:02 +08:00
caozehui
7efb738d54 微调 2026-01-22 15:25:30 +08:00
caozehui
84763eb414 微调 2026-01-22 11:26:42 +08:00
caozehui
820a6bb233 系数调整 2026-01-21 13:59:57 +08:00
caozehui
31e44cfc69 比对系数下发全局配置 2026-01-19 14:22:24 +08:00
caozehui
fb9460e307 Merge remote-tracking branch 'origin/master' 2026-01-16 10:46:11 +08:00
caozehui
99a8c97b17 比对系数下发全局配置 2026-01-16 10:46:00 +08:00
caozehui
c30569f5e1 比对系数下发全局配置 2026-01-16 10:44:41 +08:00
caozehui
cdc19e1db9 更新后端jar包 2026-01-06 10:29:29 +08:00
91 changed files with 1382 additions and 1180 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 文件夹)
2. 将升级文件放入 upgrade/ 目录:
- app.asar (前端升级包 - 文件)
- app.asar.unpacked\ (前端升级包 - 文件夹
1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
2. 双击 upgrade.bat首次会自动创建 upgrade 文件夹)
3. 将升级文件放入 upgrade/ 目录:
- app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
- entrance.jar (后端升级包)
3. 再次双击 upgrade.bat 开始升级
4. 等待完成后重启应用
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
如需完整技术文档,请联系交付方提供开发文档。
========================================

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 输出目录: out\win-unpacked\
echo 最终交付目录: out\NPQS-9100\
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
echo ========================================
echo.
pause

View File

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

View File

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

View File

@@ -115,3 +115,4 @@ activate:
private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"

View File

@@ -1 +1 @@
34820
74476

Binary file not shown.

View File

@@ -6,3 +6,4 @@
.\binlog.000028
.\binlog.000029
.\binlog.000030
.\binlog.000031

View File

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

View File

@@ -1,5 +1,6 @@
@echo off
chcp 65001 >nul
setlocal
echo ========================================
echo NPQS9100 升级工具
echo ========================================
@@ -11,8 +12,7 @@ if not exist upgrade (
echo 【首次使用】已自动创建 upgrade 目录
echo.
echo 请将升级文件放入 upgrade 目录:
echo - app.asar (前端升级包 - 文件
echo - app.asar.unpacked\ (前端升级包 - 文件夹)
echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - entrance.jar (后端升级包)
echo.
echo 放置完成后,重新运行本脚本即可升级
@@ -21,18 +21,50 @@ 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 - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入
echo - entrance.jar (后端升级包)
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,18 +99,20 @@ if %errorlevel% equ 0 (
)
echo.
echo [2/6] 备份当前版本(用于回滚)...
echo [2/5] 备份当前版本(用于回滚)...
if not exist backup mkdir backup
REM 备份前端(app.asar 和 app.asar.unpacked
REM 备份前端(仅在执行前端升级时
if %upgradeFrontend%==1 (
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 备份失败
pause
exit /b 1
)
)
@@ -89,38 +126,27 @@ if exist resources\app.asar.unpacked (
echo app.asar.unpacked 备份完成
) else (
echo app.asar.unpacked 备份失败
pause
exit /b 1
)
)
)
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

View File

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

View File

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

View File

@@ -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
```
**方法 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 进程
- **自动备份机制** - 升级前自动备份支持回滚

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**无需管理员权限**,随应用启动/关闭
**核心方法**
```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 |

View File

@@ -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 启动失败

View File

@@ -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 会在应用启动时自动启动,退出时自动清理。**
---
## ~~以下内容已废弃(保留作为历史参考)~~
<details>
<summary>点击展开查看旧版(服务模式)说明</summary>
### ~~两种启动方式(已废弃)~~
#### ~~方式 1使用启动器推荐~~
~~打包后的目录中有一个 `NPQS9100-启动器.bat` 文件。~~
#### ~~方式 2手动以管理员身份运行~~
~~右键点击 `NPQS9100.exe` 选择"以管理员身份运行"~~
### ~~常见问题(已废弃)~~
~~Q: MySQL 服务安装后还需要管理员权限吗?~~
A: **新版本不使用 Windows 服务,因此完全不需要管理员权限。**
</details>
- 需要管理员权限的启动方式对 C 端用户不稳定
- 用户直接双击使用时,提权、授权或服务安装失败会导致启动失败
- 因此当前版本统一采用无需授权的绿色包进程模式
---

View File

@@ -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, {

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: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_ACTIVATE_OPEN=false

View File

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

View File

@@ -14,7 +14,8 @@ export const getScriptList = (params: {
devId:string,
chnNum:number,
num:number,
planId:string
planId:string,
code:string
}) => {
return http.post('/result/getCheckItem', params, {loading: false})
}

View File

@@ -35,9 +35,25 @@ export namespace StandardDevice {
updateBy?: string | null; //更新用户
updateTime?: string | null; //更新时间
disabled?: boolean;
gainList?:Gain[]
}
interface Gain{
stdDevMonitorId:string;
uaGain:number;
ubGain:number;
ucGain:number;
u0Gain:number;
iaGain:number;
ibGain:number;
icGain:number;
i0Gain:number;
uabGain:number;
ubcGain:number;
ucaGain:number;
}
/**
* 标准设备表格查询分页返回的对象;
*/

View File

@@ -14,6 +14,7 @@ export namespace Base {
createTime?: string| null; //创建时间
updateBy?: string| null; //更新用户
updateTime?: string| null; //更新时间
coefficient:number //比对监测后,当电压、电流不符合时,是否对标准设备进行系数校准
}
}

View File

@@ -57,3 +57,14 @@ export const getPublicKey = (username: string) => {
export const getAutoGenerate = () => {
return http.get('/sysTestConfig/getAutoGenerate', {}, {loading: false})
}
/**
* 获取比对模式下是否能够进行系数校准
*/
export const getCanCoefficient=()=>{
return http.get('/prepare/canCoefficient', {}, {loading: false})
}
export const startCoefficient=()=>{
return http.get('/prepare/startCoefficient', {}, {loading: false})
}

View File

@@ -190,7 +190,7 @@ export default class SocketService {
* WebSocket连接配置
*/
private config: SocketConfig = {
url: 'ws://127.0.0.1:7777/hello',
url: 'ws://127.0.0.1:7778/hello',
//url: 'ws://192.168.1.124:7777/hello',
heartbeatInterval: 9000, // 9秒心跳间隔
reconnectDelay: 5000, // 5秒重连延迟

View File

@@ -165,7 +165,7 @@
:currentScriptTypeName="currentScriptTypeName"
/>
</el-tab-pane>
<el-tab-pane label="原始数据" name="rawDataTab">
<el-tab-pane label="原始数据" name="rawDataTab" v-if="!isBusiness">
<CompareDataCheckRawDataTable
v-if="activeTab === 'rawDataTab'"
:tableData="rawTableData.length == 0 ? [] : currentRawTableData"
@@ -173,7 +173,7 @@
:currentScriptTypeName="currentScriptTypeName"
/>
</el-tab-pane>
<el-tab-pane label="历史趋势" name="chartTab" v-if="!isWaveData">
<el-tab-pane label="历史趋势" name="chartTab" v-if="!isWaveData&&!isBusiness">
<CompareDataCheckChart
v-if="activeTab === 'chartTab'"
:tableData="rawTableData.length == 0 ? [] : currentRawTableData"
@@ -198,17 +198,12 @@ import { Histogram, Postcard } from '@element-plus/icons-vue'
import {generateDevReport, getPqErrSysList} from '@/api/plan/plan'
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
import {useDictStore} from '@/stores/modules/dict'
import {
changeErrorSystem,
deleteTempTable,
getContrastFormContent,
getContrastResult,
getScriptList,
reCalculate
} from '@/api/check/test'
import {changeErrorSystem, deleteTempTable, getContrastFormContent, getContrastResult, getScriptList, reCalculate} from '@/api/check/test'
import {ElMessage} from 'element-plus'
import {ResultEnum} from '@/enums/httpEnum'
const isShowRawData = import.meta.env.VITE_IS_SHOW_RAW_DATA
const {appendToBody = true} = defineProps<{
appendToBody: boolean
}>()
@@ -275,6 +270,11 @@ const currentRawTableData = computed(() => {
return Array.isArray(data) ? data : []
})
const isBusiness = computed(() => {
console.log(isShowRawData)
return modeStore.currentMode === '比对式' && isShowRawData
})
const open = async (row: any, chnNum: string, deviceId: string | null, source: number) => {
activeTab.value = 'resultTab'
isWaveData.value = false
@@ -344,7 +344,8 @@ const initScriptData = async () => {
devId: formContent.deviceId,
chnNum: formContent.chnNum,
num: formContent.num,
planId: checkStore.plan.id
planId: checkStore.plan.id,
code: checkStore.plan.code + (formContent.errorSysId != checkStore.plan.errorSysId || formContent.dataRule != checkStore.plan.dataRule ? '_temp' : '')
})
// 格式化脚本数据
@@ -688,6 +689,7 @@ defineExpose({
overflow-y: auto;
overflow-x: auto;
margin-right: 10px;
.content-tree {
width: 100%;
height: 100%;
@@ -724,10 +726,12 @@ defineExpose({
margin-top: 10px;
margin-bottom: 10px;
display: flex;
.el-tabs {
width: 100%;
}
}
.content-left {
height: 100%;
border: 1px solid #e0e0e0;
@@ -739,6 +743,7 @@ defineExpose({
}
}
}
:deep(.el-tabs--border-card > .el-tabs__content) {
height: 367px;
}

View File

@@ -128,7 +128,7 @@ import {CheckData} from '@/api/check/interface'
import {useCheckStore} from '@/stores/modules/check'
import {ElMessage, ElMessageBox} from 'element-plus'
import {getBigTestItem} from '@/api/check/test'
import {getAutoGenerate} from '@/api/user/login'
import {getAutoGenerate, getCanCoefficient, startCoefficient} from '@/api/user/login'
import { generateDevReport } from '@/api/plan/plan'
import {useModeStore} from '@/stores/modules/mode' // 引入模式 store
import {useDictStore} from '@/stores/modules/dict'
@@ -157,11 +157,6 @@ const props = defineProps({
const emit = defineEmits([
'update:testStatus',
'update:webMsgSend',
'sendPause',
'sendResume',
'sendReCheck',
'closeWebSocket'
])
// 用来保存测试项进度抽屉是否打开
@@ -684,6 +679,20 @@ const updatePercentage = async () => {
})
}
stopTimeCount(1)
let { data: canCoefficient } = await getCanCoefficient()
if(canCoefficient) {
ElMessageBox.confirm('存在系数偏差,是否进行系数校准?', '系数校准', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async ()=>{
// 调用系数校准接口
await startCoefficient()
ElMessage.success({ message: '系数下发中,请稍等...', duration: 15000 ,onClose() {
ElMessage.success('系数下发成功!')
},})
}).catch(()=>{
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
@@ -691,9 +700,16 @@ const updatePercentage = async () => {
confirmButtonText: '确定'
}
)
// 关闭WebSocket连接
emit('closeWebSocket')
//clear();
})
}else{
ElMessageBox.alert(
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
'检测完成',
{
confirmButtonText: '确定'
}
)
}
}
}
// ========== 时间计数器管理函数 ==========
@@ -862,7 +878,6 @@ const handleClick = (item: any, chnNum: number, scriptType: string) => {
}
const handlePause = () => {
//emit('sendPause')
testLogList.push({
type: 'error',
log: `${new Date().toLocaleString()}:当前测试小项正在执行中,将在该小项执行结束后暂停...`

View File

@@ -539,6 +539,7 @@ const columns = reactive<ColumnProps<Device.ResPqDev>[]>([
{ prop: 'operation', label: '操作', fixed: 'right', minWidth :200,isShow: operationShow }
])
let testType = 'test' // 检测类型:'test'-检测 'reTest'-复检
let qualifiedCount = 0 //合格数量
//比对单个报告生成
@@ -568,12 +569,14 @@ const handleSelectionChange = (selection: any[]) => {
}
// 统计已完成检测的设备数量
const result = selection.filter(item => item.checkResult != 0)
let result = selection.filter(item => item.checkState == 0)
if (result.length > 0) {
testType = 'test'
} else {
testType = 'reTest'
}
qualifiedCount=selection.filter(item => item.checkResult == 1).length
let devices: CheckData.Device[] = selection.map((item: any) => {
return {
deviceId: item.id,
@@ -925,6 +928,7 @@ const handleTest = async (val: string) => {
distinguishCancelAndClose: true,
confirmButtonText: '不合格项复检',
cancelButtonText: '全部复检',
showConfirmButton:qualifiedCount<=0,
type: 'warning'
})
.then(() => {

View File

@@ -1,7 +1,7 @@
<template>
<el-dialog :title="dialogTitle" v-model='dialogVisible' @close="close" v-bind="dialogBig" align-center>
<el-tabs type="border-card">
<el-tab-pane label="设备台账信息">
<el-tabs type="border-card" v-model="activeName">
<el-tab-pane label="设备台账信息" name="devInfo">
<div>
<el-form :model='formContent' ref='dialogFormRef' :rules='rules' :disabled="false" label-width="auto" class="form-three">
<el-divider>设备信息</el-divider>
@@ -72,6 +72,47 @@
</el-form>
</div>
</el-tab-pane>
<el-tab-pane label="通道系数" name="gainInfo">
<el-tabs type="border-card" style="margin: 15px 0px;">
<el-tab-pane v-for="(item, index) in formContent.gainList" :label="'通道'+(index+1)">
<el-form :model='item' ref='gainFormRef' :disabled="true" class="form-four">
<el-form-item label-width="30" label-position="left" label="ua" prop="uaGain">
<el-input v-model="item.uaGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ub" prop="ubGain">
<el-input v-model="item.ubGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uc" prop="ucGain">
<el-input v-model="item.ucGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="u0" prop="u0Gain">
<el-input v-model="item.u0Gain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ia" prop="iaGain">
<el-input v-model="item.iaGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ib" prop="ibGain">
<el-input v-model="item.ibGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ic" prop="icGain">
<el-input v-model="item.icGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="i0" prop="i0Gain">
<el-input v-model="item.i0Gain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uab" prop="uabGain">
<el-input v-model="item.uabGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="ubc" prop="uabGain">
<el-input v-model="item.ubcGain"/>
</el-form-item>
<el-form-item label-width="30" label-position="left" label="uca" prop="ucaGain">
<el-input v-model="item.ucaGain"/>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</el-tab-pane>
</el-tabs>
<template #footer>
<div>
@@ -84,17 +125,14 @@
</template>
<script setup lang='ts'>
import IPAddress from '@/components/IpAddress/index.vue'
import {dialogBig} from '@/utils/elementBind'
import {type StandardDevice} from '@/api/device/interface/standardDevice.ts'
import {ElMessage, type FormItemRule} from 'element-plus'
import {addPqStandardDev, updatePqStandardDev} from '@/api/device/standardDevice/index.ts'
import {computed, reactive, type Ref, ref} from 'vue'
import {useDictStore} from '@/stores/modules/dict'
import { CirclePlus, Delete, EditPen } from '@element-plus/icons-vue'
import {type Device} from '@/api/device/interface/device.ts'
// 使用 dayjs 库格式化
import dayjs from 'dayjs'
// 存储设备类型选项
const devTypeOptions = ref<Device.ResDev[]>([])
@@ -120,6 +158,8 @@ const pqChannelArray = ref([
label: '4',
},
])
const activeName=ref('devInfo')
function useMetaInfo() {
const dialogVisible = ref(false)
@@ -259,12 +299,14 @@ const pqChannelArray = ref([
// 打开弹窗,可能是新增,也可能是编辑
const open = async (sign: string, data: StandardDevice.ResPqStandardDevice, devType: Device.ResDev[]) => {
activeName.value = 'devInfo'
// 重置表单
dialogFormRef.value?.resetFields()
devTypeOptions.value = devType
titleType.value = sign
if (data.id) {
Object.assign(formContent, {...data})
console.log(formContent)
if (typeof formContent.inspectChannel === 'string') {
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
}

View File

@@ -99,13 +99,11 @@ const handleInputRetainTime = value => {
ElMessage.warning("持续时间不能小于0周波")
props.childForm[0].dipData.retainTime = 0
emit('setRetainTime', 0 )
}else if (value > 300) {
ElMessage.warning("持续时间不能大于300周波")
props.childForm[0].dipData.retainTime = 300
emit('setRetainTime', 300 )
}else if (value > 3000) {
ElMessage.warning("持续时间不能大于3000周波")
props.childForm[0].dipData.retainTime = 3000
emit('setRetainTime', 3000 )
}else{
emit('setRetainTime', value )
}
}

View File

@@ -342,7 +342,7 @@
v-model="formContent.testConfig!.realTime"
:precision="0"
:step="1"
:min="40"
:min="20"
placeholder="请输入实时数据有效组数"
/>
</el-form-item>

View File

@@ -25,6 +25,11 @@
<el-input number v-model.number='TestConfigForm.scale' placeholder="请输入误差保留小数位" onkeypress="return (/[\d]/.test(String.fromCharCode(event.keyCode)))"/>
</el-form-item>
</el-col>
<el-col :span="8" v-if="modeStore.currentMode=='比对式'">
<el-form-item label='是否进行系数校准' prop='coefficient' :label-width="140">
<el-switch v-model="TestConfigForm.coefficient" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
</el-form-item>
</el-col>
<!-- <el-col :span="8">-->
<!-- <el-form-item label='数据处理原则' prop='dataRule' :label-width="140">-->
<!-- <el-select v-model="TestConfigForm.dataRule" clearable placeholder="请选择数据处理原则">-->
@@ -235,6 +240,7 @@ const TestConfigForm = ref<Base.ResTestConfig>({
maxTime: 3,
state: 1, //状态
scale:4,
coefficient:0,
})
const RegResForm = ref<VersionRegister.ResSys_Reg_Res>({

View File

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

View File

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

View File

@@ -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<number>} 返回 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;

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