签出调整

This commit is contained in:
2025-11-27 17:10:08 +08:00
parent 1119d6a7a8
commit a6d1195f13
10 changed files with 61 additions and 291 deletions

View File

@@ -1,10 +0,0 @@
# 自动生成的配置文件
application.yml
# 运行时端口记录
.running-port
# 日志文件
logs/
*.log

View File

@@ -1,71 +1,20 @@
========================================== ==========================================
NPQS9100 Java 后端服务说明 NPQS9100 Java 后端服务
========================================== ==========================================
一、目录结构 目录说明
---------------------------------------- ----------------------------------------
entrance.jar - Spring Boot 应用程序 entrance.jar - Spring Boot 应用程序
application.yml - 应用配置文件(自动生成) application.yml - 应用配置文件(自动生成)
application.yml.template - 配置模板 application.yml.template - 配置模板
kill-java-port.bat - 智能清理Java端口占用紧急用
.running-port - 运行时端口记录(自动生成,勿删除)
二、自动管理 服务管理
---------------------------------------- ----------------------------------------
正常情况下,Java 后端服务由主应用自动管理: Java 后端服务由主应用自动管理:
- 应用启动时自动启动 Java 服务 - 应用启动时自动启动
- 应用关闭时自动停止 Java 服务 - 应用关闭时自动停止
- 无需手动操作 - 默认端口18092如被占用会自动切换
三、端口智能管理
----------------------------------------
- Java 服务默认使用端口18092
- 如果 18092 被占用自动尝试18093, 18094, ...
- 启动成功后会记录实际端口到 .running-port 文件
- kill-java-port.bat 会自动识别正确的端口进行清理
四、手动清理工具
----------------------------------------
如果应用异常退出Java 进程可能残留,导致:
- 端口被占用,无法再次启动
- 可以运行 kill-java-port.bat 清理残留进程
使用方法:
1. 双击运行kill-java-port.bat
2. 脚本会自动识别实际使用的端口
3. 清理完成后即可重新启动应用
五、安全说明
----------------------------------------
⚠️ 进程停止安全性:
- 应用自动停止时,只会停止应用自己启动的 Java 进程
- 通过进程引用精确停止,不会影响其他 Java 应用
- kill-java-port.bat 只清理占用特定端口的进程
- 完全不会影响您电脑上的其他 Java 程序
⭐ 端口冲突处理:
- 如果您的电脑上有其他应用占用了 18092 端口
- 本应用会自动切换到其他端口18093, 18094...
- kill-java-port.bat 会自动识别实际端口,精确清理
六、常见问题
----------------------------------------
Q: Java 服务无法启动?
A: 运行 kill-java-port.bat 清理残留进程
Q: 端口被占用怎么办?
A: 应用会自动切换端口,无需手动处理
Q: 如何查看 Java 日志?
A: 启动应用后,日志窗口会实时显示 Java 输出
Q: 配置文件在哪里?
A: application.yml 由应用自动生成,请勿手动修改
========================================== ==========================================

View File

@@ -1,115 +0,0 @@
@echo off
chcp 65001 >nul
color 0E
title 清理NPQS9100 Java端口占用
cls
echo ==========================================
echo 清理 NPQS9100 Java 端口占用
echo ==========================================
echo.
REM 获取Java目录脚本所在目录
set JAVA_HOME=%~dp0
cd /d "%JAVA_HOME%"
REM 读取实际运行的端口
set PORT_FILE=.running-port
set JAVA_PORT=
if exist "%PORT_FILE%" (
set /p JAVA_PORT=<"%PORT_FILE%"
echo [√] 检测到运行记录:端口 %JAVA_PORT%
echo.
) else (
echo [!] 未找到运行记录文件,使用默认端口 18092
echo.
set JAVA_PORT=18092
)
echo [1] 检查端口 %JAVA_PORT% 占用情况...
echo.
REM 查找占用该端口的连接
netstat -ano | findstr ":%JAVA_PORT%" > "%TEMP%\java_port.txt"
if %errorlevel% equ 0 (
echo 发现以下端口 %JAVA_PORT% 连接:
echo ----------------------------------------
type "%TEMP%\java_port.txt"
echo ----------------------------------------
echo.
echo [2] 提取进程ID并结束进程...
echo.
REM 提取所有LISTENING状态的PID
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%JAVA_PORT%" ^| findstr "LISTENING"') do (
if not "%%a"=="0" (
echo 正在结束进程 PID: %%a
taskkill /F /PID %%a 2>nul
if errorlevel 1 (
echo [失败] 无法结束进程 %%a
) else (
echo [成功] 已结束进程 %%a
)
)
)
REM 如果还有其他状态的连接,也尝试结束
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":%JAVA_PORT%" ^| findstr /V "LISTENING"') do (
if not "%%a"=="0" (
echo 正在结束进程 PID: %%a
taskkill /F /PID %%a 2>nul
if errorlevel 1 (
echo [已结束或无权限] 进程 %%a
) else (
echo [成功] 已结束进程 %%a
)
)
)
echo.
echo [3] 清理完成
REM 等待2秒让进程完全释放
timeout /t 2 /nobreak >nul
echo.
echo [4] 再次检查端口 %JAVA_PORT%...
netstat -ano | findstr ":%JAVA_PORT%"
if errorlevel 1 (
echo [√] 端口 %JAVA_PORT% 已完全释放
REM 删除端口记录文件
if exist "%PORT_FILE%" (
del "%PORT_FILE%"
echo [√] 已清理端口记录文件
)
) else (
echo [!] 仍有连接存在可能是TIME_WAIT状态会自动释放
)
) else (
echo [√] 没有发现占用端口 %JAVA_PORT% 的进程
REM 删除端口记录文件
if exist "%PORT_FILE%" (
del "%PORT_FILE%"
echo [√] 已清理端口记录文件
)
)
REM 清理临时文件
if exist "%TEMP%\java_port.txt" del "%TEMP%\java_port.txt"
echo.
echo ==========================================
echo 清理完成
echo ==========================================
echo.
echo 说明:
echo - 此脚本只清理 NPQS9100 应用使用的 Java 端口
echo - 不会影响您电脑上的其他 Java 应用
echo.
pause

View File

@@ -57,8 +57,8 @@
"filter": ["**/*"] "filter": ["**/*"]
}, },
{ {
"from": "build/NPQS9100-启动器.bat", "from": "build/NPQS9100.bat",
"to": "NPQS9100-启动器.bat" "to": "NPQS9100.bat"
}, },
{ {
"from": "build/extraResources/使用说明.txt", "from": "build/extraResources/使用说明.txt",
@@ -91,4 +91,4 @@
] ]
}, },
"compression": "store" "compression": "store"
} }

View File

@@ -12,7 +12,7 @@ module.exports = () => {
singleLock: true, singleLock: true,
windowsOption: { windowsOption: {
title: 'NPQS9100-自动检测平台', title: 'NPQS9100-自动检测平台',
menuBarVisible: true, // 显示菜单栏,方便查看日志 menuBarVisible: false, // 隐藏菜单栏
width: 1920, width: 1920,
height: 1000, height: 1000,
minWidth: 1024, minWidth: 1024,

View File

@@ -118,6 +118,11 @@ function createTray() {
// 创建应用菜单 // 创建应用菜单
function createApplicationMenu() { function createApplicationMenu() {
// 生产环境隐藏菜单栏
Menu.setApplicationMenu(null);
// 调试时可以使用下面的菜单(取消注释)
/*
const template = [ const template = [
{ {
label: '查看', label: '查看',
@@ -147,9 +152,10 @@ function createApplicationMenu() {
] ]
} }
]; ];
const menu = Menu.buildFromTemplate(template); const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
*/
} }
// 注册 IPC 处理器 // 注册 IPC 处理器

View File

@@ -267,40 +267,41 @@ class Lifecycle {
const win = getMainWindow(); const win = getMainWindow();
win.show(); win.show();
win.focus(); win.focus();
// 添加主窗口关闭事件监听 // 窗口关闭时,使用 Electron 原生对话框确认
win.on('close', async (event) => { win.on('close', async (event) => {
// 总是弹出确认对话框 if (global.isConfirmedExit) {
logger.info('[lifecycle] Exit already confirmed, proceeding');
return;
}
event.preventDefault(); event.preventDefault();
logger.info('[lifecycle] Window close intercepted, showing exit confirm dialog');
const { dialog } = require('electron'); const { dialog } = require('electron');
const { app } = require('electron'); const { response } = await dialog.showMessageBox(win, {
const response = await dialog.showMessageBox(win, {
type: 'question', type: 'question',
buttons: ['取消', '退出'],
defaultId: 1,
cancelId: 0,
title: '退出确认', title: '退出确认',
message: '确定要退出应用吗?', message: '确定要退出应用吗?',
buttons: ['取消', '退出'], noLink: true
defaultId: 0,
cancelId: 0
}); });
if (response.response === 1) { if (response === 1) {
// 用户确认退出
logger.info('[lifecycle] User confirmed exit'); logger.info('[lifecycle] User confirmed exit');
global.isConfirmedExit = true;
// 移除所有监听器避免循环
win.removeAllListeners('close');
// 执行清理
await this.cleanup(); await this.cleanup();
if (win && !win.isDestroyed()) {
// 退出应用 win.destroy();
}
app.quit(); app.quit();
} else {
logger.info('[lifecycle] User cancelled exit');
} }
// 用户取消,什么都不做(已经 preventDefault
}); });
// 立即刷新一次,确保显示最新内容 // 立即刷新一次,确保显示最新内容
setTimeout(() => { setTimeout(() => {
if (win && !win.isDestroyed()) { if (win && !win.isDestroyed()) {
@@ -496,22 +497,14 @@ class Lifecycle {
try { try {
// 显示提示对话框 // 显示提示对话框
const { response } = await dialog.showMessageBox({ await dialog.showMessageBox({
type: 'warning', type: 'warning',
title: '需要管理员权限', title: '告警',
message: '安装 MySQL 服务需要管理员权限', message: '需要管理员权限',
detail: '应用将以管理员身份重新启动', buttons: ['确定']
buttons: ['取消', '确定'],
defaultId: 1,
cancelId: 0
}); });
if (response === 0) { // 用户点击确定,以管理员身份重启
// 用户取消,关闭应用
logger.info('[lifecycle] User cancelled admin elevation');
app.quit();
return;
}
// 获取应用可执行文件路径 // 获取应用可执行文件路径
const exePath = app.getPath('exe'); const exePath = app.getPath('exe');
@@ -588,63 +581,6 @@ class Lifecycle {
*/ */
async electronAppReady() { async electronAppReady() {
logger.info('[lifecycle] electron-app-ready'); logger.info('[lifecycle] electron-app-ready');
// 检查管理员权限
const hasAdminPrivileges = this.checkAdminPrivileges();
logger.info('[lifecycle] Has admin privileges:', hasAdminPrivileges);
if (!hasAdminPrivileges) {
logger.warn('[lifecycle] No admin privileges, will request elevation when needed');
// 立即请求管理员权限
const { dialog, app } = require('electron');
const { spawn } = require('child_process');
setTimeout(async () => {
const { response } = await dialog.showMessageBox({
type: 'warning',
title: '需要管理员权限',
message: 'NPQS9100 需要管理员权限来管理 MySQL 服务',
detail: '应用将以管理员身份重新启动。\n\n如果您拒绝应用可能无法正常工作。',
buttons: ['退出应用', '以管理员身份启动'],
defaultId: 1,
cancelId: 0
});
if (response === 0) {
logger.info('[lifecycle] User declined admin elevation, quitting');
app.quit();
return;
}
// 以管理员身份重启
try {
const exePath = app.getPath('exe');
logger.info('[lifecycle] Restarting with admin privileges:', exePath);
const psCommand = `Start-Process -FilePath "${exePath}" -Verb RunAs`;
const child = spawn('powershell.exe', ['-Command', psCommand], {
detached: true,
stdio: 'ignore',
windowsHide: true
});
child.unref();
// 设置标记,跳过清理
this.isRestartingForAdmin = true;
// 退出当前实例
setTimeout(() => {
app.quit();
}, 500);
} catch (error) {
logger.error('[lifecycle] Failed to restart as admin:', error);
dialog.showErrorBox('启动失败', '无法以管理员身份启动应用,请手动右键选择"以管理员身份运行"');
app.quit();
}
}, 1000);
}
} }
/** /**
@@ -653,6 +589,17 @@ class Lifecycle {
async windowReady() { async windowReady() {
logger.info('[lifecycle] window-ready hook triggered'); logger.info('[lifecycle] window-ready hook triggered');
// 在创建任何窗口之前,先检查管理员权限
const hasAdminPrivileges = this.checkAdminPrivileges();
logger.info('[lifecycle] Has admin privileges:', hasAdminPrivileges);
if (!hasAdminPrivileges) {
logger.warn('[lifecycle] No admin privileges, requesting elevation');
// 调用已有的 restartAsAdmin 方法,避免代码重复
await this.restartAsAdmin();
return; // 阻止后续代码执行
}
// 创建日志管理器(但不显示窗口,仅用于写日志文件) // 创建日志管理器(但不显示窗口,仅用于写日志文件)
logger.info('[lifecycle] Creating log window manager...'); logger.info('[lifecycle] Creating log window manager...');
this.logWindowManager = new LogWindowManager(); this.logWindowManager = new LogWindowManager();

View File

@@ -32,14 +32,6 @@ onMounted(() => {
const language = globalStore.language ?? getBrowserLang() const language = globalStore.language ?? getBrowserLang()
i18n.locale.value = language i18n.locale.value = language
globalStore.setGlobalState('language', language as LanguageType) globalStore.setGlobalState('language', language as LanguageType)
// 移除 autofit使用 CSS 自适应
// autofit.init({
// el: '#app',
// dw: 1440,
// dh: 900,
// resize: true,
// limit: 0.1
// })
}) })
// element language // element language

View File

@@ -14,11 +14,12 @@
"encrypt": "ee-bin encrypt", "encrypt": "ee-bin encrypt",
"icon": "ee-bin icon", "icon": "ee-bin icon",
"re-sqlite": "electron-rebuild -f -w better-sqlite3", "re-sqlite": "electron-rebuild -f -w better-sqlite3",
"build-w": "ee-bin build --cmds=win64", "build-w": "ee-bin build --cmds=win64 && npm run rename-output",
"build-we": "ee-bin build --cmds=win_e", "build-we": "ee-bin build --cmds=win_e",
"build-m": "ee-bin build --cmds=mac", "build-m": "ee-bin build --cmds=mac",
"build-m-arm64": "ee-bin build --cmds=mac_arm64", "build-m-arm64": "ee-bin build --cmds=mac_arm64",
"build-l": "ee-bin build --cmds=linux" "build-l": "ee-bin build --cmds=linux",
"rename-output": "node -e \"const fs=require('fs'); const oldPath='out/win-unpacked'; const newPath='out/NPQS-9100'; if(fs.existsSync(oldPath)){if(fs.existsSync(newPath)) fs.rmSync(newPath,{recursive:true}); fs.renameSync(oldPath, newPath); console.log('Renamed to NPQS-9100');}\""
}, },
"repository": "https://github.com/dromara/electron-egg.git", "repository": "https://github.com/dromara/electron-egg.git",
"keywords": [ "keywords": [