import { app, BrowserWindow, ipcMain, dialog, shell } from 'electron' import * as path from 'path' import * as fs from 'fs' import { spawn } from 'child_process' import { getAppRoot, checkEnvironment } from './services/environmentService' import { setMainWindow as setDownloadMainWindow } from './services/downloadService' import { setMainWindow as setPythonMainWindow, downloadPython, installPipPackage, installDependencies, startBackend, stopBackend, } from './services/pythonService' import { setMainWindow as setGitMainWindow, downloadGit, cloneBackend } from './services/gitService' import { setupLogger, log, getLogPath, getLogFiles, cleanOldLogs } from './services/logService' // 检查是否以管理员权限运行 function isRunningAsAdmin(): boolean { try { // 在Windows上,尝试写入系统目录来检查管理员权限 if (process.platform === 'win32') { const testPath = path.join(process.env.WINDIR || 'C:\\Windows', 'temp', 'admin-test.tmp') try { fs.writeFileSync(testPath, 'test') fs.unlinkSync(testPath) return true } catch { return false } } return true // 非Windows系统暂时返回true } catch { return false } } // 重新以管理员权限启动应用 function restartAsAdmin(): void { if (process.platform === 'win32') { const exePath = process.execPath const args = process.argv.slice(1) // 使用PowerShell以管理员权限启动 spawn( 'powershell', [ '-Command', `Start-Process -FilePath "${exePath}" -ArgumentList "${args.join(' ')}" -Verb RunAs`, ], { detached: true, stdio: 'ignore', } ) app.quit() } } let mainWindow: BrowserWindow | null = null function createWindow() { log.info('开始创建主窗口') mainWindow = new BrowserWindow({ width: 1600, height: 1000, minWidth: 800, minHeight: 600, icon: path.join(__dirname, '../src/assets/AUTO-MAS.ico'), frame: false, // 去掉系统标题栏 titleBarStyle: 'hidden', // 隐藏标题栏 webPreferences: { preload: path.join(__dirname, 'preload.js'), nodeIntegration: false, contextIsolation: true, }, autoHideMenuBar: true, }) mainWindow.setMenuBarVisibility(false) const devServer = process.env.VITE_DEV_SERVER_URL if (devServer) { log.info(`加载开发服务器: ${devServer}`) mainWindow.loadURL(devServer) } else { const indexHtmlPath = path.join(app.getAppPath(), 'dist', 'index.html') log.info(`加载生产环境页面: ${indexHtmlPath}`) mainWindow.loadFile(indexHtmlPath) } mainWindow.on('closed', () => { log.info('主窗口已关闭') mainWindow = null }) // 设置各个服务的主窗口引用 if (mainWindow) { setDownloadMainWindow(mainWindow) setPythonMainWindow(mainWindow) setGitMainWindow(mainWindow) log.info('主窗口创建完成,服务引用已设置') } } // IPC处理函数 ipcMain.handle('open-dev-tools', () => { if (mainWindow) { mainWindow.webContents.openDevTools({ mode: 'undocked' }) } }) // 窗口控制 ipcMain.handle('window-minimize', () => { if (mainWindow) { mainWindow.minimize() } }) ipcMain.handle('window-maximize', () => { if (mainWindow) { if (mainWindow.isMaximized()) { mainWindow.unmaximize() } else { mainWindow.maximize() } } }) ipcMain.handle('window-close', () => { if (mainWindow) { mainWindow.close() } }) ipcMain.handle('window-is-maximized', () => { return mainWindow ? mainWindow.isMaximized() : false }) ipcMain.handle('select-folder', async () => { if (!mainWindow) return null const result = await dialog.showOpenDialog(mainWindow, { properties: ['openDirectory'], title: '选择文件夹', }) return result.canceled ? null : result.filePaths[0] }) ipcMain.handle('select-file', async (event, filters = []) => { if (!mainWindow) return [] const result = await dialog.showOpenDialog(mainWindow, { properties: ['openFile'], title: '选择文件', filters: filters.length > 0 ? filters : [{ name: '所有文件', extensions: ['*'] }], }) return result.canceled ? [] : result.filePaths }) // 在系统默认浏览器中打开URL ipcMain.handle('open-url', async (event, url: string) => { try { await shell.openExternal(url) return { success: true } } catch (error) { if (error instanceof Error) { console.error('打开链接失败:', error.message) return { success: false, error: error.message } } else { console.error('未知错误:', error) return { success: false, error: String(error) } } } }) // 环境检查 ipcMain.handle('check-environment', async () => { const appRoot = getAppRoot() return checkEnvironment(appRoot) }) // Python相关 ipcMain.handle('download-python', async (event, mirror = 'tsinghua') => { const appRoot = getAppRoot() return downloadPython(appRoot, mirror) }) ipcMain.handle('install-pip', async () => { const appRoot = getAppRoot() return installPipPackage(appRoot) }) ipcMain.handle('install-dependencies', async (event, mirror = 'tsinghua') => { const appRoot = getAppRoot() return installDependencies(appRoot, mirror) }) ipcMain.handle('start-backend', async () => { const appRoot = getAppRoot() return startBackend(appRoot) }) // Git相关 ipcMain.handle('download-git', async () => { const appRoot = getAppRoot() return downloadGit(appRoot) }) ipcMain.handle( 'clone-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { const appRoot = getAppRoot() return cloneBackend(appRoot, repoUrl) } ) ipcMain.handle( 'update-backend', async (event, repoUrl = 'https://github.com/DLmaster361/AUTO_MAA.git') => { const appRoot = getAppRoot() return cloneBackend(appRoot, repoUrl) // 使用相同的逻辑,会自动判断是pull还是clone } ) // 配置文件操作 ipcMain.handle('save-config', async (event, config) => { try { const appRoot = getAppRoot() const configDir = path.join(appRoot, 'config') const configPath = path.join(configDir, 'frontend_config.json') // 确保config目录存在 if (!fs.existsSync(configDir)) { fs.mkdirSync(configDir, { recursive: true }) } fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8') console.log(`配置已保存到: ${configPath}`) } catch (error) { console.error('保存配置文件失败:', error) throw error } }) ipcMain.handle('load-config', async () => { try { const appRoot = getAppRoot() const configPath = path.join(appRoot, 'config', 'frontend_config.json') if (fs.existsSync(configPath)) { const config = fs.readFileSync(configPath, 'utf8') console.log(`从文件加载配置: ${configPath}`) return JSON.parse(config) } return null } catch (error) { console.error('加载配置文件失败:', error) return null } }) ipcMain.handle('reset-config', async () => { try { const appRoot = getAppRoot() const configPath = path.join(appRoot, 'config', 'frontend_config.json') if (fs.existsSync(configPath)) { fs.unlinkSync(configPath) console.log(`配置文件已删除: ${configPath}`) } } catch (error) { console.error('重置配置文件失败:', error) throw error } }) // 日志文件操作 ipcMain.handle('get-log-path', async () => { try { return getLogPath() } catch (error) { log.error('获取日志路径失败:', error) throw error } }) ipcMain.handle('get-log-files', async () => { try { return getLogFiles() } catch (error) { log.error('获取日志文件列表失败:', error) throw error } }) ipcMain.handle('get-logs', async (event, lines?: number, fileName?: string) => { try { let logFilePath: string if (fileName) { // 如果指定了文件名,使用指定的文件 const appRoot = getAppRoot() logFilePath = path.join(appRoot, 'logs', fileName) } else { // 否则使用当前日志文件 logFilePath = getLogPath() } if (!fs.existsSync(logFilePath)) { return '' } const logs = fs.readFileSync(logFilePath, 'utf8') if (lines && lines > 0) { const logLines = logs.split('\n') return logLines.slice(-lines).join('\n') } return logs } catch (error) { log.error('读取日志文件失败:', error) throw error } }) ipcMain.handle('clear-logs', async (event, fileName?: string) => { try { let logFilePath: string if (fileName) { // 如果指定了文件名,清空指定的文件 const appRoot = getAppRoot() logFilePath = path.join(appRoot, 'logs', fileName) } else { // 否则清空当前日志文件 logFilePath = getLogPath() } if (fs.existsSync(logFilePath)) { fs.writeFileSync(logFilePath, '', 'utf8') log.info(`日志文件已清空: ${fileName || '当前文件'}`) } } catch (error) { log.error('清空日志文件失败:', error) throw error } }) ipcMain.handle('clean-old-logs', async (event, daysToKeep = 7) => { try { cleanOldLogs(daysToKeep) log.info(`已清理${daysToKeep}天前的旧日志文件`) } catch (error) { log.error('清理旧日志文件失败:', error) throw error } }) // 保留原有的日志操作方法以兼容现有代码 ipcMain.handle('save-logs-to-file', async (event, logs: string) => { try { const appRoot = getAppRoot() const logsDir = path.join(appRoot, 'logs') // 确保logs目录存在 if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }) } const logFilePath = path.join(logsDir, 'app.log') fs.writeFileSync(logFilePath, logs, 'utf8') log.info(`日志已保存到: ${logFilePath}`) } catch (error) { log.error('保存日志文件失败:', error) throw error } }) ipcMain.handle('load-logs-from-file', async () => { try { const appRoot = getAppRoot() const logFilePath = path.join(appRoot, 'logs', 'app.log') if (fs.existsSync(logFilePath)) { const logs = fs.readFileSync(logFilePath, 'utf8') log.info(`从文件加载日志: ${logFilePath}`) return logs } return null } catch (error) { log.error('加载日志文件失败:', error) return null } }) // 管理员权限相关 ipcMain.handle('check-admin', () => { return isRunningAsAdmin() }) ipcMain.handle('restart-as-admin', () => { restartAsAdmin() }) // 应用生命周期 // 保证应用单例运行 const gotTheLock = app.requestSingleInstanceLock() if (!gotTheLock) { app.quit() process.exit(0) } app.on('second-instance', () => { if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore() mainWindow.focus() } }) app.on('before-quit', async event => { // 只处理一次,避免多重触发 event.preventDefault() log.info('应用准备退出') try { await stopBackend() log.info('后端服务已停止') } catch (e) { log.error('停止后端时出错:', e) console.error('停止后端时出错:', e) } finally { log.info('应用退出') app.exit(0) } }) app.whenReady().then(() => { // 初始化日志系统 setupLogger() // 清理7天前的旧日志 cleanOldLogs(7) log.info('应用启动') log.info(`应用版本: ${app.getVersion()}`) log.info(`Electron版本: ${process.versions.electron}`) log.info(`Node版本: ${process.versions.node}`) log.info(`平台: ${process.platform}`) // 检查管理员权限 if (!isRunningAsAdmin()) { log.warn('应用未以管理员权限运行') console.log('应用未以管理员权限运行') // 在生产环境中,可以选择是否强制要求管理员权限 // 这里先创建窗口,让用户选择是否重新启动 } else { log.info('应用以管理员权限运行') } createWindow() }) app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit() }) app.on('activate', () => { if (mainWindow === null) createWindow() })