Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65ac4b1dde | |||
| 926b85bf8d | |||
|
|
ad02fac4ff | ||
|
|
e4de4fe43c | ||
|
|
bc7d6b24b7 | ||
|
|
3de7461960 | ||
|
|
bff94f4521 | ||
|
|
e63a3dfa92 | ||
|
|
7efb738d54 | ||
|
|
84763eb414 | ||
|
|
820a6bb233 | ||
|
|
31e44cfc69 | ||
|
|
fb9460e307 | ||
|
|
99a8c97b17 | ||
|
|
c30569f5e1 | ||
|
|
cdc19e1db9 |
@@ -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
|
||||
)
|
||||
|
||||
@@ -4,27 +4,34 @@ NPQS-9100 升级与回滚说明
|
||||
|
||||
一、升级步骤(超简单!)
|
||||
----------------------------------------
|
||||
1. 双击 upgrade.bat(首次会自动创建 upgrade 文件夹)
|
||||
2. 将升级文件放入 upgrade/ 目录:
|
||||
- app.asar (前端升级包 - 文件)
|
||||
- app.asar.unpacked\ (前端升级包 - 文件夹)
|
||||
- entrance.jar (后端升级包)
|
||||
3. 再次双击 upgrade.bat 开始升级
|
||||
4. 等待完成后重启应用
|
||||
1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
|
||||
2. 双击 upgrade.bat(首次会自动创建 upgrade 文件夹)
|
||||
3. 将升级文件放入 upgrade/ 目录:
|
||||
- app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
|
||||
- entrance.jar (后端升级包)
|
||||
4. 再次双击 upgrade.bat 开始升级
|
||||
5. 等待完成后重启应用
|
||||
|
||||
补充说明:
|
||||
- 可以只升级后端
|
||||
- 可以只升级前端,但前端升级时必须同时提供:
|
||||
app.asar
|
||||
app.asar.unpacked\
|
||||
- 数据库不由 upgrade.bat 自动备份,请务必提前手动导出 SQL
|
||||
|
||||
二、回滚步骤
|
||||
----------------------------------------
|
||||
如果升级后出现问题:
|
||||
1. 双击 rollback.bat
|
||||
2. 选择是否回滚数据库(谨慎!)
|
||||
3. 等待完成后重启应用
|
||||
1. 双击 rollback.bat(仅回滚前后端程序文件)
|
||||
2. 等待完成后重启应用
|
||||
3. 如需恢复数据库,请手动执行升级前导出的 SQL
|
||||
|
||||
三、重要提示
|
||||
----------------------------------------
|
||||
✓ 升级前会自动备份到 backup/ 目录
|
||||
✓ 数据库会自动备份到 mysql/data_backup/
|
||||
✓ 升级前会自动备份前后端程序文件到 backup/ 目录
|
||||
✓ 升级日志保存在 logs/upgrade.log
|
||||
✓ 多次升级时,backup/ 保存最后一次升级前的版本
|
||||
✓ 数据库备份与恢复由人工处理,不再由脚本自动执行
|
||||
|
||||
四、紧急情况
|
||||
----------------------------------------
|
||||
@@ -32,15 +39,15 @@ NPQS-9100 升级与回滚说明
|
||||
|
||||
【恢复前端】
|
||||
copy /Y backup\app.asar resources\app.asar
|
||||
rmdir /s /q resources\app.asar.unpacked
|
||||
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
|
||||
|
||||
【恢复后端】
|
||||
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
|
||||
|
||||
【恢复数据库】(慎用!)
|
||||
xcopy mysql\data_backup mysql\data\ /E /I /Y
|
||||
【恢复数据库】
|
||||
请使用 Navicat 或其他工具执行升级前导出的 SQL 备份
|
||||
|
||||
========================================
|
||||
详细文档请参考:doc/绿色包升级指南.md
|
||||
如需完整技术文档,请联系交付方提供开发文档。
|
||||
========================================
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -52,7 +52,8 @@ echo.
|
||||
|
||||
echo ========================================
|
||||
echo 打包完成!
|
||||
echo 输出目录: out\win-unpacked\
|
||||
echo 最终交付目录: out\NPQS-9100\
|
||||
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
|
||||
@@ -63,7 +63,8 @@ echo.
|
||||
|
||||
echo ========================================
|
||||
echo ✓ 打包完成!
|
||||
echo 输出目录: out\win-unpacked\
|
||||
echo 最终交付目录: out\NPQS-9100\
|
||||
echo 原始输出目录: out\win-unpacked\ (已自动重命名)
|
||||
echo ========================================
|
||||
echo.
|
||||
pause
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo35
Normal file
BIN
build/extraResources/mysql/data/#innodb_redo/#ib_redo35
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
34820
|
||||
74476
|
||||
|
||||
Binary file not shown.
BIN
build/extraResources/mysql/data/binlog.000031
Normal file
BIN
build/extraResources/mysql/data/binlog.000031
Normal file
Binary file not shown.
@@ -6,3 +6,4 @@
|
||||
.\binlog.000028
|
||||
.\binlog.000029
|
||||
.\binlog.000030
|
||||
.\binlog.000031
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
build/extraResources/mysql/data/pqs9100/pq_standard_dev_gain.ibd
Normal file
BIN
build/extraResources/mysql/data/pqs9100/pq_standard_dev_gain.ibd
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal
|
||||
echo ========================================
|
||||
echo NPQS9100 升级工具
|
||||
echo ========================================
|
||||
@@ -11,9 +12,8 @@ if not exist upgrade (
|
||||
echo 【首次使用】已自动创建 upgrade 目录
|
||||
echo.
|
||||
echo 请将升级文件放入 upgrade 目录:
|
||||
echo - app.asar (前端升级包 - 文件)
|
||||
echo - app.asar.unpacked\ (前端升级包 - 文件夹)
|
||||
echo - entrance.jar (后端升级包)
|
||||
echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
|
||||
echo - entrance.jar (后端升级包)
|
||||
echo.
|
||||
echo 放置完成后,重新运行本脚本即可升级
|
||||
echo.
|
||||
@@ -21,19 +21,51 @@ if not exist upgrade (
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
REM 检查是否有升级文件
|
||||
set hasUpgrade=0
|
||||
if exist upgrade\app.asar set hasUpgrade=1
|
||||
if exist upgrade\app.asar.unpacked set hasUpgrade=1
|
||||
if exist upgrade\entrance.jar set hasUpgrade=1
|
||||
REM 检查升级文件状态
|
||||
set "hasFrontendAsar=0"
|
||||
set "hasFrontendUnpacked=0"
|
||||
set "upgradeFrontend=0"
|
||||
set "upgradeBackend=0"
|
||||
set "frontendUpgraded=0"
|
||||
|
||||
if %hasUpgrade%==0 (
|
||||
if exist upgrade\app.asar set "hasFrontendAsar=1"
|
||||
if exist upgrade\app.asar.unpacked set "hasFrontendUnpacked=1"
|
||||
if exist upgrade\entrance.jar set "upgradeBackend=1"
|
||||
|
||||
if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==1 (
|
||||
set "upgradeFrontend=1"
|
||||
)
|
||||
|
||||
if %hasFrontendAsar%==1 if %hasFrontendUnpacked%==0 (
|
||||
echo 【错误】前端升级包不完整!
|
||||
echo.
|
||||
echo 当前仅检测到:upgrade\app.asar
|
||||
echo 前端升级必须同时提供以下两个内容:
|
||||
echo - upgrade\app.asar
|
||||
echo - upgrade\app.asar.unpacked\
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if %hasFrontendAsar%==0 if %hasFrontendUnpacked%==1 (
|
||||
echo 【错误】前端升级包不完整!
|
||||
echo.
|
||||
echo 当前仅检测到:upgrade\app.asar.unpacked\
|
||||
echo 前端升级必须同时提供以下两个内容:
|
||||
echo - upgrade\app.asar
|
||||
echo - upgrade\app.asar.unpacked\
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if %upgradeFrontend%==0 if %upgradeBackend%==0 (
|
||||
echo 【提示】upgrade 目录为空!
|
||||
echo.
|
||||
echo 请将升级文件放入 upgrade 目录:
|
||||
echo - app.asar (前端升级包 - 文件)
|
||||
echo - app.asar.unpacked\ (前端升级包 - 文件夹)
|
||||
echo - entrance.jar (后端升级包)
|
||||
echo - app.asar + app.asar.unpacked\ (前端升级包,必须成套放入)
|
||||
echo - entrance.jar (后端升级包)
|
||||
echo.
|
||||
echo 放置完成后,重新运行本脚本即可升级
|
||||
echo.
|
||||
@@ -42,19 +74,22 @@ if %hasUpgrade%==0 (
|
||||
)
|
||||
|
||||
echo 【检测到升级文件】
|
||||
if exist upgrade\app.asar echo - 前端升级(asar文件): upgrade\app.asar
|
||||
if exist upgrade\app.asar.unpacked echo - 前端升级(unpacked文件夹): upgrade\app.asar.unpacked\
|
||||
if exist upgrade\entrance.jar echo - 后端升级: upgrade\entrance.jar
|
||||
if %upgradeFrontend%==1 (
|
||||
echo - 前端升级: upgrade\app.asar + upgrade\app.asar.unpacked\
|
||||
)
|
||||
if %upgradeBackend%==1 (
|
||||
echo - 后端升级: upgrade\entrance.jar
|
||||
)
|
||||
echo.
|
||||
echo 【重要提示】
|
||||
echo 1. 升级前会自动备份当前版本
|
||||
echo 2. 如升级失败可运行 rollback.bat 回滚
|
||||
echo 3. 数据库会自动备份到 mysql\data_backup\
|
||||
echo 1. 升级前会自动备份当前前后端程序文件
|
||||
echo 2. 如升级失败可运行 rollback.bat 回滚前后端程序文件
|
||||
echo 3. 数据库请先使用 Navicat 或其他工具手动导出 SQL 备份
|
||||
echo.
|
||||
pause
|
||||
echo.
|
||||
|
||||
echo [1/6] 停止 NPQS9100 进程...
|
||||
echo [1/5] 停止 NPQS9100 进程...
|
||||
taskkill /F /IM NPQS9100.exe 2>nul
|
||||
if %errorlevel% equ 0 (
|
||||
echo NPQS9100 已停止
|
||||
@@ -64,63 +99,54 @@ if %errorlevel% equ 0 (
|
||||
)
|
||||
echo.
|
||||
|
||||
echo [2/6] 备份当前版本(用于回滚)...
|
||||
echo [2/5] 备份当前版本(用于回滚)...
|
||||
if not exist backup mkdir backup
|
||||
|
||||
REM 备份前端(app.asar 和 app.asar.unpacked)
|
||||
if exist resources\app.asar (
|
||||
echo 正在备份前端 app.asar...
|
||||
if not exist backup mkdir backup
|
||||
copy /Y resources\app.asar backup\app.asar >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar 备份完成
|
||||
) else (
|
||||
echo app.asar 备份失败
|
||||
REM 备份前端(仅在执行前端升级时)
|
||||
if %upgradeFrontend%==1 (
|
||||
if exist resources\app.asar (
|
||||
echo 正在备份前端 app.asar...
|
||||
copy /Y resources\app.asar backup\app.asar >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar 备份完成
|
||||
) else (
|
||||
echo app.asar 备份失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
if exist resources\app.asar.unpacked (
|
||||
echo 正在备份前端 app.asar.unpacked...
|
||||
if exist backup\app.asar.unpacked (
|
||||
rmdir /s /q backup\app.asar.unpacked 2>nul
|
||||
)
|
||||
xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar.unpacked 备份完成
|
||||
) else (
|
||||
echo app.asar.unpacked 备份失败
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if exist resources\app.asar.unpacked (
|
||||
echo 正在备份前端 app.asar.unpacked...
|
||||
if exist backup\app.asar.unpacked (
|
||||
rmdir /s /q backup\app.asar.unpacked 2>nul
|
||||
)
|
||||
xcopy resources\app.asar.unpacked backup\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar.unpacked 备份完成
|
||||
) else (
|
||||
echo app.asar.unpacked 备份失败
|
||||
)
|
||||
)
|
||||
|
||||
REM 备份后端
|
||||
if exist resources\extraResources\java\entrance.jar (
|
||||
REM 备份后端(仅在执行后端升级时)
|
||||
if %upgradeBackend%==1 if exist resources\extraResources\java\entrance.jar (
|
||||
echo 正在备份后端...
|
||||
copy /Y resources\extraResources\java\entrance.jar backup\entrance.jar >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo 后端备份完成
|
||||
) else (
|
||||
echo 后端备份失败
|
||||
)
|
||||
)
|
||||
|
||||
REM 备份数据库
|
||||
if exist mysql\data (
|
||||
echo 正在备份数据库...
|
||||
if exist mysql\data_backup (
|
||||
rmdir /s /q mysql\data_backup 2>nul
|
||||
)
|
||||
xcopy mysql\data mysql\data_backup\ /E /I /Y /Q >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo 数据库备份完成
|
||||
) else (
|
||||
echo 数据库备份失败,请手动备份后继续
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
echo.
|
||||
|
||||
echo [3/6] 记录版本信息...
|
||||
echo [3/5] 记录版本信息...
|
||||
if not exist backup\version.txt (
|
||||
echo 备份时间: %date% %time% > backup\version.txt
|
||||
echo 升级前版本备份 >> backup\version.txt
|
||||
@@ -130,29 +156,22 @@ if not exist backup\version.txt (
|
||||
echo 版本信息已记录
|
||||
echo.
|
||||
|
||||
echo [4/6] 升级前端...
|
||||
set frontendUpgraded=0
|
||||
|
||||
REM 升级 app.asar
|
||||
if exist upgrade\app.asar (
|
||||
echo [4/5] 升级前端...
|
||||
if %upgradeFrontend%==1 (
|
||||
echo 正在替换 app.asar...
|
||||
copy /Y upgrade\app.asar resources\app.asar >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar 升级完成
|
||||
set frontendUpgraded=1
|
||||
) else (
|
||||
echo app.asar 升级失败,正在回滚...
|
||||
if exist backup\app.asar (
|
||||
copy /Y backup\app.asar resources\app.asar >nul 2>&1
|
||||
echo 已回滚到升级前版本
|
||||
)
|
||||
echo 已回滚到升级前版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
REM 升级 app.asar.unpacked
|
||||
if exist upgrade\app.asar.unpacked (
|
||||
echo 正在替换 app.asar.unpacked...
|
||||
if exist resources\app.asar.unpacked (
|
||||
rmdir /s /q resources\app.asar.unpacked 2>nul
|
||||
@@ -160,26 +179,27 @@ if exist upgrade\app.asar.unpacked (
|
||||
xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
echo app.asar.unpacked 升级完成
|
||||
set frontendUpgraded=1
|
||||
set "frontendUpgraded=1"
|
||||
) else (
|
||||
echo app.asar.unpacked 升级失败,正在回滚...
|
||||
echo app.asar.unpacked 升级失败,正在回滚整个前端...
|
||||
if exist backup\app.asar (
|
||||
copy /Y backup\app.asar resources\app.asar >nul 2>&1
|
||||
)
|
||||
if exist backup\app.asar.unpacked (
|
||||
rmdir /s /q resources\app.asar.unpacked 2>nul
|
||||
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
|
||||
echo 已回滚到升级前版本
|
||||
)
|
||||
echo 已回滚到升级前版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
)
|
||||
|
||||
if %frontendUpgraded%==0 (
|
||||
) else (
|
||||
echo 无前端升级包,跳过
|
||||
)
|
||||
echo.
|
||||
|
||||
echo [5/6] 升级后端...
|
||||
if exist upgrade\entrance.jar (
|
||||
echo [5/5] 升级后端...
|
||||
if %upgradeBackend%==1 (
|
||||
echo 正在替换 JAR 文件...
|
||||
copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
|
||||
if %errorlevel% equ 0 (
|
||||
@@ -188,8 +208,17 @@ if exist upgrade\entrance.jar (
|
||||
echo 后端升级失败,正在回滚...
|
||||
if exist backup\entrance.jar (
|
||||
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar >nul 2>&1
|
||||
echo 已回滚到升级前版本
|
||||
)
|
||||
if %frontendUpgraded%==1 (
|
||||
if exist backup\app.asar (
|
||||
copy /Y backup\app.asar resources\app.asar >nul 2>&1
|
||||
)
|
||||
if exist backup\app.asar.unpacked (
|
||||
rmdir /s /q resources\app.asar.unpacked 2>nul
|
||||
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y /Q >nul 2>&1
|
||||
)
|
||||
)
|
||||
echo 已回滚到升级前版本
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
@@ -198,19 +227,17 @@ if exist upgrade\entrance.jar (
|
||||
)
|
||||
echo.
|
||||
|
||||
echo [6/6] 记录升级日志...
|
||||
if not exist logs mkdir logs
|
||||
echo ========================================== >> logs\upgrade.log
|
||||
echo 升级时间: %date% %time% >> logs\upgrade.log
|
||||
if exist upgrade\dist (
|
||||
if %upgradeFrontend%==1 (
|
||||
echo 升级内容: 前端 >> logs\upgrade.log
|
||||
)
|
||||
if exist upgrade\entrance.jar (
|
||||
if %upgradeBackend%==1 (
|
||||
echo 升级内容: 后端 >> logs\upgrade.log
|
||||
)
|
||||
echo 数据库处理: 请手动导出/导入 SQL >> logs\upgrade.log
|
||||
echo ========================================== >> logs\upgrade.log
|
||||
echo 升级日志已记录
|
||||
echo.
|
||||
|
||||
echo ========================================
|
||||
echo 升级完成!
|
||||
@@ -218,10 +245,9 @@ echo ========================================
|
||||
echo.
|
||||
echo 【提示】
|
||||
echo 1. 如需回滚,请运行 rollback.bat
|
||||
echo 2. 升级文件已使用,可删除 upgrade 目录
|
||||
echo 2. 数据库如需恢复,请手动执行之前导出的 SQL
|
||||
echo 3. 升级文件已使用,可删除 upgrade 目录
|
||||
echo.
|
||||
echo 您现在可以启动 NPQS9100 应用。
|
||||
echo.
|
||||
pause
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -40,8 +40,8 @@
|
||||
- 自动更新配置文件
|
||||
|
||||
#### **2. 盘符自动识别**
|
||||
- 应用在 C 盘 → 数据目录:`C:\NPQS9100_Data\`
|
||||
- 应用在 D 盘 → 数据目录:`D:\NPQS9100_Data\`
|
||||
- 程序目录为纯英文/ASCII路径时:数据目录位于应用目录内的 `NPQS9100_Data\`
|
||||
- 程序目录包含中文或其他非 ASCII 字符时:自动切换到当前盘符根目录下的 `NPQS9100_Runtime\`
|
||||
- 自动创建所有必要的子目录
|
||||
|
||||
#### **3. Loading 界面**
|
||||
@@ -65,18 +65,21 @@
|
||||
|
||||
### 📁 目录结构
|
||||
|
||||
交付口径说明:
|
||||
- `electron-builder` 原始输出目录:`out/win-unpacked`
|
||||
- `npm run build-w` 脚本最终交付目录:`out/NPQS-9100`
|
||||
|
||||
```
|
||||
win-unpacked/ # 绿色版目录(约 650-800 MB)
|
||||
NPQS-9100/ # 绿色版目录(约 650-800 MB)
|
||||
├── NPQS9100.exe # 主程序(双击运行)⭐
|
||||
├── NPQS9100-启动器.bat # 管理员启动器(备用)
|
||||
├── upgrade.bat # 升级工具 ⭐
|
||||
├── rollback.bat # 回滚工具 ⭐
|
||||
├── uninstall-mysql-service.bat # MySQL 服务卸载工具
|
||||
├── 使用说明.txt # 使用手册
|
||||
├── README-升级回滚.txt # 升级说明
|
||||
├── upgrade/ # 升级包目录(首次使用自动创建)
|
||||
├── backup/ # 自动备份目录
|
||||
│ ├── dist/ # 前端备份
|
||||
│ ├── app.asar # 前端主包备份
|
||||
│ ├── app.asar.unpacked/ # 前端展开目录备份
|
||||
│ ├── entrance.jar # 后端备份
|
||||
│ └── version.txt # 版本记录
|
||||
├── resources/
|
||||
@@ -93,13 +96,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB)
|
||||
├── scripts/ # 启动管理脚本(⚠️ 不要修改)
|
||||
│ ├── config-generator.js
|
||||
│ ├── java-runner.js
|
||||
│ ├── mysql-service-manager.js
|
||||
│ ├── mysql-process-manager.js
|
||||
│ ├── port-checker.js
|
||||
│ └── startup-manager.js
|
||||
├── mysql/ # MySQL 数据库
|
||||
│ ├── bin/
|
||||
│ ├── data/ # 数据库数据 ⚠️ 不要动
|
||||
│ ├── data_backup/ # 自动备份
|
||||
│ ├── my.ini # 运行时生成
|
||||
│ └── README.txt
|
||||
├── jre/ # Java 运行环境(⚠️ 不要修改)
|
||||
@@ -110,7 +112,12 @@ win-unpacked/ # 绿色版目录(约 650-800 MB)
|
||||
└── upgrade.log
|
||||
|
||||
用户数据目录(首次运行自动创建):
|
||||
X:\NPQS9100_Data\ (X 是应用所在盘符)
|
||||
- 程序目录为纯英文/ASCII路径时:
|
||||
`应用目录\NPQS9100_Data\`
|
||||
- 程序目录包含中文或其他非 ASCII 字符时:
|
||||
`X:\NPQS9100_Runtime\data\` (X 是应用所在盘符)
|
||||
|
||||
用户数据目录内包含:
|
||||
├── logs/ # Spring Boot 日志
|
||||
├── template/ # 报告模板
|
||||
├── report/ # 生成的报告
|
||||
@@ -126,10 +133,8 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
|
||||
| 文件 | 功能 | 使用场景 |
|
||||
|------|------|---------|
|
||||
| **NPQS9100.exe** | 主程序 | 日常启动 |
|
||||
| **NPQS9100-启动器.bat** | 管理员启动器 | 首次运行或权限问题时 |
|
||||
| **upgrade.bat** | 升级工具 | 收到升级包时使用 |
|
||||
| **rollback.bat** | 回滚工具 | 升级后出问题时使用 |
|
||||
| **uninstall-mysql-service.bat** | MySQL 服务卸载 | 完全卸载应用时使用 |
|
||||
| **使用说明.txt** | 使用手册 | 遇到问题时参考 |
|
||||
| **README-升级回滚.txt** | 升级说明 | 升级前阅读 |
|
||||
|
||||
@@ -151,9 +156,9 @@ X:\NPQS9100_Data\ (X 是应用所在盘符)
|
||||
|
||||
| 路径 | 说明 | 备份建议 |
|
||||
|------|------|---------|
|
||||
| **mysql/data/** | 数据库数据 | ⚠️ 定期备份! |
|
||||
| **应用目录\\mysql\\data/** 或 **X:\\NPQS9100_Runtime\\mysql\\data/** | 数据库数据 | ⚠️ 定期备份! |
|
||||
| **backup/** | 升级备份 | 自动管理,无需手动操作 |
|
||||
| **X:\NPQS9100_Data/** | 用户数据目录 | 包含日志、模板、报告 |
|
||||
| **应用目录\\NPQS9100_Data/** 或 **X:\\NPQS9100_Runtime\\data/** | 用户数据目录 | 包含日志、模板、报告 |
|
||||
|
||||
---
|
||||
|
||||
@@ -174,7 +179,7 @@ clean-and-build.bat
|
||||
3. 构建前端代码(Vue 3)
|
||||
4. 编译 Electron 主进程代码
|
||||
5. 复制 MySQL、JRE、Java 资源
|
||||
6. 生成 win-unpacked 绿色版目录
|
||||
6. 先生成 `out/win-unpacked`,再自动重命名为最终交付目录 `out/NPQS-9100`
|
||||
|
||||
**等待时间**:约 5-15 分钟
|
||||
|
||||
@@ -183,7 +188,7 @@ clean-and-build.bat
|
||||
#### **步骤 2:测试运行**
|
||||
|
||||
```bash
|
||||
cd out\win-unpacked
|
||||
cd out\NPQS-9100
|
||||
NPQS9100.exe
|
||||
```
|
||||
|
||||
@@ -200,7 +205,7 @@ NPQS9100.exe
|
||||
|
||||
```powershell
|
||||
cd out
|
||||
Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force
|
||||
Compress-Archive -Path NPQS-9100 -DestinationPath "NPQS9100-v1.0.0-绿色版.zip" -Force
|
||||
```
|
||||
|
||||
**压缩后大小**:约 350-450 MB
|
||||
@@ -237,7 +242,8 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
|
||||
├─ MySQL 连接:localhost:3307
|
||||
├─ MySQL 密码:njcnpqs ⭐ 自动写入
|
||||
├─ Java 端口:18093
|
||||
├─ 数据路径:C:\NPQS9100_Data\
|
||||
├─ 数据路径:纯英文路径时为 `应用目录\NPQS9100_Data\`
|
||||
├─ 数据路径:中文路径时为 `X:\NPQS9100_Runtime\data\`
|
||||
├─ 创建所有必要目录(logs、template、report、data)
|
||||
↓
|
||||
[80%] 启动 Spring Boot 后端
|
||||
@@ -274,7 +280,7 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
|
||||
↓
|
||||
停止 Spring Boot 进程(通过PID和端口精确清理)
|
||||
↓
|
||||
停止 MySQL 服务(如果是服务模式)
|
||||
停止 MySQL 进程(随应用启动的 mysqld.exe)
|
||||
↓
|
||||
清理资源
|
||||
↓
|
||||
@@ -289,17 +295,19 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
|
||||
|
||||
#### **升级流程**
|
||||
```
|
||||
1. 双击 upgrade.bat(首次自动创建 upgrade/ 目录)
|
||||
2. 将升级包放入 upgrade/ 目录
|
||||
3. 再次运行 upgrade.bat(自动备份 + 升级)
|
||||
4. 重启应用测试
|
||||
1. 先手动导出数据库 SQL 备份
|
||||
2. 双击 upgrade.bat(首次自动创建 upgrade/ 目录)
|
||||
3. 将升级包放入 upgrade/ 目录
|
||||
4. 再次运行 upgrade.bat(自动备份前后端程序文件 + 升级)
|
||||
5. 重启应用测试
|
||||
```
|
||||
|
||||
#### **回滚流程**
|
||||
```
|
||||
1. 双击 rollback.bat
|
||||
2. 自动从 backup/ 恢复旧版本
|
||||
3. 重启应用
|
||||
2. 自动从 backup/ 恢复前后端程序文件
|
||||
3. 如需恢复数据库,手动执行之前导出的 SQL
|
||||
4. 重启应用
|
||||
```
|
||||
|
||||
---
|
||||
@@ -310,23 +318,27 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
|
||||
|
||||
**第 1 步:准备升级包**
|
||||
|
||||
1. 双击 `upgrade.bat`
|
||||
2. 脚本自动创建 `upgrade/` 文件夹并提示
|
||||
3. 将升级文件放入 `upgrade/` 目录:
|
||||
- `dist/` - 前端升级包(可选)
|
||||
1. 先使用 Navicat 或其他工具手动导出数据库 SQL 备份
|
||||
2. 双击 `upgrade.bat`
|
||||
3. 脚本自动创建 `upgrade/` 文件夹并提示
|
||||
4. 将升级文件放入 `upgrade/` 目录:
|
||||
- `app.asar` + `app.asar.unpacked/` - 前端升级包(可选,必须成套)
|
||||
- `entrance.jar` - 后端升级包(可选)
|
||||
4. 再次双击 `upgrade.bat` 开始升级
|
||||
5. 再次双击 `upgrade.bat` 开始升级
|
||||
|
||||
**第 2 步:自动升级**
|
||||
|
||||
脚本会自动:
|
||||
- ✅ 停止 NPQS9100 进程
|
||||
- ✅ **自动备份当前版本到 `backup/` 目录**
|
||||
- ✅ 备份数据库到 `mysql/data_backup/`
|
||||
- ✅ **自动备份当前前后端程序文件到 `backup/` 目录**
|
||||
- ✅ 替换前端文件(如果有)
|
||||
- ✅ 替换后端 JAR(如果有)
|
||||
- ✅ 记录升级日志到 `logs/upgrade.log`
|
||||
|
||||
数据库说明:
|
||||
- ⚠️ 数据库不再由 `upgrade.bat` 自动备份
|
||||
- ⚠️ 升级前请务必手动导出 SQL 备份
|
||||
|
||||
**第 3 步:重启应用**
|
||||
|
||||
升级完成后,双击 `NPQS9100.exe` 启动应用。
|
||||
@@ -340,8 +352,9 @@ Compress-Archive -Path win-unpacked -DestinationPath "NPQS9100-v1.0.0-绿色版.
|
||||
**升级前端**:
|
||||
```batch
|
||||
taskkill /F /IM NPQS9100.exe
|
||||
rmdir /s /q resources\app.asar.unpacked\public\dist
|
||||
xcopy upgrade\dist resources\app.asar.unpacked\public\dist\ /E /I /Y
|
||||
copy /Y upgrade\app.asar resources\app.asar
|
||||
rmdir /s /q resources\app.asar.unpacked
|
||||
xcopy upgrade\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
|
||||
```
|
||||
|
||||
**升级后端**:
|
||||
@@ -360,23 +373,23 @@ copy /Y upgrade\entrance.jar resources\extraResources\java\entrance.jar
|
||||
|
||||
脚本会自动:
|
||||
- ✅ 停止 NPQS9100
|
||||
- ✅ 恢复前端(从 `backup/dist/`)
|
||||
- ✅ 恢复前端(从 `backup/app.asar` 和 `backup/app.asar.unpacked/`)
|
||||
- ✅ 恢复后端(从 `backup/entrance.jar`)
|
||||
- ✅ 询问是否恢复数据库(⚠️ 会丢失升级后的数据)
|
||||
- ✅ 数据库由人工恢复,不再由脚本自动处理
|
||||
|
||||
#### **方法 2:手动回滚**
|
||||
|
||||
```batch
|
||||
# 恢复前端
|
||||
rmdir /s /q resources\app.asar.unpacked\public\dist
|
||||
xcopy backup\dist resources\app.asar.unpacked\public\dist\ /E /I /Y
|
||||
copy /Y backup\app.asar resources\app.asar
|
||||
rmdir /s /q resources\app.asar.unpacked
|
||||
xcopy backup\app.asar.unpacked resources\app.asar.unpacked\ /E /I /Y
|
||||
|
||||
# 恢复后端
|
||||
copy /Y backup\entrance.jar resources\extraResources\java\entrance.jar
|
||||
|
||||
# 恢复数据库(⚠️ 谨慎!)
|
||||
rmdir /s /q mysql\data
|
||||
xcopy mysql\data_backup mysql\data\ /E /I /Y
|
||||
# 恢复数据库
|
||||
# 使用 Navicat 或其他工具执行升级前导出的 SQL 备份
|
||||
```
|
||||
|
||||
---
|
||||
@@ -385,9 +398,9 @@ xcopy mysql\data_backup mysql\data\ /E /I /Y
|
||||
|
||||
| 升级类型 | 升级包来源 | 客户放置位置 | 最终替换位置 |
|
||||
|---------|-----------|-------------|-------------|
|
||||
| **前端** | `out/win-unpacked/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` |
|
||||
| **后端** | `out/win-unpacked/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
|
||||
| **数据库** | ⚠️ **不升级,自动保留** | - | `mysql/data/` |
|
||||
| **前端** | `out/NPQS-9100/resources/app.asar` + `app.asar.unpacked/` | `upgrade/app.asar` + `upgrade/app.asar.unpacked/` | `resources/app.asar` + `resources/app.asar.unpacked/` |
|
||||
| **后端** | `out/NPQS-9100/resources/extraResources/java/entrance.jar` | `upgrade/entrance.jar` | `resources/extraResources/java/entrance.jar` |
|
||||
| **数据库** | 手动导出的 SQL 备份 | 不放入升级包 | 需要时人工导入 |
|
||||
|
||||
---
|
||||
|
||||
@@ -412,10 +425,11 @@ npm run build-frontend
|
||||
|
||||
# 4️⃣ 打包应用
|
||||
npm run build-w
|
||||
# 输出: out/win-unpacked/
|
||||
# 最终交付目录: out/NPQS-9100/
|
||||
# 原始输出目录: out/win-unpacked/(脚本会自动重命名)
|
||||
|
||||
# 5️⃣ 准备升级包
|
||||
# 从 out/win-unpacked/resources/ 复制以下文件:
|
||||
# 从 out/NPQS-9100/resources/ 复制以下文件:
|
||||
# - app.asar (文件)
|
||||
# - app.asar.unpacked/ (整个文件夹)
|
||||
#
|
||||
@@ -435,7 +449,7 @@ npm run build-w
|
||||
# - app.asar.unpacked/
|
||||
|
||||
# 2️⃣ 复制到升级目录
|
||||
# 将两个文件都放到:win-unpacked/upgrade/
|
||||
# 将两个文件都放到:NPQS-9100/upgrade/
|
||||
# - upgrade/app.asar
|
||||
# - upgrade/app.asar.unpacked/
|
||||
|
||||
@@ -478,7 +492,7 @@ npm run build-w
|
||||
|
||||
# 2️⃣ 复制到升级目录
|
||||
# 将 entrance.jar 复制到:
|
||||
# win-unpacked/upgrade/entrance.jar
|
||||
# NPQS-9100/upgrade/entrance.jar
|
||||
|
||||
# 3️⃣ 运行升级脚本
|
||||
# 双击: upgrade.bat
|
||||
@@ -497,7 +511,7 @@ NPQS9100-升级-v1.1.0.zip
|
||||
└── entrance.jar # 后端升级包
|
||||
|
||||
# 客户侧:复制到
|
||||
win-unpacked/upgrade/
|
||||
NPQS-9100/upgrade/
|
||||
├── app.asar
|
||||
├── app.asar.unpacked/
|
||||
└── entrance.jar
|
||||
@@ -511,13 +525,11 @@ win-unpacked/upgrade/
|
||||
|
||||
### ✅ 打包验证
|
||||
|
||||
- [ ] `out/win-unpacked` 目录存在
|
||||
- [ ] `out/NPQS-9100` 目录存在
|
||||
- [ ] 目录大小约 650-800 MB
|
||||
- [ ] NPQS9100.exe 存在
|
||||
- [ ] NPQS9100-启动器.bat 存在
|
||||
- [ ] upgrade.bat 存在
|
||||
- [ ] rollback.bat 存在
|
||||
- [ ] uninstall-mysql-service.bat 存在
|
||||
- [ ] 使用说明.txt 存在
|
||||
- [ ] README-升级回滚.txt 存在
|
||||
- [ ] mysql/ 目录完整
|
||||
@@ -602,10 +614,12 @@ win-unpacked/upgrade/
|
||||
- [ ] upgrade.bat 首次运行自动创建 upgrade/ 目录
|
||||
- [ ] upgrade.bat 检测到升级文件后正常升级
|
||||
- [ ] 升级前自动备份到 backup/ 目录
|
||||
- [ ] 升级前已手动导出数据库 SQL
|
||||
- [ ] 升级后应用正常运行
|
||||
- [ ] 升级后数据库数据保留
|
||||
- [ ] rollback.bat 能正确回滚前端
|
||||
- [ ] rollback.bat 能正确回滚后端
|
||||
- [ ] 如需恢复数据库,能手动导入 SQL
|
||||
- [ ] 回滚后应用恢复正常
|
||||
|
||||
---
|
||||
@@ -710,7 +724,7 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
|
||||
|
||||
### Q8: 升级后数据丢失?
|
||||
|
||||
**A**: 检查 `mysql/data/` 目录是否完整,如有备份,从 `mysql/data_backup/` 恢复
|
||||
**A**: 检查升级前是否已手动导出数据库 SQL 备份。如需恢复数据库,请使用 Navicat 或其他工具执行该 SQL 备份。
|
||||
|
||||
---
|
||||
|
||||
@@ -728,7 +742,8 @@ C:\Users\[用户名]\AppData\Roaming\NQPS9100\logs\
|
||||
**A**:
|
||||
1. 检查 `backup/` 目录是否有备份文件
|
||||
2. 查看 `backup/version.txt` 确认备份版本
|
||||
3. 手动执行回滚步骤(参考本文档)
|
||||
3. 手动执行前后端回滚步骤(参考本文档)
|
||||
4. 如问题与数据库有关,手动导入升级前导出的 SQL
|
||||
|
||||
---
|
||||
|
||||
@@ -767,27 +782,32 @@ VITE_API_URL=http://192.168.1.100:18092
|
||||
|
||||
---
|
||||
|
||||
### Q14: MySQL 服务如何卸载?
|
||||
### Q14: 现在还需要安装或卸载 MySQL 服务吗?
|
||||
|
||||
**A**:
|
||||
**方法 1:使用卸载脚本(推荐)**
|
||||
```batch
|
||||
双击: uninstall-mysql-service.bat
|
||||
```
|
||||
**A**:
|
||||
|
||||
**方法 2:手动卸载**
|
||||
```batch
|
||||
# 停止服务
|
||||
net stop mysql9100
|
||||
**当前版本不需要。**
|
||||
|
||||
# 删除服务
|
||||
sc delete mysql9100
|
||||
```
|
||||
当前版本已经放弃“首次使用时把 MySQL 安装成 Windows 服务并设置开机自启”的方案,改为**绿色包 + 进程模式**:
|
||||
|
||||
**注意**:
|
||||
- 卸载服务不会删除数据库数据
|
||||
- 数据保存在 `mysql/data/` 目录
|
||||
- 下次启动应用会重新安装服务
|
||||
- 双击 `NPQS9100.exe` 时,应用会自动启动 `mysqld.exe`
|
||||
- 退出应用时,MySQL 进程会自动停止
|
||||
- **无需管理员权限**
|
||||
- **不会注册 Windows 服务**
|
||||
- **不会依赖开机自启**
|
||||
|
||||
**为什么改成这样?**
|
||||
|
||||
- 旧方案需要管理员权限
|
||||
- 用户可能直接双击使用,导致服务安装失败后应用启动失败
|
||||
- 也可能出现授权失败、赋权不完整等问题
|
||||
- 对 C 端用户来说不稳定,因此改为随应用启停的绿色包进程模式
|
||||
|
||||
**数据说明**:
|
||||
|
||||
- 程序目录为纯英文/ASCII路径时,MySQL 数据位于 `应用目录\mysql\data\`
|
||||
- 程序目录包含中文或其他非 ASCII 字符时,MySQL 数据位于 `X:\NPQS9100_Runtime\mysql\data\`
|
||||
- 当前版本不依赖任何 MySQL Windows 服务
|
||||
|
||||
---
|
||||
|
||||
@@ -800,8 +820,7 @@ sc delete mysql9100
|
||||
**开发者文档**(doc/ 目录):
|
||||
- `doc/NPQS-9100绿色包完整指南.md` - 本文档(完整指南)
|
||||
- `doc/打包前检查清单.md` - 逐项检查
|
||||
- `doc/管理员权限说明.md` - 权限问题处理
|
||||
- `doc/MySQL服务化方案说明.md` - MySQL 服务管理
|
||||
- `doc/管理员权限说明.md` - 历史服务模式与当前进程模式说明
|
||||
|
||||
---
|
||||
|
||||
@@ -812,7 +831,7 @@ sc delete mysql9100
|
||||
- `scripts/port-checker.js` - 端口检测工具
|
||||
- `scripts/startup-manager.js` - 启动状态管理
|
||||
- `scripts/config-generator.js` - 配置文件生成
|
||||
- `scripts/mysql-service-manager.js` - MySQL 服务管理器
|
||||
- `scripts/mysql-process-manager.js` - MySQL 进程管理器
|
||||
- `scripts/java-runner.js` - Java 运行器
|
||||
- `scripts/log-window-manager.js` - 日志窗口管理
|
||||
- `electron/preload/lifecycle.js` - 生命周期管理
|
||||
@@ -827,7 +846,7 @@ StartupManager (显示 Loading)
|
||||
↓ 调用
|
||||
PortChecker (检测端口)
|
||||
↓ 调用
|
||||
MySQLServiceManager (管理 MySQL 服务)
|
||||
MySQLProcessManager (管理 MySQL 进程)
|
||||
↓ 调用
|
||||
ConfigGenerator (生成配置)
|
||||
↓ 调用
|
||||
@@ -840,14 +859,14 @@ JavaRunner (启动 Spring Boot)
|
||||
|
||||
### 用户体验提升
|
||||
- ✅ **任务栏只显示1个图标** - Loading 窗口不在任务栏显示
|
||||
- ✅ **纯绿色版** - 只生成 win-unpacked 目录,压缩成 zip 即可发布
|
||||
- ✅ **纯绿色版** - 最终交付目录统一为 `out/NPQS-9100`,压缩成 zip 即可发布
|
||||
- ✅ **一键解压即用** - 用户解压后双击即可运行
|
||||
- ✅ **热更新机制** - 前后端可独立升级,支持一键回滚
|
||||
|
||||
### 技术改进
|
||||
- ✅ **MySQL 密码自动配置** - 配置生成器自动写入密码
|
||||
- ✅ **权限自动授权** - 支持 localhost 和 127.0.0.1 访问
|
||||
- ✅ **MySQL 服务化** - 使用 Windows 服务管理,开机自启
|
||||
- ✅ **MySQL 进程模式** - 随应用启动和退出自动管理,无需管理员权限
|
||||
- ✅ **窗口管理优化** - 使用 destroy() 确保窗口完全释放
|
||||
- ✅ **精确进程清理** - 不会误杀其他 Java 进程
|
||||
- ✅ **自动备份机制** - 升级前自动备份,支持回滚
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 启动失败
|
||||
|
||||
@@ -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 端用户不稳定
|
||||
- 用户直接双击使用时,提权、授权或服务安装失败会导致启动失败
|
||||
- 因此当前版本统一采用无需授权的绿色包进程模式
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准设备表格查询分页返回的对象;
|
||||
*/
|
||||
|
||||
@@ -14,6 +14,7 @@ export namespace Base {
|
||||
createTime?: string| null; //创建时间
|
||||
updateBy?: string| null; //更新用户
|
||||
updateTime?: string| null; //更新时间
|
||||
coefficient:number //比对监测后,当电压、电流不符合时,是否对标准设备进行系数校准
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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秒重连延迟
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,16 +679,37 @@ const updatePercentage = async () => {
|
||||
})
|
||||
}
|
||||
stopTimeCount(1)
|
||||
ElMessageBox.alert(
|
||||
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
|
||||
'检测完成',
|
||||
{
|
||||
confirmButtonText: '确定'
|
||||
}
|
||||
)
|
||||
// 关闭WebSocket连接
|
||||
emit('closeWebSocket')
|
||||
//clear();
|
||||
|
||||
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(
|
||||
'检测全部结束,你可以停留在此页面查看检测结果,或返回首页进行复检、报告生成和归档等操作',
|
||||
'检测完成',
|
||||
{
|
||||
confirmButtonText: '确定'
|
||||
}
|
||||
)
|
||||
})
|
||||
}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()}:当前测试小项正在执行中,将在该小项执行结束后暂停...`
|
||||
|
||||
@@ -198,8 +198,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx" name="useProTable">
|
||||
import { onBeforeMount, onMounted, type PropType, reactive, ref, watch } from 'vue'
|
||||
import { type Action, ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {onBeforeMount, onMounted, type PropType, reactive, ref, watch} from 'vue'
|
||||
import {type Action, ElMessage, ElMessageBox} from 'element-plus'
|
||||
import TestPopup from './testPopup.vue'
|
||||
import dataCheckPopup from './dataCheckSingleChannelSingleTestPopup.vue'
|
||||
import CompareDataCheckSingleChannelSingleTestPopup from '@/views/home/components/compareDataCheckSingleChannelSingleTestPopup.vue'
|
||||
@@ -207,20 +207,20 @@ import ProTable from '@/components/ProTable/index.vue'
|
||||
import SelectTestItemPopup from '@/views/home/components/selectTestItemPopup.vue'
|
||||
import WriteTHPopup from '@/views/home/components/writeTHPopup.vue'
|
||||
import DeviceConnectionPopup from '@/views/home/components/deviceConnectionPopup.vue'
|
||||
import { type Device } from '@/api/device/interface/device'
|
||||
import { type ColumnProps, type ProTableInstance } from '@/components/ProTable/interface'
|
||||
import { type Plan } from '@/api/plan/interface'
|
||||
import { type StandardDevice } from '@/api/device/interface/standardDevice'
|
||||
import { downloadDevData, generateDevReport, getBoundPqDevList } from '@/api/plan/plan'
|
||||
import { getPqDev } from '@/api/device/device'
|
||||
import { useAppSceneStore, useModeStore } from '@/stores/modules/mode' // 引入模式 store
|
||||
import { useCheckStore } from '@/stores/modules/check'
|
||||
import { CheckData } from '@/api/check/interface'
|
||||
import { useAuthStore } from '@/stores/modules/auth'
|
||||
import { useDownload } from '@/hooks/useDownload'
|
||||
import { documentedPqDev } from '@/api/device/report'
|
||||
import { ResultEnum } from '@/enums/httpEnum'
|
||||
import { getPqMonList } from '@/api/device/monitor/index'
|
||||
import {type Device} from '@/api/device/interface/device'
|
||||
import {type ColumnProps, type ProTableInstance} from '@/components/ProTable/interface'
|
||||
import {type Plan} from '@/api/plan/interface'
|
||||
import {type StandardDevice} from '@/api/device/interface/standardDevice'
|
||||
import {downloadDevData, generateDevReport, getBoundPqDevList} from '@/api/plan/plan'
|
||||
import {getPqDev} from '@/api/device/device'
|
||||
import {useAppSceneStore, useModeStore} from '@/stores/modules/mode' // 引入模式 store
|
||||
import {useCheckStore} from '@/stores/modules/check'
|
||||
import {CheckData} from '@/api/check/interface'
|
||||
import {useAuthStore} from '@/stores/modules/auth'
|
||||
import {useDownload} from '@/hooks/useDownload'
|
||||
import {documentedPqDev} from '@/api/device/report'
|
||||
import {ResultEnum} from '@/enums/httpEnum'
|
||||
import {getPqMonList} from '@/api/device/monitor/index'
|
||||
import ReportResultPopup from '@/views/home/components/reportResultPopup.vue'
|
||||
|
||||
const checkStore = useCheckStore()
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -1,100 +1,138 @@
|
||||
<template>
|
||||
<el-dialog :title="dialogTitle" v-model='dialogVisible' @close="close" v-bind="dialogBig" align-center>
|
||||
<el-tabs type="border-card">
|
||||
<el-tab-pane label="设备台账信息">
|
||||
<div >
|
||||
<el-form :model='formContent' ref='dialogFormRef' :rules='rules' :disabled="false" label-width="auto" class="form-three">
|
||||
<el-divider >设备信息</el-divider>
|
||||
|
||||
<el-form-item label="设备名称" prop="name" >
|
||||
<el-input v-model='formContent.name' placeholder="请输入设备名称" maxlength="32" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label='设备类型' prop='devType' >
|
||||
<el-select v-model="formContent.devType" filterable clearable placeholder="请选择设备类型" @change="handleDevTypeChange">
|
||||
<el-option
|
||||
v-for="item in devTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='设备厂家' prop='manufacturer'>
|
||||
<el-select v-model="formContent.manufacturer" clearable placeholder="请选择设备厂家">
|
||||
<el-option
|
||||
v-for="item in dictStore.getDictData('Dev_Manufacturers')"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider >参数信息</el-divider>
|
||||
<el-form-item label='通讯协议' prop='protocol'>
|
||||
<el-select v-model="formContent.protocol" clearable placeholder="请选择通讯协议">
|
||||
<el-option
|
||||
v-for="item in dictStore.getDictData('Protocol')"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址" prop="ip" placeholder="请输入IP地址">
|
||||
<el-input v-model="formContent.ip"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="端口号" prop="port" placeholder="请输入端口号" >
|
||||
<el-input v-model="formContent.port" />
|
||||
</el-form-item>
|
||||
<el-form-item label='可检通道数' prop='inspectChannel' >
|
||||
<el-select v-model="formContent.inspectChannel" multiple collapse-tags :max-collapse-tags="4" placeholder="请选择可检通道数" clearable>
|
||||
<el-option
|
||||
v-for="(option, index) in pqChannelArray"
|
||||
:key="index"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='是否加密' prop='encryptionFlag' >
|
||||
<el-select v-model="formContent.encryptionFlag" clearable placeholder="请选择是否加密">
|
||||
<el-option label="是" :value="1"></el-option>
|
||||
<el-option label="否" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='识别码' prop='series' clearable v-if="formContent.encryptionFlag">
|
||||
<el-input v-model='formContent.series' placeholder="请输入识别码" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item label='密钥' prop='devKey' clearable v-if="formContent.encryptionFlag">
|
||||
<el-input v-model='formContent.devKey' placeholder="请输入密钥" show-password/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
<el-dialog :title="dialogTitle" v-model='dialogVisible' @close="close" v-bind="dialogBig" align-center>
|
||||
<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>
|
||||
|
||||
<el-form-item label="设备名称" prop="name">
|
||||
<el-input v-model='formContent.name' placeholder="请输入设备名称" maxlength="32" show-word-limit/>
|
||||
</el-form-item>
|
||||
<el-form-item label='设备类型' prop='devType'>
|
||||
<el-select v-model="formContent.devType" filterable clearable placeholder="请选择设备类型" @change="handleDevTypeChange">
|
||||
<el-option
|
||||
v-for="item in devTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='设备厂家' prop='manufacturer'>
|
||||
<el-select v-model="formContent.manufacturer" clearable placeholder="请选择设备厂家">
|
||||
<el-option
|
||||
v-for="item in dictStore.getDictData('Dev_Manufacturers')"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-divider>参数信息</el-divider>
|
||||
<el-form-item label='通讯协议' prop='protocol'>
|
||||
<el-select v-model="formContent.protocol" clearable placeholder="请选择通讯协议">
|
||||
<el-option
|
||||
v-for="item in dictStore.getDictData('Protocol')"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="IP地址" prop="ip" placeholder="请输入IP地址">
|
||||
<el-input v-model="formContent.ip"/>
|
||||
</el-form-item>
|
||||
<el-form-item label="端口号" prop="port" placeholder="请输入端口号">
|
||||
<el-input v-model="formContent.port"/>
|
||||
</el-form-item>
|
||||
<el-form-item label='可检通道数' prop='inspectChannel'>
|
||||
<el-select v-model="formContent.inspectChannel" multiple collapse-tags :max-collapse-tags="4" placeholder="请选择可检通道数" clearable>
|
||||
<el-option
|
||||
v-for="(option, index) in pqChannelArray"
|
||||
:key="index"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='是否加密' prop='encryptionFlag'>
|
||||
<el-select v-model="formContent.encryptionFlag" clearable placeholder="请选择是否加密">
|
||||
<el-option label="是" :value="1"></el-option>
|
||||
<el-option label="否" :value="0"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label='识别码' prop='series' clearable v-if="formContent.encryptionFlag">
|
||||
<el-input v-model='formContent.series' placeholder="请输入识别码" show-password/>
|
||||
</el-form-item>
|
||||
<el-form-item label='密钥' prop='devKey' clearable v-if="formContent.encryptionFlag">
|
||||
<el-input v-model='formContent.devKey' placeholder="请输入密钥" show-password/>
|
||||
</el-form-item>
|
||||
</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>
|
||||
<template #footer>
|
||||
<div >
|
||||
<el-button @click='close()'>取 消</el-button>
|
||||
<el-button type="primary" @click='save()'>保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
<div>
|
||||
<el-button @click='close()'>取 消</el-button>
|
||||
<el-button type="primary" @click='save()'>保存</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
|
||||
import IPAddress from '@/components/IpAddress/index.vue'
|
||||
import { dialogBig } from '@/utils/elementBind'
|
||||
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 {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 {type Device} from '@/api/device/interface/device.ts'
|
||||
// 使用 dayjs 库格式化
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 存储设备类型选项
|
||||
const devTypeOptions = ref<Device.ResDev[]>([])
|
||||
@@ -120,112 +158,114 @@ const pqChannelArray = ref([
|
||||
label: '4',
|
||||
},
|
||||
])
|
||||
const activeName=ref('devInfo')
|
||||
|
||||
function useMetaInfo() {
|
||||
const dialogVisible = ref(false)
|
||||
const titleType = ref('add')
|
||||
const formContent = reactive<StandardDevice.ResPqStandardDevice>({
|
||||
id: '',
|
||||
name: '',
|
||||
devType:'',
|
||||
manufacturer:'',
|
||||
protocol: 'MMS',
|
||||
ip: '',
|
||||
port: 102,
|
||||
inspectChannel:'',
|
||||
encryptionFlag: 0,
|
||||
state: 1,
|
||||
})
|
||||
return { dialogVisible, titleType, formContent }
|
||||
}
|
||||
|
||||
const { dialogVisible, titleType, formContent } = useMetaInfo()
|
||||
// 清空formContent
|
||||
const resetFormContent = () => {
|
||||
Object.assign(
|
||||
formContent,{
|
||||
|
||||
function useMetaInfo() {
|
||||
const dialogVisible = ref(false)
|
||||
const titleType = ref('add')
|
||||
const formContent = reactive<StandardDevice.ResPqStandardDevice>({
|
||||
id: '',
|
||||
name: '',
|
||||
devType: '',
|
||||
manufacturer: '',
|
||||
protocol: 'MMS',
|
||||
ip: '',
|
||||
port: 102,
|
||||
inspectChannel: '',
|
||||
encryptionFlag: 0,
|
||||
state: 1,
|
||||
})
|
||||
return {dialogVisible, titleType, formContent}
|
||||
}
|
||||
|
||||
const {dialogVisible, titleType, formContent} = useMetaInfo()
|
||||
// 清空formContent
|
||||
const resetFormContent = () => {
|
||||
Object.assign(
|
||||
formContent, {
|
||||
id: '',
|
||||
name: '',
|
||||
devType:'',
|
||||
manufacturer:'',
|
||||
devType: '',
|
||||
manufacturer: '',
|
||||
protocol: 'MMS',
|
||||
ip: '',
|
||||
port: 102,
|
||||
inspectChannel:'',
|
||||
inspectChannel: '',
|
||||
encryptionFlag: 0,
|
||||
state: 1,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let dialogTitle = computed(() => {
|
||||
return titleType.value === 'add' ? '新增标准设备' : '编辑标准设备'
|
||||
})
|
||||
|
||||
|
||||
//定义校验规则
|
||||
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
|
||||
name: [{required: true, message: '设备名称必填!', trigger: 'blur'}],
|
||||
devType: [{required: true, message: '设备类型必选!', trigger: 'change'}],
|
||||
manufacturer: [{required: true, message: '生产厂家必选!', trigger: 'change'}],
|
||||
ip: [
|
||||
{required: true, message: 'IP地址必填!', trigger: 'blur'},
|
||||
{pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: 'IP地址格式错误', trigger: 'blur'}
|
||||
],
|
||||
port: [
|
||||
{required: true, message: '端口号必填!', trigger: 'blur'},
|
||||
{pattern: /^(6553[0-5]|655[0-2][0-9]|64[0-9]{3}|[1-5]?[0-9]{1,4})$/, message: '端口号范围0到65535的整数', trigger: 'blur'}
|
||||
],
|
||||
inspectChannel: [{required: true, message: '可检通道数必选', trigger: 'change'}],
|
||||
encryptionFlag: [{required: true, message: '是否加密必选!', trigger: 'change'}],
|
||||
series: [{required: true, message: '请输入识别码', trigger: 'blur'}],
|
||||
devKey: [{required: true, message: '请输入密钥', trigger: 'blur'}],
|
||||
protocol: [{required: true, message: '通讯协议必选!', trigger: 'change'}],
|
||||
})
|
||||
|
||||
// 关闭弹窗
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
// 清空dialogForm中的值
|
||||
resetFormContent()
|
||||
// 重置表单
|
||||
dialogFormRef.value?.resetFields()
|
||||
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
const save = () => {
|
||||
try {
|
||||
dialogFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (formContent.encryptionFlag === 0) {
|
||||
formContent.series = ''
|
||||
formContent.devKey = ''
|
||||
}
|
||||
if (valid) {
|
||||
//保存时判是否加密,把识别码密钥字段清空
|
||||
if (formContent.encryptionFlag === 0) {
|
||||
formContent.series = null
|
||||
formContent.devKey = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
let dialogTitle = computed(() => {
|
||||
return titleType.value === 'add' ? '新增标准设备' : '编辑标准设备'
|
||||
})
|
||||
|
||||
|
||||
//定义校验规则
|
||||
const rules: Ref<Record<string, Array<FormItemRule>>> = ref({
|
||||
name : [{ required: true, message: '设备名称必填!', trigger: 'blur' }],
|
||||
devType: [{ required: true, message: '设备类型必选!', trigger: 'change' }],
|
||||
manufacturer:[{ required: true, message: '生产厂家必选!', trigger: 'change' }],
|
||||
ip: [
|
||||
{ required: true, message: 'IP地址必填!', trigger: 'blur' },
|
||||
{ pattern: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/, message: 'IP地址格式错误', trigger: 'blur' }
|
||||
],
|
||||
port: [
|
||||
{ required: true, message: '端口号必填!', trigger: 'blur' },
|
||||
{ pattern: /^(6553[0-5]|655[0-2][0-9]|64[0-9]{3}|[1-5]?[0-9]{1,4})$/, message: '端口号范围0到65535的整数', trigger: 'blur' }
|
||||
],
|
||||
inspectChannel:[ { required: true, message: '可检通道数必选', trigger: 'change' }],
|
||||
encryptionFlag: [{ required: true, message: '是否加密必选!', trigger: 'change' }],
|
||||
series: [{ required: true, message: '请输入识别码', trigger: 'blur' }],
|
||||
devKey: [{ required: true, message: '请输入密钥', trigger: 'blur' }],
|
||||
protocol: [{required: true, message: '通讯协议必选!', trigger: 'change'}],
|
||||
})
|
||||
|
||||
// 关闭弹窗
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
// 清空dialogForm中的值
|
||||
resetFormContent()
|
||||
// 重置表单
|
||||
dialogFormRef.value?.resetFields()
|
||||
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
const save = () => {
|
||||
try {
|
||||
dialogFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (formContent.encryptionFlag === 0) {
|
||||
formContent.series = ''
|
||||
formContent.devKey = ''
|
||||
}
|
||||
if (valid) {
|
||||
//保存时判是否加密,把识别码密钥字段清空
|
||||
if(formContent.encryptionFlag === 0){
|
||||
formContent.series = null
|
||||
formContent.devKey = null
|
||||
}
|
||||
|
||||
// 可检通道转为字符串逗号分隔(保存前临时转换)
|
||||
let originalInspectChannel = formContent.inspectChannel; // 保存原始值
|
||||
//可检通道转为字符串逗号分隔
|
||||
// 确保 inspectChannel 是数组再执行 join
|
||||
if (Array.isArray(formContent.inspectChannel)) {
|
||||
formContent.inspectChannel = formContent.inspectChannel
|
||||
//可检通道转为字符串逗号分隔
|
||||
// 确保 inspectChannel 是数组再执行 join
|
||||
if (Array.isArray(formContent.inspectChannel)) {
|
||||
formContent.inspectChannel = formContent.inspectChannel
|
||||
.map(Number) // 将值转为数字以保证正确排序
|
||||
.sort((a, b) => a - b) // 数字升序排序
|
||||
.map(String) // 恢复为字符串用于保存
|
||||
.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
try {
|
||||
if (formContent.id) {
|
||||
await updatePqStandardDev(formContent);
|
||||
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
||||
} else {
|
||||
ElMessage.success({message: `${dialogTitle.value}成功!`})
|
||||
} else {
|
||||
// 新增需要把通讯协议转成字典ID
|
||||
const protocolItem = dictStore.getDictData('Protocol').find(item => item.name === formContent.protocol);
|
||||
if (protocolItem) {
|
||||
@@ -233,7 +273,7 @@ const pqChannelArray = ref([
|
||||
}
|
||||
|
||||
await addPqStandardDev(formContent);
|
||||
ElMessage.success({ message: `${dialogTitle.value}成功!` })
|
||||
ElMessage.success({message: `${dialogTitle.value}成功!`})
|
||||
}
|
||||
close()
|
||||
// 刷新表格
|
||||
@@ -249,7 +289,7 @@ const pqChannelArray = ref([
|
||||
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('验证过程中出现错误', err)
|
||||
@@ -258,20 +298,22 @@ const pqChannelArray = ref([
|
||||
|
||||
|
||||
// 打开弹窗,可能是新增,也可能是编辑
|
||||
const open = async (sign: string, data: StandardDevice.ResPqStandardDevice,devType:Device.ResDev[]) => {
|
||||
// 重置表单
|
||||
dialogFormRef.value?.resetFields()
|
||||
devTypeOptions.value = devType
|
||||
titleType.value = sign
|
||||
if (data.id) {
|
||||
Object.assign(formContent,{ ...data })
|
||||
if (typeof formContent.inspectChannel === 'string') {
|
||||
formContent.inspectChannel = formContent.inspectChannel.split(',').filter(Boolean)
|
||||
}
|
||||
//handleDevTypeChange(data.devType)
|
||||
} else {
|
||||
resetFormContent()
|
||||
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)
|
||||
}
|
||||
//handleDevTypeChange(data.devType)
|
||||
} else {
|
||||
resetFormContent()
|
||||
}
|
||||
|
||||
|
||||
dialogVisible.value = true
|
||||
@@ -281,38 +323,38 @@ const open = async (sign: string, data: StandardDevice.ResPqStandardDevice,devTy
|
||||
const handleDevTypeChange = (value: string) => {
|
||||
|
||||
// 在这里处理选中事件的逻辑
|
||||
const dev = devTypeOptions.value.find(t =>t.id === value)
|
||||
const dev = devTypeOptions.value.find(t => t.id === value)
|
||||
if (dev) {
|
||||
const maxChannel = dev.devChns
|
||||
const maxChannel = dev.devChns
|
||||
// 动态设置 pqChannelArray 从 1 到 dev.devChns.length
|
||||
pqChannelArray.value = Array.from({ length: dev.devChns }, (_, i) => ({
|
||||
pqChannelArray.value = Array.from({length: dev.devChns}, (_, i) => ({
|
||||
value: String(i + 1),
|
||||
label: String(i + 1),
|
||||
}))
|
||||
|
||||
//if(titleType.value == 'add') // 默认全选所有通道
|
||||
formContent.inspectChannel = pqChannelArray.value.map(channel => channel.value)
|
||||
formContent.inspectChannel = pqChannelArray.value.map(channel => channel.value)
|
||||
// 过滤掉超出新通道数范围的选项
|
||||
if (Array.isArray(formContent.inspectChannel)) {
|
||||
formContent.inspectChannel = formContent.inspectChannel.filter(
|
||||
(channel) => parseInt(channel, 10) <= maxChannel
|
||||
(channel) => parseInt(channel, 10) <= maxChannel
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 可选:恢复默认值
|
||||
pqChannelArray.value = [
|
||||
{ value: '1', label: '1' },
|
||||
{ value: '2', label: '2' },
|
||||
{ value: '3', label: '3' },
|
||||
{ value: '4', label: '4' },
|
||||
{value: '1', label: '1'},
|
||||
{value: '2', label: '2'},
|
||||
{value: '3', label: '3'},
|
||||
{value: '4', label: '4'},
|
||||
]
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 对外映射
|
||||
defineExpose({ open })
|
||||
defineExpose({open})
|
||||
const props = defineProps<{
|
||||
refreshTable: (() => Promise<void>) | undefined;
|
||||
}>()
|
||||
|
||||
@@ -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 )
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,7 +342,7 @@
|
||||
v-model="formContent.testConfig!.realTime"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
:min="40"
|
||||
:min="20"
|
||||
placeholder="请输入实时数据有效组数"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
@@ -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>({
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -9,7 +9,7 @@ class JavaRunner {
|
||||
constructor() {
|
||||
// 在开发与打包后均可解析到应用根目录下的 jre 目录
|
||||
// 开发环境:项目根目录
|
||||
// 打包后:应用根目录(win-unpacked)
|
||||
// 打包后:应用根目录(最终交付目录)
|
||||
const isDev = !process.resourcesPath;
|
||||
const baseDir = isDev
|
||||
? path.join(__dirname, '..')
|
||||
|
||||
@@ -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
41
scripts/path-utils.js
Normal 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
|
||||
};
|
||||
Reference in New Issue
Block a user