423 lines
11 KiB
JavaScript
423 lines
11 KiB
JavaScript
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;
|
||
|