// electron/main.ts import { app, BrowserWindow, Menu, globalShortcut, net, ipcMain } from 'electron'; import path from 'path'; import http from 'http'; import { URL } from 'url'; //import { apiRoutes } from './api'; // 导入API路由配置 import dotenv from 'dotenv'; // 添加openai模块 import OpenAI from 'openai'; dotenv.config({ path: path.join(__dirname, '../.env') }); // 根据实际路径调整 // 确保在news.ts中能读取到 console.log('JUHE_KEY:', process.env.JUHE_NEWS_KEY?.substring(0, 3) + '***'); // 打印前3位验证 console.log('CAIYUN_KEY:', process.env.CAIYUN_API_KEY?.substring(0, 3) + '***'); // 打印前3位验证 // 环境配置 const isDev = process.env.NODE_ENV === 'development'; // 注册 IPC 处理器 function registerIpcHandlers() { // 天气接口(需要修改) ipcMain.handle('get-weather', async (_, { lon, lat }) => { console.log('[IPC] 开始获取天气数据', { lon, lat }); // 检查缓存有效性 const now = Date.now(); if (now - apiCache.weather.timestamp < apiCache.weather.ttl) { console.log('[Cache] 返回缓存的天气数据'); return apiCache.weather.data; } try { const apiKey = process.env.CAIYUN_API_KEY!; const apiUrl = `https://api.caiyunapp.com/v2.6/${apiKey}/${lon},${lat}/weather`; const response = await net.fetch(apiUrl); const jsonData = await response.json(); if (jsonData.status !== 'ok') { throw new Error(jsonData.message || '天气数据获取失败'); } // 详细数据格式化 const formattedData = { realtime: formatRealtime(jsonData.result.realtime), forecast: formatDailyForecast(jsonData.result.daily) }; console.log('[IPC] 格式化天气数据:', formattedData); // 更新缓存 apiCache.weather.data = formattedData; apiCache.weather.timestamp = now; return formattedData; } catch (error: any) { console.error('[IPC] 天气请求失败:', error); return { error: true, message: error.message }; } }); // 实时天气格式化 const formatRealtime = (data: any) => ({ temperature: data.temperature.toFixed(1), humidity: (data.humidity * 100).toFixed(1) + '%', wind: { speed: (data.wind.speed * 3.6).toFixed(1) + 'km/h', direction: getWindDirection(data.wind.direction) }, airQuality: { aqi: data.air_quality.aqi.chn, description: data.air_quality.description.chn }, skycon: data.skycon, apparentTemperature: data.apparent_temperature.toFixed(1) }); // 天气预报格式化 const formatDailyForecast = (data: any) => ({ temperature: data.temperature.map((item: any) => ({ date: item.date, max: item.max, min: item.min })), skycon: data.skycon, precipitation: data.precipitation }); // 风向转换 const getWindDirection = (degrees: number) => { const directions = ['北风', '东北风', '东风', '东南风', '南风', '西南风', '西风', '西北风']; return directions[Math.round(degrees % 360 / 45) % 8]; }; // 新闻接口 ipcMain.handle('get-news', async () => { console.log('[IPC] 开始获取新闻数据'); const now = Date.now(); if (now - apiCache.news.timestamp < apiCache.news.ttl) { console.log('[Cache] 返回缓存的新闻数据'); return apiCache.news.data; } try { const apiKey = process.env.JUHE_NEWS_KEY!; const response = await net.fetch( `https://v.juhe.cn/toutiao/index?type=top&key=${apiKey}` ); const json = await response.json(); console.log('[IPC] 新闻响应:', json); // 添加原始响应日志 const formatted = formatNews(json.result?.data || []); // 更新缓存 apiCache.news.data = formatted; apiCache.news.timestamp = now; return formatted; //return formatNews(json.result?.data || []); } catch (error) { console.error('[IPC] 新闻请求失败:', error); throw error; } }); // 聊天接口 ipcMain.handle('chat-with-deepseek', async (event, { messages }) => { try { const openai = new OpenAI({ baseURL: 'https://api.deepseek.com', apiKey: process.env.DEEPSEEK_API_KEY!, }); const stream = await openai.chat.completions.create({ model: 'deepseek-chat', messages, stream: true, }); for await (const chunk of stream) { const content = chunk.choices[0]?.delta?.content || ''; event.sender.send('chat-stream-chunk', content); } event.sender.send('chat-stream-end'); } catch (error: any) { console.error('Deepseek API Error:', error); event.sender.send('chat-stream-error', error.message); throw error; } }); } const formatNews = (items: any[]) => items.map(item => ({ uniquekey: item.uniquekey, title: item.title, date: item.date, category: item.category || "头条新闻", author_name: item.author_name || "未知作者", url: item.url, thumbnail_pic_s: item.thumbnail_pic_s, is_content: item.is_content })); // 添加缓存对象 const apiCache = { weather: { data: null as any, timestamp: 0, //ttl: 1800000 // 30分钟缓存 //6小时缓存 ttl: 21600000 }, news: { data: null as any, timestamp: 0, //ttl: 3600000 // 1小时缓存 //2小时缓存 ttl: 7200000 } }; // 禁用默认菜单(提升安全性和专业性) Menu.setApplicationMenu(null); /** * 创建主窗口 */ async function createWindow() { const mainWindow = new BrowserWindow({ width: 1200, height: 800, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, // 启用上下文隔离(安全必需) nodeIntegration: false, // 禁用Node集成(安全要求) webSecurity: !isDev, // 生产环境启用安全策略 sandbox: true // 启用沙箱模式 }, show: false // 先隐藏窗口直到内容加载完成 }); // 优化加载体验 mainWindow.once('ready-to-show', () => { mainWindow.show(); if (isDev) { mainWindow.webContents.openDevTools({ mode: 'detach' }); // 打开开发者工具 console.log('Developer tools opened in detached mode'); } }); // 新增权限处理代码(必须在loadURL之前) const ses = mainWindow.webContents.session; // 自动处理权限请求 ses.setPermissionRequestHandler((webContents, permission, callback) => { const allowedPermissions = new Set(['media', 'microphone', 'audioCapture']); callback(allowedPermissions.has(permission)); }); // 自动授予设备权限 // 替换原来的setDevicePermissionHandler ses.setPermissionRequestHandler((webContents, permission, callback) => { // 统一允许所有媒体相关权限 if (['microphone', 'audioCapture', 'media'].includes(permission)) { return callback(true); } callback(false); }); // 加载内容 const loadURL = isDev ? 'http://localhost:5173' : `file://${path.join(__dirname, '../dist/index.html')}`; console.log(`Loading URL: ${loadURL}`); await mainWindow.loadURL(loadURL); registerGlobalShortcuts(mainWindow); return mainWindow; } /** * 统一响应格式(带安全头) */ function sendResponse( res: http.ServerResponse, status: number, data: unknown, headers: Record = {} ) { const securityHeaders = { 'Content-Security-Policy': "default-src 'self'", 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY' }; const responseHeaders = { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...securityHeaders, ...headers }; res.writeHead(status, responseHeaders); res.end(JSON.stringify(data)); } /** * 注册全局快捷键 */ function registerGlobalShortcuts(win: BrowserWindow) { const shortcut = process.platform === 'darwin' ? 'Command+Q' : 'Ctrl+Q'; const ret = globalShortcut.register(shortcut, () => { console.log('Gracefully shutting down...'); win.destroy(); app.quit(); }); if (!ret) { console.error('Failed to register shortcut'); } } /** * 应用启动流程 */ app.whenReady() .then(() => { // 注册 IPC 处理器的代码 registerIpcHandlers(); console.log('Creating browser window...'); return createWindow(); }) .catch((error) => { console.error('App initialization failed:', error); app.quit(); }); /** * 生命周期管理 */ app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('will-quit', () => { globalShortcut.unregisterAll(); });