C端改造

This commit is contained in:
2025-12-10 18:47:17 +08:00
parent 5708afb816
commit 4c477ae22a
61 changed files with 259 additions and 135 deletions

View File

@@ -8,7 +8,19 @@
"Bash(iconv:*)",
"Bash(powershell:*)",
"Bash(del:*)",
"Bash(git -C \"c:\\code\\gitea\\NPQS-9100\\pqs-9100_client\" log -1 --oneline)"
"Bash(git -C \"c:\\code\\gitea\\NPQS-9100\\pqs-9100_client\" log -1 --oneline)",
"Bash(npm run icon:*)",
"Bash(node:*)",
"Bash(xxd:*)",
"Bash(awk:*)",
"Bash(md5sum:*)",
"Bash(if exist \"out\" rd /s /q \"out\")",
"Bash(findstr:*)",
"Read(//c/code/gitea/NPQS-9100/pqs-9100_tool_client/cmd/**)",
"Bash(copy:*)",
"Bash(npm run build-w:*)",
"Read(//c/code/gitea/NPQS-9100/pqs-9100_tool_client/**)",
"Bash(npm install:*)"
],
"deny": [],
"ask": []

View File

@@ -1,5 +1,5 @@
server:
port: 18092
port: 18093
spring:
application:
name: entrance
@@ -51,7 +51,7 @@ socket:
port: 61000
webSocket:
port: 7777
port: 7778
#源参数下发,暂态数据默认值
Dip:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

BIN
build/icons/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -1,81 +0,0 @@
@echo off
chcp 65001 >nul
title 检查并清除 MySQL9100 服务
color 0E
echo ========================================
echo 检查并清除 MySQL9100 服务
echo ========================================
echo.
REM 检查是否有管理员权限
net session >nul 2>&1
if %errorlevel% neq 0 (
echo [错误] 需要管理员权限来操作 Windows 服务
echo.
echo 请右键点击此脚本,选择"以管理员身份运行"
echo.
pause
exit /b 1
)
echo [✓] 已具有管理员权限
echo.
REM 检查服务是否存在
echo [1/4] 检查 mysql9100 服务是否存在...
sc query mysql9100 >nul 2>&1
if %errorlevel% equ 0 (
echo [✓] 发现 mysql9100 服务
echo.
REM 显示服务状态
echo [2/4] 服务状态信息:
sc query mysql9100
echo.
REM 停止服务
echo [3/4] 正在停止 mysql9100 服务...
sc stop mysql9100 >nul 2>&1
if %errorlevel% equ 0 (
echo [✓] 服务已停止
) else (
echo [!] 服务可能已经停止或停止失败(继续删除)
)
REM 等待服务完全停止
timeout /t 2 /nobreak >nul
echo.
REM 删除服务
echo [4/4] 正在删除 mysql9100 服务...
sc delete mysql9100
if %errorlevel% equ 0 (
echo [✓] 服务已成功删除
echo.
echo ========================================
echo 清除完成!
echo ========================================
) else (
echo [✗] 删除服务失败
echo.
echo 可能的原因:
echo 1. 服务仍在运行中(请重启电脑后重试)
echo 2. 权限不足
echo 3. 服务被其他程序锁定
echo.
pause
exit /b 1
)
) else (
echo [!] 未发现 mysql9100 服务
echo.
echo 系统中没有名为 mysql9100 的服务,无需清除。
)
echo.
echo 提示:如果需要完全清理,还可以:
echo 1. 删除 MySQL 数据目录mysql/data
echo 2. 删除 MySQL 配置文件mysql/my.ini
echo.
pause

20
build/upgrade/README.txt Normal file
View File

@@ -0,0 +1,20 @@
# 升级包放置目录
## 使用方法
1. 将升级文件放入此目录:
- app.asar (前端升级包 - 文件)
- app.asar.unpacked/ (前端升级包 - 文件夹)
- entrance.jar (后端升级包)
2. 双击运行根目录的 upgrade.bat 脚本
3. 升级完成后重启应用
## 注意事项
- 可以只放前端或只放后端,支持单独升级
- 升级前会自动备份到 backup/ 目录
- 如果升级失败,运行 rollback.bat 可回滚
详细说明请参考README-升级回滚.txt

View File

@@ -5,6 +5,7 @@
"directories": {
"output": "out"
},
"npmRebuild": false,
"asar": true,
"asarUnpack": [
"public/images/**/*"
@@ -21,23 +22,19 @@
"!python/"
],
"extraResources": [
{
"from": "build/extraResources/dll",
"to": "extraResources/dll",
"filter": ["**/*"]
},
{
"from": "build/extraResources/java",
"to": "extraResources/java",
"filter": ["**/*"]
},
{
"from": "build/extraResources/read.txt",
"to": "extraResources/read.txt"
"from": "build/extraResources/templates",
"to": "extraResources/templates",
"filter": ["**/*"]
},
{
"from": "build/extraResources/使用说明.txt",
"to": "extraResources/使用说明.txt"
"from": "build/extraResources/read.txt",
"to": "extraResources/read.txt"
},
{
"from": "scripts/",
@@ -57,12 +54,9 @@
"filter": ["**/*"]
},
{
"from": "build/NPQS9100.bat",
"to": "NPQS9100.bat"
},
{
"from": "build/extraResources/使用说明.txt",
"to": "使用说明.txt"
"from": "build/upgrade",
"to": "upgrade",
"filter": ["**/*"]
},
{
"from": "build/upgrade.bat",
@@ -78,7 +72,7 @@
}
],
"win": {
"icon": "public/images/icon.png",
"icon": "build/icons/icon.ico",
"requestedExecutionLevel": "requireAdministrator",
"signAndEditExecutable": false,
"verifyUpdateCodeSignature": false,
@@ -90,5 +84,16 @@
}
]
},
"nsis": {
"oneClick": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"installerIcon": "build/icons/icon.ico",
"uninstallerIcon": "build/icons/icon.ico",
"installerHeaderIcon": "build/icons/icon.ico",
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "NPQS9100"
},
"compression": "store"
}

View File

@@ -0,0 +1,65 @@
# 管理员权限说明
> ⚠️ **文档已过期** - 本文档描述的是旧版服务模式,自 2025-12-01 起已改用**进程模式(绿色包)**。
>
> 🎉 **新版本不再需要管理员权限!**
>
> 参考最新文档:`MySQL进程模式改造方案.md` 和 `NPQS-9100绿色包完整指南.md`
---
## ~~🔐 为什么需要管理员权限?~~(已废弃)
~~NPQS9100 需要管理 MySQL Windows 服务,执行以下操作:~~
- ~~✅ 安装 MySQL 服务 (`mysql9100`)~~
- ~~✅ 启动/停止服务~~
- ~~✅ 删除服务(重新安装时)~~
~~这些操作都需要 **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>
---
## 📝 版本历史
| 日期 | 版本 | 说明 |
|------|------|------|
| 2025-12-01 | v2.0 | 改用进程模式,无需管理员权限 ✅ |
| 2025-10-17 | v1.0 | 服务模式,需要管理员权限(已废弃)|

View File

@@ -25,7 +25,7 @@ module.exports = () => {
},
frame: true,
show: false, // 初始不显示,等待服务启动完成后再显示
icon: path.join(getBaseDir(), 'public', 'images', 'icon.png'),
icon: path.join(getBaseDir(), 'public', 'images', 'logo-32.png'),
},
logger: {
level: 'INFO',

View File

@@ -125,34 +125,34 @@ class Lifecycle {
}
// 步骤5: 检测 Java 端口
this.logWindowManager.addLog('system', '▶ 步骤7: 检测可用的 Java 端口从18092开始)...');
this.logWindowManager.addLog('system', '▶ 步骤7: 检测可用的 Java 端口从18093开始)...');
this.startupManager.updateProgress('check-java-port', { mysqlPort: this.mysqlPort });
this.javaPort = await PortChecker.findAvailablePort(18092, 100);
this.javaPort = await PortChecker.findAvailablePort(18093, 100);
if (this.javaPort === -1) {
this.logWindowManager.addLog('error', 'Java 端口检测失败18092-18191 全部被占用');
throw new Error('无法找到可用的后端服务端口18092-18191 全部被占用)');
this.logWindowManager.addLog('error', 'Java 端口检测失败18093-18192 全部被占用');
throw new Error('无法找到可用的后端服务端口18093-18192 全部被占用)');
}
// 步骤5.5: 检测 WebSocket 端口
this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 WebSocket 端口从7777开始)...');
this.logWindowManager.addLog('system', '▶ 步骤8: 检测可用的 WebSocket 端口从7778开始)...');
this.websocketPort = await PortChecker.findAvailablePort(7777, 100);
this.websocketPort = await PortChecker.findAvailablePort(7778, 100);
if (this.websocketPort === -1) {
this.logWindowManager.addLog('error', 'WebSocket 端口检测失败7777-7876 全部被占用');
throw new Error('无法找到可用的 WebSocket 端口7777-7876 全部被占用)');
this.logWindowManager.addLog('error', 'WebSocket 端口检测失败7778-7877 全部被占用');
throw new Error('无法找到可用的 WebSocket 端口7778-7877 全部被占用)');
}
if (this.javaPort !== 18092) {
this.logWindowManager.addLog('warn', `⚠ Java 默认端口 18092 已被占用,自动切换到端口: ${this.javaPort}`);
if (this.javaPort !== 18093) {
this.logWindowManager.addLog('warn', `⚠ Java 默认端口 18093 已被占用,自动切换到端口: ${this.javaPort}`);
} else {
this.logWindowManager.addLog('success', `✓ Java 将使用默认端口: ${this.javaPort}`);
}
if (this.websocketPort !== 7777) {
this.logWindowManager.addLog('warn', `⚠ WebSocket 默认端口 7777 已被占用,自动切换到端口: ${this.websocketPort}`);
if (this.websocketPort !== 7778) {
this.logWindowManager.addLog('warn', `⚠ WebSocket 默认端口 7778 已被占用,自动切换到端口: ${this.websocketPort}`);
} else {
this.logWindowManager.addLog('success', `✓ WebSocket 将使用默认端口: ${this.websocketPort}`);
}
@@ -434,8 +434,17 @@ 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, {
javaPort: this.javaPort
javaPort: this.javaPort,
logPath: logPath // 传递日志路径
});
// 监听Java进程输出

View File

@@ -21,7 +21,7 @@ VITE_API_URL=/api
#VITE_PROXY=[["/api","http://127.0.0.1:18092/"]]
#VITE_PROXY=[["/api","http://192.168.1.124:18092/"]]
VITE_PROXY=[["/api","http://192.168.1.125:18092/"]]
VITE_PROXY=[["/api","http://192.168.2.125:18092/"]]
# VITE_PROXY=[["/api","http://192.168.1.138:8080/"]]张文
# 开启激活验证
VITE_ACTIVATE_OPEN=true
VITE_ACTIVATE_OPEN=false

View File

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

View File

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

View File

@@ -1131,7 +1131,7 @@ const open = async (sign: string, data: Plan.ReqPlan, currentMode: string, plan:
formContent.name = ''
// 新建子计划允许选择数据源
const datasourceDicts = dictStore.getDictData('Datasource')
formContent.datasourceIds = [datasourceDicts[0]?.code] ?? []
formContent.datasourceIds = datasourceDicts[0]?.code ? [datasourceDicts[0].code] : []
formContent.memberIds = []
}
generateData()

View File

@@ -35,7 +35,8 @@
"debug": "^4.4.0",
"ee-bin": "^4.1.4",
"electron": "^31.7.6",
"electron-builder": "^25.1.8"
"electron-builder": "^25.1.8",
"icon-gen": "^5.0.0"
},
"dependencies": {
"autofit.js": "^3.2.8",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 739 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

BIN
public/images/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 786 B

After

Width:  |  Height:  |  Size: 943 B

View File

@@ -88,15 +88,15 @@ class ConfigGenerator {
console.log('[ConfigGenerator] Data path:', this.dataPath);
console.log('[ConfigGenerator] MySQL port:', options.mysqlPort || 3306);
console.log('[ConfigGenerator] MySQL password:', options.mysqlPassword || 'njcnpqs');
console.log('[ConfigGenerator] Java port:', options.javaPort || 18092);
console.log('[ConfigGenerator] WebSocket port:', options.websocketPort || 7777);
console.log('[ConfigGenerator] Java port:', options.javaPort || 18093);
console.log('[ConfigGenerator] WebSocket port:', options.websocketPort || 7778);
resolve({
configPath: this.configPath,
dataPath: this.dataPath,
mysqlPort: options.mysqlPort || 3306,
javaPort: options.javaPort || 18092,
websocketPort: options.websocketPort || 7777
javaPort: options.javaPort || 18093,
websocketPort: options.websocketPort || 7778
});
} catch (error) {
console.error('[ConfigGenerator] Failed to generate config:', error);
@@ -123,6 +123,75 @@ class ConfigGenerator {
console.log('[ConfigGenerator] Created directory:', dir);
}
});
// 复制内置的报告模板文件
this.copyBuiltInTemplates();
}
/**
* 复制内置的报告模板文件到用户数据目录
*/
copyBuiltInTemplates() {
try {
const isDev = !process.resourcesPath;
const baseDir = isDev
? path.join(__dirname, '..')
: path.dirname(process.resourcesPath);
// 内置模板源路径
const templateSource = isDev
? path.join(baseDir, 'build', 'extraResources', 'templates')
: path.join(process.resourcesPath, 'extraResources', 'templates');
// 目标路径:用户数据目录/template/
const templateDest = path.join(this.dataPath, 'template');
// 检查源模板是否存在
if (!fs.existsSync(templateSource)) {
console.log('[ConfigGenerator] Built-in templates not found, skipping copy');
return;
}
// 递归复制模板文件(只复制不存在的文件,不覆盖已有文件)
this.copyTemplatesRecursive(templateSource, templateDest);
console.log('[ConfigGenerator] Built-in templates copied successfully');
} catch (error) {
console.error('[ConfigGenerator] Failed to copy templates:', error);
}
}
/**
* 递归复制模板文件(不覆盖已存在的文件)
* @param {string} src 源目录
* @param {string} dest 目标目录
*/
copyTemplatesRecursive(src, dest) {
// 确保目标目录存在
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
// 读取源目录内容
const entries = fs.readdirSync(src, { withFileTypes: true });
entries.forEach(entry => {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
// 递归复制子目录
this.copyTemplatesRecursive(srcPath, destPath);
} else {
// 只复制不存在的文件(不覆盖用户修改过的文件)
if (!fs.existsSync(destPath)) {
fs.copyFileSync(srcPath, destPath);
console.log('[ConfigGenerator] Copied template:', destPath);
} else {
console.log('[ConfigGenerator] Template already exists, skipping:', destPath);
}
}
});
}
/**

View File

@@ -166,11 +166,20 @@ class JavaRunner {
'-Dfile.encoding=UTF-8', // 设置文件编码为UTF-8解决中文乱码
'-Duser.language=zh', // 设置语言为中文
'-Duser.region=CN', // 设置地区为中国
'-jar',
jarPath,
`--spring.config.location=${configPath}`
];
// 如果提供了 logPath通过 JVM 系统属性传递给 logback
if (options.logPath) {
// Windows 路径使用正斜杠或保持原样JVM 会自动处理
// 方案1转换为正斜杠跨平台兼容
const normalizedLogPath = options.logPath.replace(/\\/g, '/');
javaArgs.push(`-DlogHomeDir=${normalizedLogPath}`);
console.log('[Java] Setting log path to:', normalizedLogPath);
console.log('[Java] Original log path:', options.logPath);
}
javaArgs.push('-jar', jarPath, `--spring.config.location=${configPath}`);
const defaultOptions = {
cwd: path.dirname(jarPath),
stdio: ['ignore', 'pipe', 'pipe'],

View File

@@ -300,6 +300,7 @@ default-character-set=utf8mb4
// 如果还没退出,强制杀死
this.log('warn', '优雅关闭超时,强制终止...');
this.mysqlProcess.kill('SIGTERM');
await this.sleep(1000);
}
this.log('success', 'MySQL 进程已关闭');
@@ -309,16 +310,30 @@ default-character-set=utf8mb4
if (this.mysqlProcess) {
this.mysqlProcess.kill('SIGTERM');
await this.sleep(1000);
}
// 确保进程被杀死
try {
await this.execCommand('taskkill /F /IM mysqld.exe');
await this.sleep(2000); // 等待文件句柄释放
} catch (e) {
// 忽略
}
}
// 最终检查:确保所有 mysqld.exe 进程都被终止
try {
const { stdout } = await this.execCommand('tasklist /FI "IMAGENAME eq mysqld.exe" /FO CSV /NH');
if (stdout.includes('mysqld.exe')) {
this.log('warn', '检测到残留进程,强制清理...');
await this.execCommand('taskkill /F /IM mysqld.exe');
await this.sleep(2000); // 额外等待以确保文件句柄释放
}
} catch (e) {
// 忽略
}
this.mysqlProcess = null;
this.currentPort = null;
}