mirror3/electron/main.ts

294 lines
8.1 KiB
TypeScript
Raw Normal View History

2025-03-06 18:22:25 +00:00
// 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';
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位验证
dotenv.config({ path: path.join(__dirname, '../.env') });
// 环境配置
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;
}
});
}
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<string, string> = {}
) {
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();
});