Files
pqs-9100_client/scripts/log-window-manager.js
2025-11-26 08:50:22 +08:00

423 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { BrowserWindow } = require('electron');
const path = require('path');
const fs = require('fs');
/**
* 日志窗口管理器
* 显示 MySQL 和 Spring Boot 的实时日志
*/
class LogWindowManager {
constructor() {
this.logWindow = null;
this.logs = [];
this.maxLogs = 1000; // 最多保留1000条日志
// 初始化日志文件路径
this.initLogFile();
}
/**
* 初始化日志文件路径(按天滚动)
*/
initLogFile() {
// 开发环境:项目根目录的 logs 文件夹
// 打包后:应用根目录的 logs 文件夹
const isDev = !process.resourcesPath;
const baseDir = isDev
? path.join(__dirname, '..')
: path.dirname(process.resourcesPath);
this.logsDir = path.join(baseDir, 'logs');
// 确保 logs 目录存在
if (!fs.existsSync(this.logsDir)) {
fs.mkdirSync(this.logsDir, { recursive: true });
}
// 生成当天的日志文件名startup-YYYYMMDD.log
const today = new Date();
const dateStr = today.getFullYear() +
String(today.getMonth() + 1).padStart(2, '0') +
String(today.getDate()).padStart(2, '0');
this.logFilePath = path.join(this.logsDir, `startup-${dateStr}.log`);
console.log('[LogWindowManager] Log file:', this.logFilePath);
// 写入启动标记
this.writeToFile(new Date().toISOString().replace('T', ' ').substring(0, 19), 'SYSTEM', '=' .repeat(80));
this.writeToFile(new Date().toISOString().replace('T', ' ').substring(0, 19), 'SYSTEM', 'NPQS9100 应用启动');
this.writeToFile(new Date().toISOString().replace('T', ' ').substring(0, 19), 'SYSTEM', '=' .repeat(80));
// 清理超过30天的旧日志
this.cleanOldLogs(30);
}
/**
* 清理旧日志文件
* @param {number} days - 保留天数
*/
cleanOldLogs(days) {
try {
const now = Date.now();
const maxAge = days * 24 * 60 * 60 * 1000; // 转换为毫秒
const files = fs.readdirSync(this.logsDir);
let deletedCount = 0;
files.forEach(file => {
if (file.startsWith('startup-') && file.endsWith('.log')) {
const filePath = path.join(this.logsDir, file);
const stats = fs.statSync(filePath);
const age = now - stats.mtimeMs;
if (age > maxAge) {
fs.unlinkSync(filePath);
deletedCount++;
console.log(`[LogWindowManager] Deleted old log: ${file}`);
}
}
});
if (deletedCount > 0) {
console.log(`[LogWindowManager] Cleaned up ${deletedCount} old log file(s)`);
}
} catch (error) {
console.error('[LogWindowManager] Failed to clean old logs:', error);
}
}
/**
* 写入日志到文件
*/
writeToFile(timestamp, type, message) {
try {
const logLine = `[${timestamp}] [${type.toUpperCase()}] ${message}\n`;
fs.appendFileSync(this.logFilePath, logLine, 'utf-8');
} catch (error) {
console.error('[LogWindowManager] Failed to write log to file:', error);
}
}
/**
* 创建日志窗口
*/
createLogWindow() {
const isDev = !process.resourcesPath;
const iconPath = isDev
? path.join(__dirname, '..', 'public', 'images', 'icon.png')
: path.join(process.resourcesPath, 'app.asar.unpacked', 'public', 'images', 'icon.png');
this.logWindow = new BrowserWindow({
width: 900,
height: 600,
title: 'NPQS9100 - 服务日志',
backgroundColor: '#1e1e1e',
icon: iconPath,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
// 加载日志页面
const logHtml = this.generateLogHTML();
this.logWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(logHtml)}`);
// 窗口关闭事件 - 只清理引用,不退出应用
this.logWindow.on('closed', () => {
console.log('[LogWindow] Log window closed by user');
this.logWindow = null;
});
// 防止日志窗口关闭时退出应用(但允许隐藏)
this.closeHandler = (event) => {
// 只是隐藏窗口,不是真正关闭
// 这样可以随时再打开
event.preventDefault();
this.logWindow.hide();
console.log('[LogWindow] Log window hidden');
};
this.logWindow.on('close', this.closeHandler);
return this.logWindow;
}
/**
* 生成日志HTML页面
*/
generateLogHTML() {
return `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>NPQS9100 服务日志</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Consolas', 'Courier New', monospace;
background: #1e1e1e;
color: #d4d4d4;
padding: 10px;
overflow: hidden;
}
.header {
background: #2d2d30;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-size: 14px;
font-weight: bold;
color: #4ec9b0;
}
.controls button {
background: #0e639c;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
margin-left: 5px;
font-size: 12px;
}
.controls button:hover {
background: #1177bb;
}
.log-container {
background: #1e1e1e;
border: 1px solid #3e3e42;
border-radius: 4px;
height: calc(100vh - 70px);
overflow-y: auto;
padding: 10px;
font-size: 12px;
line-height: 1.5;
}
.log-entry {
margin-bottom: 2px;
white-space: pre-wrap;
word-wrap: break-word;
}
.log-mysql { color: #4ec9b0; }
.log-java { color: #dcdcaa; }
.log-system { color: #569cd6; }
.log-error { color: #f48771; }
.log-warn { color: #ce9178; }
.log-success { color: #608b4e; }
.timestamp {
color: #858585;
margin-right: 8px;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1e1e1e;
}
::-webkit-scrollbar-thumb {
background: #424242;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #4e4e4e;
}
</style>
</head>
<body>
<div class="header">
<div class="title">📝 NPQS9100 服务日志监控</div>
<div class="controls">
<button onclick="clearLogs()">清空日志</button>
<button onclick="toggleAutoScroll()">自动滚动: <span id="autoScrollStatus">开</span></button>
</div>
</div>
<div class="log-container" id="logContainer"></div>
<script>
const { ipcRenderer } = require('electron');
const logContainer = document.getElementById('logContainer');
let autoScroll = true;
// 接收日志
ipcRenderer.on('log-message', (event, data) => {
addLog(data);
});
function addLog(data) {
const entry = document.createElement('div');
entry.className = 'log-entry';
const timestamp = document.createElement('span');
timestamp.className = 'timestamp';
timestamp.textContent = data.timestamp;
entry.appendChild(timestamp);
const message = document.createElement('span');
message.className = \`log-\${data.type}\`;
message.textContent = data.message;
entry.appendChild(message);
logContainer.appendChild(entry);
// 自动滚动到底部
if (autoScroll) {
logContainer.scrollTop = logContainer.scrollHeight;
}
// 限制日志条数
while (logContainer.children.length > 1000) {
logContainer.removeChild(logContainer.firstChild);
}
}
function clearLogs() {
logContainer.innerHTML = '';
}
function toggleAutoScroll() {
autoScroll = !autoScroll;
document.getElementById('autoScrollStatus').textContent = autoScroll ? '开' : '关';
}
</script>
</body>
</html>
`;
}
/**
* 添加日志
*/
addLog(type, message) {
const now = new Date();
const timestamp = now.toLocaleTimeString();
const fullTimestamp = now.toISOString().replace('T', ' ').substring(0, 19);
const logEntry = {
timestamp,
type,
message
};
this.logs.push(logEntry);
// 限制日志数量
if (this.logs.length > this.maxLogs) {
this.logs.shift();
}
// 写入文件(使用完整时间戳)
this.writeToFile(fullTimestamp, type, message);
// 发送到窗口
if (this.logWindow && !this.logWindow.isDestroyed()) {
this.logWindow.webContents.send('log-message', logEntry);
}
// 同时输出到控制台
console.log(`[${type.toUpperCase()}] ${message}`);
}
/**
* 显示日志窗口
*/
show() {
if (!this.logWindow || this.logWindow.isDestroyed()) {
// 窗口已被销毁,重新创建
console.log('[LogWindow] Recreating log window...');
this.createLogWindow();
// 重新发送历史日志
this.logs.forEach(log => {
this.logWindow.webContents.send('log-message', log);
});
} else {
this.logWindow.show();
this.logWindow.focus();
console.log('[LogWindow] Log window shown');
}
}
/**
* 隐藏日志窗口
*/
hide() {
if (this.logWindow && !this.logWindow.isDestroyed()) {
this.logWindow.hide();
console.log('[LogWindow] Log window hidden');
}
}
/**
* 检查日志窗口是否可见
*/
isVisible() {
return this.logWindow && !this.logWindow.isDestroyed() && this.logWindow.isVisible();
}
/**
* 切换日志窗口显示/隐藏
*/
toggle() {
if (this.isVisible()) {
this.hide();
} else {
this.show();
}
}
/**
* 关闭日志窗口(真正销毁)
*/
close() {
if (this.logWindow && !this.logWindow.isDestroyed()) {
try {
// 移除 close 事件监听,允许真正关闭
if (this.closeHandler) {
this.logWindow.removeListener('close', this.closeHandler);
}
this.logWindow.removeAllListeners('close');
this.logWindow.removeAllListeners('closed');
// 强制销毁窗口
this.logWindow.destroy();
console.log('[LogWindow] Log window destroyed');
} catch (error) {
console.error('[LogWindow] Error closing log window:', error);
} finally {
this.logWindow = null;
this.closeHandler = null;
}
}
}
/**
* 获取所有日志
*/
getLogs() {
return this.logs;
}
/**
* 清空日志
*/
clearLogs() {
this.logs = [];
if (this.logWindow && !this.logWindow.isDestroyed()) {
this.logWindow.webContents.send('clear-logs');
}
}
}
module.exports = LogWindowManager;