Electron + Vue3开源跨平台壁纸工具实战(八)主进程-核心功能

系列

Electron + Vue3开源跨平台壁纸工具实战(一)

Electron + Vue3开源跨平台壁纸工具实战(二)本地运行

Electron + Vue3开源跨平台壁纸工具实战(三)主进程

Electron + Vue3开源跨平台壁纸工具实战(四)主进程-数据管理(1)

Electron + Vue3开源跨平台壁纸工具实战(五)主进程-数据管理(2)

Electron + Vue3开源跨平台壁纸工具实战(六)子进程服务

Electron + Vue3开源跨平台壁纸工具实战(七)进程通信

Electron + Vue3开源跨平台壁纸工具实战(八)主进程-核心功能

源码

省流点我进Github获取源码,欢迎fork、star、PR

主进程


目录结构

主进程相关代码位于 src/main/ 目录下,结构如下:

bash 复制代码
src/main/
├── index.mjs                # 主进程入口,应用初始化、全局事件、窗口/服务创建
├── ApiBase.js               # API 插件开发基类
├── cache.mjs                # 缓存管理
├── logger.mjs               # 日志系统
├── updater.mjs              # 自动更新逻辑
├── jobs/                    # 定时任务相关模块
│   └── CleanOldLogs.mjs     # 日志清理任务
├── store/                   # 数据与业务核心模块(Manager/Store)
│   ├── index.mjs            # Store聚合与IPC注册
│   ├── ApiManager.mjs       # API统一管理
│   ├── DatabaseManager.mjs  # 数据库管理
│   ├── FileManager.mjs      # 文件管理
│   ├── ResourcesManager.mjs # 壁纸资源管理
│   ├── SettingManager.mjs   # 设置管理
│   ├── TaskScheduler.mjs    # 定时任务调度
│   ├── VersionManager.mjs   # 版本管理
│   ├── WallpaperManager.mjs # 壁纸调度与切换
│   └── WordsManager.mjs     # 词库管理
├── windows/                 # 各类窗口管理
│   ├── MainWindow.mjs           # 主窗口
│   ├── DynamicWallpaperWindow.mjs # 动态壁纸窗口
│   ├── RhythmWallpaperWindow.mjs  # 律动壁纸窗口
│   ├── SuspensionBall.mjs        # 悬浮球窗口
│   ├── ViewImageWindow.mjs       # 图片预览窗口
│   └── LoadingWindow.mjs         # 启动/加载窗口
├── utils/                   # 主进程通用工具
│   ├── file.mjs             # 文件相关工具
│   ├── utils.mjs            # 通用工具函数
│   └── dynamicWallpaper.mjs # 动态壁纸相关工具
├── child_server/            # 本地API、文件、Socket服务(子进程)
│   ├── index.mjs
│   ├── ChildServer.mjs
│   ├── file_server/
│   └── h5_server/
│       ├── api/
│       └── socket/

结构说明

  • index.mjs:主进程入口,负责应用生命周期、全局事件、窗口/服务创建、异常处理等。
  • store/ :核心业务与数据管理,所有 Manager 单例聚合于 store/index.mjs,统一对外暴露。
  • windows/:所有 Electron 窗口的创建、管理与事件处理。
  • utils/:主进程常用工具函数、文件操作、动态壁纸处理等。
  • child_server/:本地 API、文件、Socket 服务,提升性能与安全性,支持 H5 端和多端同步。
  • jobs/:定时任务相关模块,如日志清理等。
  • logger.mjs:日志系统,支持多级别日志输出与持久化。
  • updater.mjs:自动更新逻辑,集成 Electron 的更新机制。
  • ApiBase.js:API 插件开发基类,便于扩展第三方壁纸源。

核心功能模块详解

环境变量

主进程在启动时会设置一系列环境变量,这些变量主要用于统一管理应用运行时的各种目录路径和资源位置,便于在不同平台、开发/生产环境下灵活切换。

主要功能:

  • 路径管理:统一管理应用各功能模块的路径配置
  • 环境适配:根据开发和生产环境自动适配配置
  • 平台兼容:支持 Windows、macOS、Linux 多平台路径差异
  • 资源隔离:将用户数据、系统资源、临时文件等分类管理
  • 配置统一:确保主进程和子进程使用相同的路径配置

设置位置src/main/index.mjs(应用启动阶段)

环境变量分类:

系统路径变量:

  • FBW_USER_DATA_PATH:用户数据根目录路径
  • FBW_LOGS_PATH:日志文件目录路径
  • FBW_DATABASE_PATH:数据库目录路径
  • FBW_DATABASE_FILE_PATH:数据库文件完整路径
  • FBW_DOWNLOAD_PATH:下载文件目录路径
  • FBW_CERTS_PATH:证书文件目录路径
  • FBW_PLUGINS_PATH:插件目录路径
  • FBW_TEMP_PATH:临时文件目录路径
  • FBW_RESOURCES_PATH:资源文件目录路径(开发/生产环境自适应)

环境检测变量:

  • NODE_ENV:运行环境标识(development/production)

环境变量初始化:

js:src/main/index.mjs 复制代码
// 获取用户数据路径
const userDataPath = app.getPath('userData')

// 系统目录路径配置
process.env.FBW_USER_DATA_PATH = userDataPath
process.env.FBW_LOGS_PATH = getDirPathByName(userDataPath, 'logs')
process.env.FBW_DATABASE_PATH = getDirPathByName(userDataPath, 'database')
process.env.FBW_DATABASE_FILE_PATH = path.join(process.env.FBW_DATABASE_PATH, 'fbw.db')
process.env.FBW_DOWNLOAD_PATH = getDirPathByName(userDataPath, 'download')
process.env.FBW_CERTS_PATH = getDirPathByName(userDataPath, 'certs')
process.env.FBW_PLUGINS_PATH = getDirPathByName(userDataPath, 'plugins')
process.env.FBW_TEMP_PATH = getDirPathByName(userDataPath, 'temp')

// 资源路径配置(开发/生产环境自适应)
process.env.FBW_RESOURCES_PATH = app.isPackaged
  ? path.join(process.resourcesPath, 'resources') // 生产环境
  : path.join(__dirname, '../../resources') // 开发环境

目录创建工具:

js:src/main/utils/utils.mjs 复制代码
// 获取应用相关目录地址
export const getDirPathByName = (userDataPath = '', dirName = '') => {
  let dirPath = ''
  const sysDirs = ['database', 'logs', 'download', 'temp', 'certs', 'plugins']

  if (sysDirs.includes(dirName)) {
    dirPath = path.join(userDataPath, dirName)
  } else {
    dirPath = path.join(userDataPath, 'temp', dirName)
  }

  // 自动创建目录
  if (dirPath && !fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath, { recursive: true })
  }

  return dirPath
}

环境检测工具:

js:src/main/utils/utils.mjs 复制代码
// 环境检测函数
export const isDev = () => {
  return process.env.NODE_ENV === 'development'
}

export const isProd = () => {
  return process.env.NODE_ENV === 'production'
}

// 操作系统检测
export const osType = OS_TYPES[os.type()] || 'win'
export const isLinux = () => osType === 'linux'
export const isMac = () => osType === 'mac'
export const isWin = () => osType === 'win'

// 函数类型检测
export const isFunc = (func) => {
  return typeof func === 'function'
}

环境变量使用场景:

数据库管理:

js:src/main/store/DatabaseManager.mjs 复制代码
// 数据库文件路径
this.db = new Database(process.env.FBW_DATABASE_FILE_PATH)

日志管理:

js:src/main/logger.mjs 复制代码
// 日志文件路径
const logFilePath = join(process.env.FBW_LOGS_PATH, fileName)

文件下载:

js:src/main/store/FileManager.mjs 复制代码
// 下载文件路径
const downloadFilePath = path.join(process.env.FBW_DOWNLOAD_PATH, 'wallpaper.png')

插件管理:

js:src/main/store/ApiManager.mjs 复制代码
// 插件目录路径
this.sysApiDir = path.join(process.env.FBW_RESOURCES_PATH, 'api')
this.userApiDir = path.join(process.env.FBW_PLUGINS_PATH, 'api')

证书管理:

js:src/main/utils/utils.mjs 复制代码
// 证书路径
const certPath = process.env.FBW_CERTS_PATH

资源访问:

js:src/main/utils/utils.mjs 复制代码
// 资源文件路径
const resourcePath = path.resolve(process.env.FBW_RESOURCES_PATH, './h5')

架构设计与最佳实践:

分层架构:

  • 配置层:环境变量和配置文件管理
  • 工具层:路径处理和目录创建工具
  • 业务层:具体业务逻辑实现

配置管理策略:

  • 环境隔离:开发和生产环境配置分离
  • 路径统一:统一路径管理和自动创建
  • 类型安全:环境变量类型检查和验证
  • 默认值处理:提供合理的默认配置

性能优化策略:

  • 路径缓存:缓存常用路径避免重复解析
  • 懒加载:按需创建目录和文件
  • 批量操作:批量处理路径相关操作

安全机制:

  • 路径验证:验证环境变量路径的合法性
  • 权限控制:限制敏感操作的访问权限
  • 数据隔离:不同模块间数据隔离

监控与调试:

  • 环境日志:记录环境变量和路径变化
  • 调试工具:提供路径查看和验证工具
  • 错误追踪:路径相关错误的追踪和分析

全局变量

主进程通过 global.FBW 对象维护全局状态、窗口实例、工具方法等,方便在各模块间共享和调用。

主要功能:

  • 状态管理:维护应用全局状态和标志位
  • 实例共享:提供窗口实例和服务的全局访问
  • 工具共享:提供全局工具函数和辅助方法
  • 配置隔离:将配置与业务逻辑分离,便于维护
  • 跨模块访问:提供模块间的数据共享和通信机制
  • 插件支持:为插件和扩展提供全局能力访问

设置位置src/main/index.mjs(应用启动阶段)

全局变量结构:

核心全局对象:

  • global.FBW:主全局对象,包含所有全局状态和工具
  • global.logger:全局日志记录器实例

global.FBW 对象结构:

js:src/main/index.mjs 复制代码
global.FBW = {
  // API 开发辅助工具
  apiHelpers: {
    axios, // HTTP 请求库
    ApiBase, // API 基类
    calculateImageOrientation, // 图片方向计算
    calculateImageQuality // 图片质量计算
  },

  // 图标路径
  iconLogo: getIconPath('icon_512x512.png'), // 应用主图标
  iconTray: getIconPath('icon_32x32.png'), // 托盘图标

  // 全局状态标志
  flags: {
    isQuitting: false // 应用退出标志
  },

  // 窗口实例
  loadingWindow: LoadingWindow.getInstance(),
  mainWindow: MainWindow.getInstance(),
  viewImageWindow: ViewImageWindow.getInstance(),
  suspensionBall: SuspensionBall.getInstance(),
  dynamicWallpaperWindow: DynamicWallpaperWindow.getInstance(),
  rhythmWallpaperWindow: RhythmWallpaperWindow.getInstance(),

  // 数据管理器
  store: Store实例,

  // 全局工具函数
  sendCommonData: Function, // 发送通用数据
  sendMsg: Function // 发送消息
}

全局变量初始化:

js:src/main/index.mjs 复制代码
// 全局变量初始化
global.FBW = global.FBW || {}

// API 开发辅助工具
global.FBW.apiHelpers = {
  axios,
  ApiBase,
  calculateImageOrientation,
  calculateImageQuality
}

// 图标路径配置
global.FBW.iconLogo = getIconPath('icon_512x512.png')
global.FBW.iconTray = getIconPath('icon_32x32.png')

// 全局状态标志
global.FBW.flags = {
  isQuitting: false
}

// 窗口实例初始化
global.FBW.loadingWindow = LoadingWindow.getInstance()
global.FBW.mainWindow = MainWindow.getInstance()
global.FBW.viewImageWindow = ViewImageWindow.getInstance()
global.FBW.suspensionBall = SuspensionBall.getInstance()
global.FBW.dynamicWallpaperWindow = DynamicWallpaperWindow.getInstance()
global.FBW.rhythmWallpaperWindow = RhythmWallpaperWindow.getInstance()

全局工具函数:

js:src/main/index.mjs 复制代码
// 发送通用数据到渲染进程
global.FBW.sendCommonData = (win) => {
  if (!win) {
    return
  }
  const data = {
    osType,
    isLinux: isLinux(),
    isMac: isMac(),
    isWin: isWin(),
    isDev: isDev(),
    isProd: isProd(),
    h5ServerUrl: global.FBW.store?.h5ServerUrl
  }
  win.webContents.send('main:commonData', data)
}

// 发送消息到渲染进程
global.FBW.sendMsg = (win, msgOption) => {
  if (!win) {
    return
  }
  win.webContents.send('main:sendMsg', msgOption)
}

应用信息配置:

js:src/common/config.js 复制代码
export const appInfo = {
  appName: 'Flying Bird Wallpaper',
  version,
  author: 'OXOYO',
  homepage: 'https://github.com/OXOYO/Flying-Bird-Wallpaper',
  github: 'https://github.com/OXOYO/Flying-Bird-Wallpaper',
  email: 'zmn2007.hi@163.com'
}

图标路径管理:

js:src/main/utils/utils.mjs 复制代码
// 获取图标路径
export const getIconPath = (iconName) => {
  return path.resolve(path.join(process.env.FBW_RESOURCES_PATH, 'icons', iconName))
}

// 图标使用示例
const trayIcon = nativeImage.createFromPath(global.FBW.iconTray).resize({ width: 20, height: 20 })

const windowIcon = global.FBW.iconLogo

全局状态管理:

项目中的全局状态通过 global.FBW.flags 对象进行管理,包含应用退出标志等状态信息:

js:src/main/index.mjs 复制代码
// 全局状态标志
global.FBW.flags = {
  isQuitting: false // 应用退出标志
}

全局变量使用场景:

窗口管理:

js:src/main/index.mjs 复制代码
// 窗口实例访问
const mainWindow = global.FBW.mainWindow
const suspensionBall = global.FBW.suspensionBall

// 窗口操作
global.FBW.mainWindow.toggle()
global.FBW.suspensionBall.createOrOpen()

数据管理:

js:src/main/index.mjs 复制代码
// 数据管理器访问
const store = global.FBW.store
const wallpaperManager = global.FBW.store?.wallpaperManager

// 数据操作
await global.FBW.store?.toggleAutoSwitchWallpaper()
await global.FBW.store?.doManualSwitchWallpaper('next')

消息通信:

js:src/main/index.mjs 复制代码
// 发送消息到渲染进程
global.FBW.sendMsg(global.FBW.mainWindow.win, {
  type: 'main:wallpaperUpdate',
  data: { wallpaper: newWallpaper }
})

// 发送通用数据
global.FBW.sendCommonData(global.FBW.mainWindow.win)

API 开发:

js:src/main/index.mjs 复制代码
// API 辅助工具访问
const { axios, ApiBase } = global.FBW.apiHelpers

// 图片处理工具
const { calculateImageOrientation, calculateImageQuality } = global.FBW.apiHelpers

图标资源:

js:src/main/index.mjs 复制代码
// 图标路径访问
const logoIcon = global.FBW.iconLogo
const trayIcon = global.FBW.iconTray

// 创建托盘图标
const tray = new Tray(nativeImage.createFromPath(global.FBW.iconTray))

架构设计与最佳实践:

分层架构:

  • 全局层:全局状态和实例管理
  • 工具层:通用工具函数和辅助方法
  • 业务层:具体业务逻辑实现

全局状态管理:

  • 单例模式:全局对象使用单例模式
  • 状态同步:多窗口间状态同步机制
  • 内存管理:合理的内存使用和清理
  • 错误处理:全局错误捕获和处理

性能优化策略:

  • 懒加载:按需初始化全局对象
  • 缓存机制:合理使用缓存减少重复计算
  • 内存优化:及时清理不需要的全局引用
  • 引用优化:避免循环引用和内存泄漏

安全机制:

  • 访问控制:限制敏感操作的访问权限
  • 数据隔离:不同模块间数据隔离
  • 错误边界:防止全局错误影响整个应用
  • 类型检查:全局变量的类型安全检查

监控与调试:

  • 状态监控:监控全局状态变化
  • 性能监控:监控全局对象的内存使用
  • 调试工具:提供全局状态查看工具
  • 错误追踪:全局错误的追踪和分析

最佳实践:

全局变量使用:

  • 最小化全局状态
  • 使用命名空间避免冲突
  • 提供清晰的访问接口
  • 及时清理无用引用

状态管理:

  • 集中管理全局状态
  • 提供状态变更通知
  • 支持状态回滚和恢复
  • 记录状态变更历史

工具函数:

  • 提供统一的工具接口
  • 支持异步操作
  • 包含错误处理机制
  • 提供性能优化选项

总结

  • 环境变量主要用于路径、资源、配置的全局统一,保证多平台和多环境下的兼容性和灵活性。
  • 全局变量(global.FBW)则是主进程的"总线",集中管理窗口、服务、工具、状态等,极大提升了主进程的可维护性和扩展性。

如需了解更多细节,可查阅 src/main/index.mjs 相关代码。


应用管理

应用管理是主进程的入口,负责整个应用的初始化、生命周期管理和全局配置。

主要功能:

  • 应用初始化 :在 app.whenReady() 中完成所有初始化工作,包括:
    • 环境变量设置:配置用户数据路径、日志路径、数据库路径等
    • 全局变量初始化 :设置 global.FBW 对象和全局状态
    • 日志系统初始化:启动 Pino 日志系统
    • 异常处理设置:配置未处理异常和 Promise 拒绝的捕获
    • 窗口实例创建:创建所有窗口的单例实例
    • 开发环境配置:安装 Vue DevTools、启用调试器
    • 更新器初始化:设置自动更新检查和事件监听
    • 应用标识设置:设置应用用户模型ID
    • 菜单和任务栏配置:清空默认菜单、配置任务栏
    • 快捷键注册:注册全局快捷键(Mac平台)
    • IPC处理器注册:注册所有主进程与渲染进程的通信处理器
    • 自定义协议处理 :注册 fbwtp:// 协议处理器
    • Store初始化:初始化数据管理和业务逻辑层
    • 子进程服务启动:启动文件服务和H5服务
    • 定时任务启动:启动所有定时任务
    • 电源监控设置:启动系统电源状态监控
    • 托盘创建:创建系统托盘图标和菜单
    • 窗口显示:根据启动参数和设置决定显示哪些窗口
    • 动态壁纸恢复:恢复上次的动态壁纸设置
  • 单实例控制 :使用 app.requestSingleInstanceLock() 确保应用只运行一个实例
  • 全局变量设置 :初始化 global.FBW 对象,包含所有全局状态和工具
  • 环境变量配置:设置各种路径和配置的环境变量
  • 异常处理:捕获未处理的异常和 Promise 拒绝
  • 电源管理 :通过 powerMonitor 监听系统电源状态,智能管理应用行为
    • 系统挂起/恢复:监听系统休眠和唤醒事件,自动暂停/恢复壁纸切换
    • 锁屏/解锁:监听屏幕锁定状态,在锁屏时暂停壁纸切换
    • 电池模式:监听电源状态变化,在电池模式下启用省电策略
    • 系统空闲:监控系统空闲状态,在用户不活跃时暂停任务
    • 智能恢复:根据电源状态智能恢复之前的任务状态

关键代码:

js:src/main/index.mjs 复制代码
// 确保单实例
const gotTheLock = app.requestSingleInstanceLock()

if (!gotTheLock) {
  app.quit()
} else {
  app.on('second-instance', () => {
    global.FBW.mainWindow.reopen()
  })
}

// 环境变量设置
const userDataPath = app.getPath('userData')
process.env.FBW_USER_DATA_PATH = userDataPath
process.env.FBW_LOGS_PATH = getDirPathByName(userDataPath, 'logs')
process.env.FBW_DATABASE_PATH = getDirPathByName(userDataPath, 'database')
process.env.FBW_DATABASE_FILE_PATH = path.join(process.env.FBW_DATABASE_PATH, 'fbw.db')
process.env.FBW_DOWNLOAD_PATH = getDirPathByName(userDataPath, 'download')
process.env.FBW_CERTS_PATH = getDirPathByName(userDataPath, 'certs')
process.env.FBW_PLUGINS_PATH = getDirPathByName(userDataPath, 'plugins')
process.env.FBW_TEMP_PATH = getDirPathByName(userDataPath, 'temp')
process.env.FBW_RESOURCES_PATH = app.isPackaged
  ? path.join(process.resourcesPath, 'resources')
  : path.join(__dirname, '../../resources')

// 全局变量初始化
global.FBW = global.FBW || {}
global.FBW.apiHelpers = { axios, ApiBase, calculateImageOrientation, calculateImageQuality }
global.FBW.iconLogo = getIconPath('icon_512x512.png')
global.FBW.iconTray = getIconPath('icon_32x32.png')
global.FBW.flags = { isQuitting: false }

// 日志系统初始化
logger()
global.logger.info(`isDev: ${isDev()} process.env.NODE_ENV: ${process.env.NODE_ENV}`)

// 窗口实例创建
global.FBW.loadingWindow = LoadingWindow.getInstance()
global.FBW.mainWindow = MainWindow.getInstance()
global.FBW.viewImageWindow = ViewImageWindow.getInstance()
global.FBW.suspensionBall = SuspensionBall.getInstance()
global.FBW.dynamicWallpaperWindow = DynamicWallpaperWindow.getInstance()
global.FBW.rhythmWallpaperWindow = RhythmWallpaperWindow.getInstance()

// 开发环境配置
if (isDev()) {
  installExtension(VUEJS_DEVTOOLS_BETA)
    .then(({ name }) => global.logger.info(`Added Extension: ${name}`))
    .catch((err) => global.logger.error(`An error occurred: ${err}`))
}

// 更新器初始化
updater = new Updater()
updater.on('update-available', (info) => {
  const notice = new Notification({
    title: t('actions.checkUpdate'),
    body: t('messages.updateAvailable', { version: `v${info.version}` })
  })
  notice.show()
})

// 应用标识设置
electronApp.setAppUserModelId('co.oxoyo.flying-bird-wallpaper')

// 菜单和任务栏配置
const menu = Menu.buildFromTemplate([])
Menu.setApplicationMenu(menu)
if (isWin()) {
  app.setUserTasks([])
}

// 快捷键注册(Mac平台)
if (isMac()) {
  localShortcut.register('CommandOrControl+A', () => {
    global.FBW.mainWindow.win.webContents.selectAll()
  })
  localShortcut.register('CommandOrControl+C', () => {
    global.FBW.mainWindow.win.webContents.copy()
  })
  localShortcut.register('CommandOrControl+V', () => {
    global.FBW.mainWindow.win.webContents.paste()
  })
}

// IPC处理器注册
ipcMain.handle('main:selectFolder', async () => {
  return await dialog.showOpenDialog({ properties: ['openDirectory'] })
})
ipcMain.handle('main:clearCache', () => {
  cache.clear()
  global.FBW.sendMsg(global.FBW.mainWindow.win, {
    type: 'success',
    message: t('messages.clearCacheSuccess')
  })
})

// Store初始化
global.FBW.store = new Store()
await global.FBW.store?.waitForInitialization()

// Store内部初始化过程:
// 1. 数据库管理器初始化
// 2. 版本管理器初始化
// 3. 设置管理器初始化
// 4. API管理器初始化
// 5. 文件服务子进程创建
// 6. H5服务子进程创建
// 7. 任务调度器初始化
// 8. 词库管理器初始化
// 9. 文件管理器初始化
// 10. 资源管理器初始化
// 11. 壁纸管理器初始化
// 12. IPC通信处理
// 13. 文件服务子进程启动
// 14. H5服务启动(如果启用)
// 15. 定时任务启动
// 16. 开机自启动设置处理
// 17. 电源监控设置

// 动态壁纸恢复
if (
  global.FBW.store?.settingData?.dynamicAutoPlayOnStartup &&
  global.FBW.store?.settingData?.dynamicLastVideoPath
) {
  global.FBW.dynamicWallpaperWindow?.setDynamicWallpaper(
    global.FBW.store?.settingData?.dynamicLastVideoPath
  )
}

// 托盘创建
createTray()

// 窗口显示逻辑
if (process.argv.includes('--autoStart')) {
  if (global.FBW.store?.settingData?.openMainWindowOnStartup) {
    global.FBW.loadingWindow.create(global.FBW.mainWindow.create)
  } else {
    global.FBW.mainWindow.create()
  }
} else {
  global.FBW.mainWindow.create()
}

// 悬浮球显示
if (global.FBW.store?.settingData?.suspensionBallVisible) {
  global.FBW.suspensionBall.createOrOpen()
}

// 电源监控设置(在 Store 类中)
setupPowerMonitor() {
  // 监听系统挂起事件
  powerMonitor.on('suspend', () => {
    global.logger.info('系统挂起,暂停自动切换壁纸')
    this.handleSystemIdle(true)
  })

  // 监听系统恢复事件
  powerMonitor.on('resume', () => {
    global.logger.info('系统恢复,恢复自动切换壁纸状态')
    this.handleSystemIdle(false)
  })

  // 监听锁屏事件
  powerMonitor.on('lock-screen', () => {
    global.logger.info('系统锁屏,暂停自动切换壁纸')
    this.handleSystemIdle(true)
  })

  // 监听解锁事件
  powerMonitor.on('unlock-screen', () => {
    global.logger.info('系统解锁,恢复自动切换壁纸状态')
    this.handleSystemIdle(false)
  })

  // 监听系统空闲状态
  if (powerMonitor.getSystemIdleState) {
    // 每分钟检查一次系统空闲状态
    setInterval(() => {
      // 系统空闲阈值,单位为秒,默认5分钟
      const idleState = powerMonitor.getSystemIdleState(300)
      if (idleState === 'idle' && !this.powerState.isSystemIdle) {
        global.logger.info('系统空闲,暂停自动切换壁纸')
        this.handleSystemIdle(true)
      } else if (idleState === 'active' && this.powerState.isSystemIdle) {
        global.logger.info('系统活跃,恢复自动切换壁纸状态')
        this.handleSystemIdle(false)
      }
    }, 60000)
  }

  // 监听电池模式
  powerMonitor.on('on-battery', () => {
    global.logger.info('进入电池模式')
    this.powerState.isOnBattery = true
    if (this.settingData.powerSaveMode) {
      global.logger.info('省电模式下自动暂停所有定时任务')
      this.taskScheduler.clearAllTasks()
      this.powerState.wasPausedByBattery = true
    }
  })

  powerMonitor.on('on-ac', () => {
    global.logger.info('恢复交流电')
    if (this.powerState.isOnBattery && this.powerState.wasPausedByBattery) {
      global.logger.info('恢复所有定时任务')
      this.startScheduledTasks()
      this.powerState.wasPausedByBattery = false
    }
    this.powerState.isOnBattery = false
  })
}

窗口管理

窗口管理负责创建、管理和控制应用的所有窗口,包括主窗口、功能窗口和系统托盘。所有窗口都采用单例模式设计,确保全局唯一性。

窗口类型及功能:

  • MainWindow:主窗口,应用的核心界面

    • 尺寸:1020x700(最小尺寸)
    • 特性:无边框、自定义标题栏、支持最小化隐藏
    • 功能:壁纸浏览、设置管理、搜索、收藏等核心功能
    • 行为:关闭时隐藏而非退出,支持托盘恢复
  • LoadingWindow:启动/加载窗口

    • 尺寸:200x200(固定尺寸)
    • 特性:透明背景、无边框、不可调整大小
    • 功能:应用启动时显示加载动画
    • 行为:10秒超时自动关闭,加载完成后自动销毁
  • ViewImageWindow:图片预览窗口

    • 尺寸:1200x800(最小1020x700)
    • 特性:无边框、自定义标题栏
    • 功能:大图预览、图片信息查看、下载管理
    • 行为:支持多实例,每个图片独立窗口
  • SuspensionBall:悬浮球窗口

    • 尺寸:60x220(固定尺寸)
    • 特性:透明背景、无边框、始终置顶、不可调整大小
    • 功能:快速操作入口、壁纸切换、设置快捷方式
    • 行为:跨工作区可见、不显示在任务栏、支持拖拽定位
  • DynamicWallpaperWindow:动态壁纸窗口

    • 尺寸:全屏(根据显示器尺寸)
    • 特性:透明背景、无边框、桌面级别、点击穿透
    • 功能:视频壁纸播放、性能模式切换、透明度调节
    • 行为:全屏覆盖、支持多显示器、后台播放
  • RhythmWallpaperWindow:律动壁纸窗口

    • 尺寸:全屏(根据显示器尺寸)
    • 特性:透明背景、无边框、桌面级别、点击穿透
    • 功能:音频可视化效果、多种律动模式、实时音频分析
    • 行为:全屏覆盖、音频驱动、视觉效果同步

主要功能:

  • 窗口创建与销毁:统一管理所有窗口的生命周期,采用单例模式确保全局唯一性
  • 窗口位置管理:保存和恢复窗口位置,支持多显示器环境
  • 窗口状态控制:最小化、最大化、恢复、关闭等操作,支持自定义关闭行为
  • 窗口层级管理:桌面级窗口、置顶窗口、普通窗口的层级控制
  • 窗口交互控制:点击穿透、鼠标事件处理、拖拽支持
  • 系统托盘:创建托盘图标和右键菜单,支持快速操作
  • 快捷键注册:支持全局快捷键操作,平台特定优化
  • 窗口通信:窗口间数据传递和状态同步
  • 窗口恢复:应用重启后恢复窗口状态和位置

关键代码:

js:src/main/index.mjs 复制代码
// 窗口实例管理(单例模式)
global.FBW.loadingWindow = LoadingWindow.getInstance()
global.FBW.mainWindow = MainWindow.getInstance()
global.FBW.viewImageWindow = ViewImageWindow.getInstance()
global.FBW.suspensionBall = SuspensionBall.getInstance()
global.FBW.dynamicWallpaperWindow = DynamicWallpaperWindow.getInstance()
global.FBW.rhythmWallpaperWindow = RhythmWallpaperWindow.getInstance()

// 主窗口配置
const mainWindowOptions = {
  width: 1020,
  height: 700,
  minWidth: 1020,
  minHeight: 700,
  show: false,
  frame: false,
  backgroundColor: '#efefef',
  autoHideMenuBar: true,
  titleBarStyle: 'hidden',
  icon: global.FBW.iconLogo,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.mjs'),
    sandbox: false,
    webSecurity: false,
    contextIsolation: true,
    nodeIntegration: false,
    allowRunningInsecureContent: true,
    devTools: true
  }
}

// 悬浮球配置
const suspensionBallOptions = {
  width: 60,
  height: 220,
  minWidth: 60,
  minHeight: 220,
  maxWidth: 60,
  maxHeight: 220,
  frame: false,
  resizable: false,
  show: false,
  transparent: true,
  backgroundColor: '#00000000',
  titleBarStyle: 'hidden',
  hasShadow: false,
  alwaysOnTop: true,
  acceptFirstMouse: true,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.mjs'),
    sandbox: false,
    webSecurity: false,
    devTools: true,
    enableDragAndDrop: false
  }
}

// 动态壁纸窗口配置
const dynamicWallpaperOptions = {
  frame: false,
  show: false,
  transparent: true,
  skipTaskbar: true,
  type: isMac() ? 'desktop' : '',
  autoHideMenuBar: true,
  enableLargerThanScreen: true,
  hasShadow: false,
  webPreferences: {
    preload: path.join(__dirname, '../preload/index.mjs'),
    sandbox: false,
    webSecurity: false,
    contextIsolation: true,
    nodeIntegration: false,
    allowRunningInsecureContent: true,
    devTools: true
  }
}

// 窗口位置管理
const getWindowPosition = (name) => {
  const window = global.FBW[name]
  const win = window?.win
  if (win) {
    const [x, y] = win.getPosition()
    return { x, y }
  }
  return null
}

const setWindowPosition = (name, position) => {
  const window = global.FBW[name]
  const win = window?.win
  if (win) {
    win.setPosition(parseInt(position.x), parseInt(position.y), false)
    if (name === 'suspensionBall') {
      win.setSize(60, 220, false)
    }
  }
}

// 窗口状态控制
const resizeWindow = (name, action) => {
  const window = global.FBW[name]
  const win = window?.win
  if (win) {
    switch (action) {
      case 'minimize':
        win.minimize()
        break
      case 'maximize':
        if (win.isMaximized()) {
          win.unmaximize()
        } else {
          win.maximize()
        }
        break
      case 'unmaximize':
        win.unmaximize()
        break
      case 'restore':
        win.restore()
        break
      case 'close':
        win.close()
        break
    }
  }
}

// 托盘创建
const createTray = () => {
  const trayIcon = nativeImage.createFromPath(global.FBW.iconTray).resize({ width: 20, height: 20 })
  trayIcon.setTemplateImage(true)

  tray = new Tray(trayIcon)
  tray.setToolTip(t('appInfo.name'))

  // 托盘点击事件
  tray.on('click', () => {
    global.FBW.mainWindow.toggle()
  })

  // 托盘右键菜单
  tray.on('right-click', () => {
    const contextMenuList = [
      {
        label: global.FBW.store?.settingData?.autoSwitchWallpaper
          ? t('actions.autoSwitchWallpaper.stop')
          : t('actions.autoSwitchWallpaper.start'),
        click: () => {
          global.FBW.store?.toggleAutoSwitchWallpaper()
        }
      },
      {
        label: t('actions.nextWallpaper'),
        click: () => {
          global.FBW.store?.doManualSwitchWallpaper('next')
        }
      },
      {
        label: t('actions.prevWallpaper'),
        click: () => {
          global.FBW.store?.doManualSwitchWallpaper('prev')
        }
      },
      {
        label: global.FBW.store?.settingData?.suspensionBallVisible
          ? t('actions.closeSuspensionBall')
          : t('actions.openSuspensionBall'),
        click: () => {
          global.FBW.suspensionBall.toggle()
        }
      }
    ]
  })
}

// 窗口通信
global.FBW.sendCommonData = (win) => {
  if (!win) return
  const data = {
    osType,
    isLinux: isLinux(),
    isMac: isMac(),
    isWin: isWin(),
    isDev: isDev(),
    isProd: isProd(),
    h5ServerUrl: global.FBW.store?.h5ServerUrl
  }
  win.webContents.send('main:commonData', data)
}

global.FBW.sendMsg = (win, msgOption) => {
  if (!win) return
  win.webContents.send('main:sendMsg', msgOption)
}

// 窗口特殊功能
// 1. 主窗口关闭行为:隐藏而非退出
this.win.on('close', (event) => {
  if (!global.FBW.flags.isQuitting) {
    event.preventDefault()
    this.win?.hide()
    if (isMac() && app.dock) {
      app.dock.hide()
    }
  }
})

// 2. 悬浮球跨工作区显示
this.win.setVisibleOnAllWorkspaces(true)
this.win.setSkipTaskbar(true)

// 3. 动态壁纸窗口点击穿透
if (isWin()) {
  this.win.setIgnoreMouseEvents(true, { forward: true })
}

// 4. 窗口位置恢复
const savedPosition = global.FBW.store?.settingData?.windowPositions?.[name]
if (savedPosition) {
  this.win.setPosition(savedPosition.x, savedPosition.y, false)
}

设计模式与最佳实践:

  • 单例模式:所有窗口类都采用单例模式,确保全局唯一性,避免重复创建
  • 事件驱动:通过 IPC 事件实现窗口间通信,解耦窗口逻辑
  • 状态管理:窗口状态通过 Store 统一管理,支持持久化和恢复
  • 平台适配:针对不同操作系统(Windows、macOS、Linux)提供特定优化
  • 性能优化:桌面级窗口使用点击穿透,减少不必要的鼠标事件处理
  • 用户体验:主窗口关闭时隐藏而非退出,保持应用后台运行
  • 资源管理:及时清理窗口资源,避免内存泄漏

窗口层级说明:

  1. 桌面级窗口:DynamicWallpaperWindow、RhythmWallpaperWindow

    • 位于桌面背景层,不干扰用户操作
    • 支持点击穿透,鼠标事件传递给下层窗口
    • 全屏覆盖,提供沉浸式体验
  2. 置顶窗口:SuspensionBall

    • 始终显示在最顶层
    • 跨工作区可见,方便快速访问
    • 不显示在任务栏,保持界面简洁
  3. 普通窗口:MainWindow、ViewImageWindow、LoadingWindow

    • 遵循系统窗口管理规则
    • 支持最小化、最大化、关闭等标准操作
    • 与系统任务栏集成

数据管理

数据管理通过 Store 类聚合所有 Manager,统一管理应用的数据、服务和业务逻辑。采用分层架构设计,每个 Manager 负责特定的业务领域,通过依赖注入实现组件间的解耦。

Manager 组件详解:

  • DatabaseManager:数据库管理

    • 负责 SQLite 数据库的初始化和操作
    • 支持 WAL 模式,提供高性能并发访问
    • 自动创建表结构和索引
    • 提供统一的 CRUD 操作接口
    • 支持事务处理和错误恢复
  • SettingManager:设置管理

    • 处理用户配置的存储和读取
    • 支持配置热更新和事件通知
    • 提供配置验证和默认值管理
    • 支持多语言配置和国际化
    • 实现配置的持久化和恢复
  • ApiManager:API 管理

    • 统一管理所有壁纸源的 API 调用
    • 支持内置 API 和用户自定义 API 插件
    • 提供 API 插件的动态加载和热更新
    • 实现 API 调用的错误处理和重试机制
    • 支持 API 限流和缓存策略
  • ResourcesManager:资源管理

    • 处理壁纸资源的获取和缓存
    • 支持多种资源源的统一管理
    • 实现资源的智能调度和负载均衡
    • 提供资源质量评估和筛选
    • 支持资源的批量下载和管理
  • WallpaperManager:壁纸管理

    • 负责壁纸的切换和调度
    • 支持自动切换和手动切换模式
    • 实现壁纸切换的智能算法
    • 提供壁纸历史记录和收藏功能
    • 支持多显示器壁纸同步
  • FileManager:文件管理

    • 处理文件下载、存储和清理
    • 支持断点续传和并发下载
    • 实现文件去重和版本管理
    • 提供文件压缩和格式转换
    • 支持自动清理和存储优化
  • WordsManager:词库管理

    • 处理搜索关键词的管理
    • 支持关键词的智能推荐
    • 实现搜索历史和学习算法
    • 提供多语言关键词支持
    • 支持关键词的导入导出
  • TaskScheduler:任务调度

    • 管理定时任务和后台作业
    • 支持任务的动态调度和取消
    • 实现任务优先级和依赖管理
    • 提供任务执行状态监控
    • 支持任务失败重试和恢复
  • VersionManager:版本管理

    • 处理应用版本升级和数据迁移
    • 支持增量更新和回滚机制
    • 实现版本兼容性检查
    • 提供升级进度和状态通知
    • 支持多版本数据迁移策略

主要功能:

  • 数据持久化:通过 SQLite 数据库存储用户数据,支持 WAL 模式和高性能并发访问
  • 配置管理:统一管理应用设置和用户偏好,支持热更新和事件通知
  • 资源调度:智能调度壁纸资源的获取和切换,支持多源负载均衡
  • 任务调度:管理定时任务和后台作业,支持动态调度和状态监控
  • 状态同步:确保多窗口间的数据一致性,支持实时状态更新
  • 插件管理:支持 API 插件的动态加载和热更新
  • 版本控制:处理应用版本升级和数据迁移,支持增量更新
  • 缓存策略:实现多级缓存机制,提升应用性能
  • 错误处理:提供统一的错误处理和恢复机制
  • 性能优化:通过索引优化和查询优化提升数据库性能

关键代码:

js 复制代码
// Store 初始化
global.FBW.store = new Store()
await global.FBW.store?.waitForInitialization()

// Store 内部结构(依赖注入)
this.dbManager = DatabaseManager.getInstance(global.logger)
this.settingManager = SettingManager.getInstance(global.logger, this.dbManager)
this.apiManager = ApiManager.getInstance(global.logger, this.dbManager)
this.taskScheduler = TaskScheduler.getInstance(global.logger)
this.wordsManager = WordsManager.getInstance(global.logger, this.dbManager, this.settingManager)
this.fileManager = FileManager.getInstance(global.logger, this.dbManager, this.settingManager, this.fileServer, this.wordsManager)
this.resourcesManager = ResourcesManager.getInstance(global.logger, this.dbManager, this.settingManager, this.apiManager)
this.wallpaperManager = WallpaperManager.getInstance(global.logger, this.dbManager, this.settingManager, this.fileManager, this.apiManager)

// 数据库管理器配置
const dbOptions = {
  journal_mode: 'WAL',
  busy_timeout: 5000,
  pragma: {
    journal_mode: 'WAL',
    busy_timeout: 5000,
    synchronous: 'NORMAL',
    cache_size: -64000, // 64MB
    temp_store: 'MEMORY'
  }
}

// 设置管理器事件监听
this.settingManager.on('SETTING_UPDATED', (newSettings) => {
  this.logger.info('设置已更新,通知相关组件')
  this.notifySettingChange(newSettings)
})

// API 管理器插件加载
async loadApi() {
  const apiMap = {}

  // 加载内置 API
  await this.loadApiFromDir(this.sysApiDir, apiMap)

  // 加载用户 API
  await this.loadApiFromDir(this.userApiDir, apiMap)

  this.apiMap = apiMap
  this.logger.info(`成功加载 ${Object.keys(apiMap).length} 个API插件`)
}

// 壁纸管理器切换逻辑
async switchWallpaper(direction = 'next') {
  const currentWallpaper = await this.getCurrentWallpaper()
  const nextWallpaper = await this.getNextWallpaper(currentWallpaper, direction)

  if (nextWallpaper) {
    await this.setWallpaper(nextWallpaper)
    await this.updateWallpaperHistory(nextWallpaper)
    this.emit('WALLPAPER_CHANGED', nextWallpaper)
  }
}

// 任务调度器
scheduleTask(timerKey, interval, callback, initialDelay = 0) {
  if (this.timers[timerKey]) {
    clearInterval(this.timers[timerKey])
  }

  if (initialDelay > 0) {
    setTimeout(() => {
      callback()
      this.timers[timerKey] = setInterval(callback, interval)
    }, initialDelay)
  } else {
    this.timers[timerKey] = setInterval(callback, interval)
  }
}

// 文件管理器下载
async downloadFile(url, filePath, options = {}) {
  const { onProgress, onComplete, onError } = options

  try {
    const response = await axios({
      method: 'GET',
      url: url,
      responseType: 'stream',
      onDownloadProgress: (progressEvent) => {
        if (onProgress) {
          const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total)
          onProgress(percent)
        }
      }
    })

    const writer = fs.createWriteStream(filePath)
    response.data.pipe(writer)

    return new Promise((resolve, reject) => {
      writer.on('finish', () => {
        if (onComplete) onComplete(filePath)
        resolve(filePath)
      })
      writer.on('error', reject)
    })
  } catch (error) {
    if (onError) onError(error)
    throw error
  }
}

// 缓存管理
class CacheManager {
  constructor() {
    this.memoryCache = new Map()
    this.diskCache = new Map()
  }

  async get(key) {
    // 先查内存缓存
    if (this.memoryCache.has(key)) {
      return this.memoryCache.get(key)
    }

    // 再查磁盘缓存
    if (this.diskCache.has(key)) {
      const value = await this.diskCache.get(key)
      this.memoryCache.set(key, value)
      return value
    }

    return null
  }

  async set(key, value, ttl = 3600000) {
    this.memoryCache.set(key, value)
    this.diskCache.set(key, value)

    setTimeout(() => {
      this.memoryCache.delete(key)
      this.diskCache.delete(key)
    }, ttl)
  }
  }

架构设计与最佳实践:

  • 分层架构:采用分层设计,每个 Manager 负责特定业务领域,通过依赖注入实现解耦
  • 单例模式:所有 Manager 都采用单例模式,确保全局唯一性和资源复用
  • 事件驱动:通过事件机制实现组件间通信,支持松耦合的架构设计
  • 异步处理:所有数据操作都采用异步处理,避免阻塞主线程
  • 错误处理:统一的错误处理机制,支持错误恢复和降级处理
  • 性能优化:通过缓存、索引、连接池等技术提升性能
  • 数据一致性:通过事务处理和状态管理确保数据一致性
  • 可扩展性:插件化架构支持功能扩展和定制化需求

数据流设计:

  1. 数据输入层:API 调用、用户操作、系统事件
  2. 业务逻辑层:各 Manager 处理具体业务逻辑
  3. 数据存储层:SQLite 数据库、文件系统、缓存
  4. 数据输出层:UI 更新、事件通知、外部接口

性能优化策略:

  • 数据库优化:WAL 模式、索引优化、查询优化
  • 缓存策略:多级缓存、LRU 算法、TTL 机制
  • 并发控制:连接池、锁机制、事务管理
  • 资源管理:内存管理、文件清理、连接复用

子进程服务管理

子进程服务管理通过 ChildServer 类创建和管理独立的子进程,提供文件服务和 H5 服务。采用 Electron 的 utilityProcess 实现进程隔离,通过 MessageChannel 进行进程间通信。

服务类型详解:

  • FileServer:文件服务子进程

    • 处理文件扫描、目录刷新、文件信息提取
    • 支持批量文件处理和进度报告
    • 实现文件去重和增量更新
    • 提供文件元数据计算和缓存
    • 支持大文件目录的高效处理
  • H5Server:H5 服务子进程

    • 提供 Web API 和 Socket 服务
    • 支持 HTTPS/HTTP 双模式运行
    • 实现实时通信和数据同步
    • 提供静态资源服务和文件下载
    • 支持多客户端连接和状态管理

主要功能:

  • 进程隔离:将文件处理和 Web 服务分离到独立进程,避免主进程阻塞
  • 性能优化:通过多进程架构提升应用性能,支持并行处理
  • 服务管理:统一管理子进程的创建、通信和销毁,支持热重启
  • 错误隔离:子进程崩溃不影响主进程,提供自动恢复机制
  • 通信机制:通过 MessageChannel 实现高效的进程间通信
  • 资源管理:独立管理子进程资源,支持内存和 CPU 优化
  • 监控调试:提供子进程状态监控和日志收集
  • 安全隔离:子进程运行在受限环境中,提升应用安全性

关键代码:

js 复制代码
// 子进程服务创建
this.fileServer = createFileServer()
this.h5Server = createH5Server()

// 子进程创建
export const createFileServer = () => new ChildServer('fileServer', fileServerPath)
export const createH5Server = () => new ChildServer('h5Server', h5ServerPath)

// ChildServer 核心实现
export default class ChildServer {
  #serverName
  #serverPath
  #child
  #port2

  constructor(serverName, serverPath) {
    this.#serverName = serverName
    this.#serverPath = serverPath
    this.#child = null
    this.#port2 = null
  }

  start({ options, onMessage = () => {} } = {}) {
    const { port1, port2 } = new MessageChannelMain()
    this.#child = utilityProcess.fork(this.#serverPath, options)
    this.#port2 = port2

    this.#child.on('exit', () => {
      this.#child = null
      this.#port2 = null
    })

    this.#port2.on('message', onMessage)
    this.#port2.start()

    // 初始消息
    this.#child.postMessage(
      {
        serverName: this.#serverName,
        event: 'SERVER_FORKED'
      },
      [port1]
    )

    // 服务启动消息
    this.postMessage({
      serverName: this.#serverName,
      event: 'SERVER_START'
    })
  }

  stop(callback) {
    if (!this.#child) return
    const isSuccess = this.#child?.kill()
    typeof callback === 'function' && callback(isSuccess)
  }

  restart({ params, onMessage = () => {}, stopCallback = () => {} } = {}) {
    this.stop(stopCallback)
    this.start({ params, onMessage })
  }

  postMessage(data) {
    this.#port2?.postMessage(data)
  }
}

// 文件服务子进程实现
process.parentPort.on('message', (e) => {
  const [port] = e.ports

  port.on('message', async (e) => {
    const { data } = e

    if (data.event === 'REFRESH_DIRECTORY') {
      // 分批处理大量文件
      const processBatch = async (files, batchSize = 1000) => {
        const results = []
        for (let i = 0; i < files.length; i += batchSize) {
          const batch = files.slice(i, i + batchSize)
          results.push(...batch)
          // 允许事件循环处理其他任务
          await new Promise((resolve) => setTimeout(resolve, 0))
        }
        return results
      }

      // 并行处理多个目录
      const dirPromises = data.folderPaths.map((folderPath) =>
        readDirRecursive(data.resourceName, folderPath, data.allowedFileExt, data.existingFiles)
      )

      const results = await Promise.all(dirPromises)

      // 合并结果并发送进度
      port.postMessage({
        event: 'REFRESH_DIRECTORY::SUCCESS',
        data: results,
        stats: { totalFiles: results.length }
      })
    }
  })
})

// H5服务子进程实现
process.parentPort.on('message', (e) => {
  const [port] = e.ports

  port.on('message', async (e) => {
    const { data } = e

    if (data.event === 'SERVER_START') {
      // 初始化数据库管理器
      dbManager = DatabaseManager.getInstance(logger)
      await dbManager.waitForInitialization()

      // 初始化各种管理器
      settingManager = SettingManager.getInstance(logger, dbManager)
      await settingManager.waitForInitialization()

      // 启动服务器
      const serverRes = await server({
        dbManager,
        settingManager,
        resourcesManager,
        fileManager,
        logger,
        postMessage,
        onStartSuccess: (url) => {
          port.postMessage({
            event: 'SERVER_START::SUCCESS',
            url
          })
        }
      })

      ioServer = serverRes.ioServer
    }
  })
})

// H5服务器配置
const app = new Koa()
const ioServer = new Server(httpServer, {
  cors: { origin: '*', methods: ['GET', 'POST'] },
  transports: ['websocket', 'polling'],
  pingTimeout: 30000,
  pingInterval: 25000,
  upgradeTimeout: 10000,
  maxHttpBufferSize: 1e6
})

// Socket.IO 事件处理
ioServer.on('connection', (socket) => {
  socket.on('getSettingData', async (params, callback) => {
    try {
      const res = await settingManager.getSettingData()
      callback(res)
    } catch (err) {
      callback({ success: false, message: '操作失败' })
    }
  })

  socket.on('searchImages', async (params, callback) => {
    try {
      const res = await resourcesManager.search(params)
      callback(res)
    } catch (err) {
      callback({ success: false, message: '搜索失败' })
    }
  })
})

// 子进程服务管理
class SubProcessManager {
  constructor() {
    this.processes = new Map()
    this.messageHandlers = new Map()
  }

  createProcess(name, path, options = {}) {
    const childServer = new ChildServer(name, path)
    this.processes.set(name, childServer)

    childServer.start({
      options,
      onMessage: (data) => {
        const handler = this.messageHandlers.get(name)
        if (handler) {
          handler(data)
        }
      }
    })

    return childServer
  }

  registerMessageHandler(name, handler) {
    this.messageHandlers.set(name, handler)
  }

  stopProcess(name) {
    const process = this.processes.get(name)
    if (process) {
      process.stop()
      this.processes.delete(name)
    }
  }

  restartProcess(name, options = {}) {
    const process = this.processes.get(name)
    if (process) {
      process.restart(options)
    }
  }
}

架构设计与最佳实践:

  • 进程隔离:使用 Electron 的 utilityProcess 实现真正的进程隔离,避免主进程阻塞
  • 通信机制:通过 MessageChannel 实现高效的进程间通信,支持结构化数据传输
  • 错误处理:子进程崩溃不影响主进程,提供自动重启和错误恢复机制
  • 资源管理:独立管理子进程资源,支持内存和 CPU 优化,避免资源泄漏
  • 性能优化:通过并行处理和批量操作提升性能,支持大文件目录处理
  • 安全隔离:子进程运行在受限环境中,提升应用安全性
  • 监控调试:提供子进程状态监控、日志收集和性能分析
  • 热重启:支持子进程的热重启,无需重启整个应用

子进程生命周期管理:

  1. 创建阶段:通过 utilityProcess.fork 创建子进程,建立通信通道
  2. 初始化阶段:子进程加载必要的模块和初始化服务
  3. 运行阶段:处理主进程发送的消息,执行相应的业务逻辑
  4. 监控阶段:监控子进程状态,处理异常和错误
  5. 销毁阶段:优雅关闭子进程,清理资源

通信协议设计:

  • 事件驱动:基于事件的通信模式,支持异步消息处理
  • 结构化数据:使用 JSON 格式传输结构化数据
  • 错误处理:统一的错误处理和响应机制
  • 进度报告:支持长时间操作的进度报告和状态更新
  • 双向通信:支持主进程和子进程的双向通信

性能优化策略:

  • 批量处理:对大量文件进行批量处理,减少通信开销
  • 并行处理:支持多个目录的并行扫描和处理
  • 内存管理:及时释放不需要的内存,避免内存泄漏
  • 缓存机制:在子进程中实现缓存,减少重复计算
  • 流式处理:对大文件进行流式处理,避免内存溢出

自定义协议

自定义协议通过 Electron 的 protocol 模块注册自定义 URL 协议,用于处理本地资源访问。采用 fbwtp:// 协议提供高性能的本地文件服务,支持图片处理、视频播放和静态资源访问。

协议名称: fbwtp://

主要功能:

  • 图片处理 :通过 /api/images/get 路径处理图片请求,支持尺寸调整和格式转换
  • 视频处理 :通过 /api/videos/get 路径处理视频请求,支持流式传输
  • 静态资源:提供本地文件的 HTTP 风格访问接口
  • 缓存机制:实现智能缓存策略,提升访问性能
  • 安全访问:提供安全的本地资源访问机制,防止路径遍历攻击
  • 性能优化:通过协议处理减少文件 I/O 开销,支持流式处理
  • 格式支持:支持多种图片和视频格式的自动识别和处理
  • 压缩优化:智能压缩和尺寸调整,减少传输数据量

关键代码:

js 复制代码
// 协议注册
protocol.registerSchemesAsPrivileged([
  {
    scheme: 'fbwtp',
    privileges: {
      bypassCSP: true,
      secure: true,
      standard: true,
      supportFetchAPI: true,
      allowServiceWorkers: true,
      stream: true
    }
  }
])

// 协议处理
protocol.handle('fbwtp', async (request) => {
  const urlObj = new URL(request.url)
  const filePath = urlObj.searchParams.get('filePath')

  switch (urlObj.pathname) {
    case '/api/images/get': {
      const w = urlObj.searchParams.get('w')
      const h = urlObj.searchParams.get('h')
      const res = await handleFileResponse({ filePath, w, h })
      return new Response(res.data, { status: res.status, headers: res.headers })
    }
    case '/api/videos/get': {
      const res = await handleFileResponse({ filePath })
      return new Response(res.data, { status: res.status, headers: res.headers })
    }
  }
})

// 文件响应处理
export const handleFileResponse = async (query) => {
  const ret = { status: 404, headers: {}, data: null }

  try {
    let { filePath, w, compressStartSize } = query
    if (!filePath) return ret

    // 转换文件路径
    filePath = transFilePath(filePath)
    const width = w ? parseInt(w, 10) : null

    // 生成缓存键
    const cacheKey = `filePath=${filePath}&width=${width}`

    // 1. 先查缓存(只缓存小图/小文件)
    if (cache.has(cacheKey)) {
      const cacheData = cache.get(cacheKey)
      ret.status = 200
      ret.headers = { ...cacheData.headers, 'Server-Timing': `cache-hit;dur=${Date.now() - T1}` }
      ret.data = cacheData.data
      return ret
    }

    // 2. 未命中缓存,获取文件信息
    const stats = await fs.promises.stat(filePath)
    const originalFileSize = stats.size
    const extension = path.extname(filePath).toLowerCase()
    const mimeType = mimeTypes[extension] || 'application/octet-stream'
    const CACHE_LIMIT = 10 * 1024 * 1024 // 10MB

    // 判断是否需要缩放
    const canResize =
      ['.png', '.jpg', '.jpeg', '.avif', '.webp', '.gif'].includes(extension) && width
    const needResize = canResize && originalFileSize > startSize

    if (originalFileSize < CACHE_LIMIT) {
      // 小图缓存处理
      let fileBuffer
      if (needResize) {
        fileBuffer = await sharp(filePath)
          .resize({
            width,
            fit: 'inside',
            withoutEnlargement: true,
            kernel: 'lanczos3',
            fastShrinkOnLoad: true
          })
          .toBuffer()
      } else {
        fileBuffer = await fs.promises.readFile(filePath)
      }

      const headers = {
        'Content-Type': mimeType,
        'Content-Length': fileBuffer.length.toString(),
        'Cache-Control': 'max-age=3600',
        ETag: `"${stats.mtimeMs}-${originalFileSize}"`,
        'Last-Modified': stats.mtime.toUTCString()
      }

      // 缓存处理结果
      cache.set(cacheKey, { data: fileBuffer, headers })

      ret.status = 200
      ret.headers = headers
      ret.data = fileBuffer
    } else {
      // 大图流式处理
      let streamData
      const headers = {
        'Content-Type': mimeType,
        'Cache-Control': 'max-age=3600',
        'Original-Size': originalFileSize,
        'Last-Modified': stats.mtime.toUTCString(),
        ETag: `"${stats.mtimeMs}-${originalFileSize}"`
      }

      if (needResize) {
        const inputStream = fs.createReadStream(filePath)
        const transformer = sharp().resize({
          width,
          fit: 'inside',
          withoutEnlargement: true,
          kernel: 'lanczos3',
          fastShrinkOnLoad: true
        })
        streamData = inputStream.pipe(transformer)
        headers['X-Resize-Stream'] = '1'
      } else {
        streamData = fs.createReadStream(filePath)
        headers['Content-Length'] = originalFileSize
      }

      ret.status = 200
      ret.headers = headers
      ret.data = streamData
    }

    return ret
  } catch (err) {
    return ret
  }
}

// 文件路径转换
export const transFilePath = (filePath) => {
  // 处理 Windows 上的绝对路径
  if (process.platform === 'win32') {
    filePath = filePath.replace(/\//g, '\\')
    filePath = filePath.replace(/^([a-zA-Z])\\/, '$1:\\')
  } else {
    // macOS 和 Linux 确保是绝对路径
    if (!filePath.startsWith('/')) {
      filePath = '/' + filePath
    }
  }
  filePath = decodeURIComponent(filePath)
  return filePath
}

// 协议使用示例
// 图片访问:fbwtp://api/images/get?filePath=/path/to/image.jpg&w=800
// 视频访问:fbwtp://api/videos/get?filePath=/path/to/video.mp4
// 原始文件:fbwtp://api/images/get?filePath=/path/to/file.png

架构设计与最佳实践:

  • 协议设计:采用 RESTful 风格的 API 设计,支持查询参数和路径参数
  • 缓存策略:使用 LRU 缓存算法,实现智能缓存机制,区分小文件缓存和大文件流式处理
  • 性能优化:通过 Sharp 库实现高效的图片处理和格式转换
  • 安全机制:路径验证和转换,防止路径遍历攻击
  • 错误处理:统一的错误处理和响应机制
  • 流式处理:支持大文件的流式传输,避免内存溢出
  • 格式支持:自动识别文件格式,支持多种图片和视频格式
  • 跨平台兼容:处理不同操作系统的路径格式差异

协议路由设计:

  • 图片处理路由/api/images/get - 处理图片请求,支持尺寸调整和格式转换
  • 视频处理路由/api/videos/get - 处理视频请求,支持流式传输

缓存实现:

  • LRU 缓存 :使用 lru-cache 库实现,最大 1000 个条目,1GB 总大小
  • 智能缓存:小文件(<10MB)使用内存缓存,大文件使用流式传输
  • 缓存键生成:基于文件路径和尺寸参数生成唯一缓存键
  • 自动清理:支持 TTL 自动清理和 LRU 淘汰机制
  • 性能监控:记录缓存命中率和处理时间

性能优化策略:

  • 缓存分层:内存缓存策略,小文件缓存,大文件流式处理
  • 智能压缩:根据文件大小和类型自动选择压缩策略
  • 流式传输:大文件使用流式传输,小文件使用缓存
  • 并发处理:支持多个请求的并发处理
  • Sharp 优化:使用 Lanczos3 内核进行高质量图片缩放

安全机制:

  • 路径验证 :通过 transFilePath 函数验证和转换文件路径
  • 格式限制:限制支持的文件格式(png, jpg, jpeg, avif, webp, gif)
  • 大小限制:10MB 作为缓存和流式处理的分界线
  • 错误隐藏:统一的错误处理,避免敏感信息泄露

监控与调试:

  • 性能监控:通过 Server-Timing 头记录各阶段处理时间
  • 错误日志:详细的错误日志记录和分类
  • 缓存统计:缓存命中率和效率统计
  • 调试工具:开发环境下的调试和性能分析工具

进程通信

进程通信通过 Electron 的 IPC(进程间通信)机制实现主进程与渲染进程、子进程之间的通信。采用事件驱动的异步通信模式,支持双向数据传输和状态同步。

通信类型:

  • 主进程 ↔ 渲染进程 :通过 ipcMain.handleipcRenderer.invoke 进行请求-响应通信
  • 主进程 → 渲染进程 :通过 webContents.send 进行单向消息推送
  • 主进程 ↔ 子进程 :通过 MessageChannelutilityProcess 进行进程间通信
  • 窗口间通信:通过主进程中转实现窗口间的数据传递和状态同步

主要功能:

  • 系统级操作:文件选择、目录打开、URL 打开、系统对话框等
  • 窗口管理:窗口位置、大小、状态控制,窗口间跳转
  • 数据管理:设置数据获取/更新、资源搜索、收藏管理
  • 壁纸操作:壁纸切换、设置、自动切换控制
  • 服务管理:H5 服务启停、缓存清理、数据库操作
  • 状态同步:在窗口间同步应用状态和数据变更
  • 事件通知:向渲染进程发送通知和消息

IPC 处理器分类:

系统操作类:

js 复制代码
// 文件选择
ipcMain.handle('main:selectFolder', async () => {
  return await dialog.showOpenDialog({ properties: ['openDirectory'] })
})

ipcMain.handle('main:selectFile', async (event, type) => {
  const filters = []
  if (type === 'image') {
    filters.push({ name: 'Images', extensions: ['jpg', 'png', 'gif'] })
  } else if (type === 'video') {
    filters.push({ name: 'Movies', extensions: ['mp4'] })
  }
  return await dialog.showOpenDialog({
    properties: ['openFile'],
    filters
  })
})

// 系统操作
ipcMain.handle('main:openDir', (event, dirName) => {
  const dirPath = getDirPathByName(userDataPath, dirName)
  shell.openPath(dirPath)
})

ipcMain.handle('main:openUrl', (event, url) => {
  shell.openExternal(url)
})

ipcMain.handle('main:showItemInFolder', (event, filePath) => {
  shell.showItemInFolder(filePath)
})

窗口控制类:

js 复制代码
// 窗口位置和状态
ipcMain.handle('main:setWindowPosition', (event, name, position) => {
  setWindowPosition(name, position)
})

ipcMain.handle('main:getWindowPosition', (event, name) => {
  return getWindowPosition(name)
})

ipcMain.handle('main:resizeWindow', (event, name, action) => {
  resizeWindow(name, action)
})

// 窗口切换
ipcMain.handle('main:toggleMainWindow', () => {
  global.FBW.mainWindow.toggle()
})

ipcMain.handle('main:openSuspensionBall', (event, params) => {
  global.FBW.suspensionBall.createOrOpen(params)
})

ipcMain.handle('main:closeSuspensionBall', (event, params) => {
  global.FBW.suspensionBall.close(params)
})

数据管理类:

js 复制代码
// 设置数据
ipcMain.handle('main:getSettingData', () => {
  return this.settingManager.getSettingData()
})

ipcMain.handle('main:updateSettingData', async (event, formData) => {
  return await this.updateSettingData(formData)
})

// 资源管理
ipcMain.handle('main:getResourceMap', () => {
  return this.dbManager.getResourceMap()
})

ipcMain.handle('main:search', async (event, params) => {
  return await this.resourcesManager.search(params)
})

// 收藏管理
ipcMain.handle('main:addToFavorites', async (event, resourceId, isPrivacySpace = false) => {
  return await this.resourcesManager.addToFavorites(resourceId, isPrivacySpace)
})

ipcMain.handle('main:removeFavorites', async (event, resourceId, isPrivacySpace = false) => {
  return await this.resourcesManager.removeFavorites(resourceId, isPrivacySpace)
})

壁纸操作类:

js 复制代码
// 壁纸切换
ipcMain.handle('main:nextWallpaper', async () => {
  return this.doManualSwitchWallpaper('next')
})

ipcMain.handle('main:prevWallpaper', async () => {
  return this.doManualSwitchWallpaper('prev')
})

// 壁纸设置
ipcMain.handle('main:setAsWallpaperWithDownload', async (event, item) => {
  return await this.wallpaperManager.setAsWallpaperWithDownload(item)
})

ipcMain.handle('main:setWebWallpaper', (event, url) => {
  return this.setWebWallpaper(url)
})

ipcMain.handle('main:setColorWallpaper', (event, color) => {
  return this.setColorWallpaper(color)
})

// 自动切换控制
ipcMain.handle('main:toggleAutoSwitchWallpaper', async () => {
  return this.toggleAutoSwitchWallpaper()
})

动态壁纸控制类:

js 复制代码
// 动态壁纸设置
ipcMain.handle('main:setDynamicWallpaper', async (event, videoPath) => {
  return await global.FBW.dynamicWallpaperWindow.setDynamicWallpaper(videoPath)
})

ipcMain.handle('main:setDynamicWallpaperMute', (event, mute) => {
  global.FBW.dynamicWallpaperWindow.setMute(mute)
})

ipcMain.handle('main:setDynamicWallpaperPerformance', (event, mode) => {
  global.FBW.dynamicWallpaperWindow.setPerformanceMode(mode)
})

ipcMain.handle('main:setDynamicWallpaperOpacity', (event, opacity) => {
  global.FBW.dynamicWallpaperWindow.setOpacity(opacity)
})

服务管理类:

js 复制代码
// 缓存管理
ipcMain.handle('main:clearCache', () => {
  cache.clear()
  global.FBW.sendMsg(global.FBW.mainWindow.win, {
    type: 'success',
    message: t('messages.clearCacheSuccess')
  })
})

// H5 服务控制
ipcMain.handle('main:startH5Server', () => {
  this.handleH5ServerStart(3, 2000)
})

ipcMain.handle('main:stopH5Server', () => {
  this.handleH5ServerStop()
})

// 数据库操作
ipcMain.handle('main:doClearDB', async (event, tableName, resourceName) => {
  const res = await this.dbManager.clearDB(tableName, resourceName)
  global.FBW.sendMsg(global.FBW.mainWindow.win, {
    type: res.success ? 'success' : 'error',
    message: res.message
  })
  return res
})

消息推送机制:

js 复制代码
// 通用数据推送
global.FBW.sendCommonData = (win) => {
  const data = {
    osType,
    isLinux: isLinux(),
    isMac: isMac(),
    isWin: isWin(),
    isDev: isDev(),
    isProd: isProd(),
    h5ServerUrl: global.FBW.store?.h5ServerUrl
  }
  win.webContents.send('main:commonData', data)
}

// 消息通知推送
global.FBW.sendMsg = (win, msgOption) => {
  if (!win) return
  win.webContents.send('main:sendMsg', msgOption)
}

// 设置数据更新推送
this.notifySettingChange = (newSettings) => {
  // 向所有窗口推送设置更新
  global.FBW.mainWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  global.FBW.viewImageWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  global.FBW.suspensionBall.win.webContents.send('main:settingDataUpdate', this.settingData)
  global.FBW.dynamicWallpaperWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
  global.FBW.rhythmWallpaperWindow.win.webContents.send('main:settingDataUpdate', this.settingData)
}

// 动作触发推送
this.triggerAction = (action, data) => {
  global.FBW.mainWindow.win.webContents.send('main:triggerAction', action, data)
}

窗口间通信示例:

js 复制代码
// 主窗口跳转到指定页面
global.FBW.mainWindow.win.webContents.send('main:jumpToPage', key)

// 图片预览窗口数据推送
this.win.webContents.send('main:sendPostData', data)

// 动态壁纸窗口控制
this.win.webContents.send('main:setVideoSource', videoPath)
this.win.webContents.send('main:setVideoMute', mute)
this.win.webContents.send('main:setVideoFrameRate', frameRate)
this.win.webContents.send('main:setVideoScaleMode', mode)
this.win.webContents.send('main:setVideoBrightness', brightness)
this.win.webContents.send('main:setVideoContrast', value)

架构设计与最佳实践:

  • 事件驱动架构:采用事件驱动的异步通信模式,支持松耦合的组件交互
  • 请求-响应模式 :使用 ipcMain.handleipcRenderer.invoke 实现可靠的请求-响应通信
  • 单向推送模式 :使用 webContents.send 实现高效的单向消息推送
  • 统一命名规范 :所有 IPC 事件采用 main:action 的命名规范,便于管理和维护
  • 错误处理机制:统一的错误处理和响应机制,确保通信的可靠性
  • 状态同步策略:通过主进程中转实现窗口间的状态同步,避免直接窗口间通信
  • 性能优化:异步处理避免阻塞,批量操作减少通信开销
  • 安全隔离:渲染进程只能通过预定义的 IPC 接口访问主进程功能

通信模式设计:

  1. 同步请求-响应:适用于需要立即返回结果的操作

    js 复制代码
    // 渲染进程
    const result = await ipcRenderer.invoke('main:getSettingData')
    
    // 主进程
    ipcMain.handle('main:getSettingData', () => {
      return this.settingManager.getSettingData()
    })
  2. 异步通知推送:适用于状态变更和事件通知

    js 复制代码
    // 主进程推送
    win.webContents.send('main:settingDataUpdate', newSettings)
    
    // 渲染进程监听
    ipcRenderer.on('main:settingDataUpdate', (event, settings) => {
      // 处理设置更新
    })
  3. 窗口间通信:通过主进程中转实现窗口间数据传递

    js 复制代码
    // 窗口A → 主进程 → 窗口B
    global.FBW.mainWindow.win.webContents.send('main:triggerAction', action, data)

错误处理策略:

  • 统一错误格式 :所有错误响应采用统一的格式 { success: boolean, message: string }
  • 异常捕获:使用 try-catch 包装所有 IPC 处理器,确保异常不会导致进程崩溃
  • 超时处理:对长时间运行的操作设置超时机制
  • 重试机制:对网络相关操作实现重试逻辑
  • 用户反馈 :通过 sendMsg 向用户提供操作结果反馈

性能优化策略:

  • 批量操作:将多个小操作合并为批量操作,减少通信次数
  • 缓存机制:对频繁访问的数据实现缓存,减少重复请求
  • 异步处理:所有 IPC 处理器都采用异步处理,避免阻塞主线程
  • 延迟加载:按需加载数据和功能,减少初始化时间
  • 资源清理:及时清理不需要的事件监听器,避免内存泄漏

安全机制:

  • 输入验证:对所有 IPC 参数进行验证和清理
  • 权限控制:根据操作类型实现不同的权限控制
  • 路径安全:对文件路径进行安全验证,防止路径遍历攻击
  • 数据隔离:确保不同窗口间的数据隔离
  • 错误隐藏:避免在错误信息中暴露敏感的系统信息

监控与调试:

  • 通信日志:记录重要的 IPC 通信事件,便于调试和监控
  • 性能监控:监控 IPC 通信的性能指标,识别性能瓶颈
  • 错误统计:统计 IPC 通信错误,及时发现和解决问题
  • 调试工具:开发环境下的 IPC 通信调试工具
  • 事件追踪:支持 IPC 事件的追踪和回放

日志管理

日志管理通过 Pino 日志库提供高性能的日志记录功能,支持多级别日志、文件轮转和自动清理。采用结构化日志记录,便于问题排查和性能监控。

主要功能:

  • 多级别日志:支持 error、warn、info、debug 等不同级别的日志记录
  • 环境适配:开发环境使用 pino-pretty 美化输出,生产环境使用 pino-roll 文件轮转
  • 文件轮转:自动按日期和大小轮转日志文件,防止单个文件过大
  • 异常捕获:自动捕获和记录未处理的异常和 Promise 拒绝
  • 自动清理:定时清理过期日志文件,节省存储空间
  • 全局访问 :通过 global.logger 提供全局日志访问接口
  • 结构化日志:支持结构化数据记录,便于日志分析和处理

日志配置:

js 复制代码
// 日志系统初始化
export default (logDir, fileName = 'app.log') => {
  // 获取用户数据目录
  const logFilePath = join(logDir || process.env.FBW_LOGS_PATH, fileName)

  // 配置 pino-roll 传输
  const transport = pino.transport(
    isDev()
      ? {
          // 开发环境:美化输出
          target: 'pino-pretty',
          options: {
            colorize: true, // 彩色输出
            translateTime: 'SYS:standard', // 时间格式
            ignore: 'pid,hostname' // 忽略字段
          }
        }
      : {
          // 生产环境:文件轮转
          target: 'pino-roll',
          options: {
            file: logFilePath, // 日志文件路径
            frequency: 'daily', // 每天生成新文件
            size: '10M', // 每个文件最大 10MB
            mkdir: true, // 自动创建目录
            dateFormat: 'yyyy-MM-dd' // 文件命名格式:app.log.2025-02-27.1
          }
        }
  )

  // 创建 Pino 实例并挂载到全局
  const logger = pino(transport)
  global.logger = logger
}

异常处理机制:

js 复制代码
// 捕获未处理的异常
process.on('uncaughtException', (err) => {
  const errorMessage = err.message
  const errorName = err.name
  const errorStack = err.stack

  global.logger.error(
    `Uncaught Exception: Name => ${errorName}, Message => ${errorMessage}, Stack => ${errorStack}`
  )
})

// 捕获未处理的 Promise 拒绝
process.on('unhandledRejection', (reason) => {
  if (reason instanceof Error) {
    const errorName = reason.name
    const errorMessage = reason.message
    const errorStack = reason.stack

    global.logger.warn(
      `Uncaught Rejection: Name => ${errorName}, Message => ${errorMessage}, Stack => ${errorStack}`
    )
  } else {
    global.logger.warn(`Unhandled Rejection: ${reason}`)
  }
})

日志使用示例:

js 复制代码
// 应用启动日志
global.logger.info(`isDev: ${isDev()} process.env.NODE_ENV: ${process.env.NODE_ENV}`)
global.logger.info(`getIconPath FBW_RESOURCES_PATH: ${process.env.FBW_RESOURCES_PATH}`)

// 错误日志
global.logger.error(`设置动态壁纸失败: ${err}`)
global.logger.error(`H5服务器启动失败: ${data}`)

// 警告日志
global.logger.warn(`H5服务器IP无效: ${ip},尝试重启服务`)
global.logger.warn('主窗口未初始化,无法发送H5服务器URL')

// 信息日志
global.logger.info('Store 初始化完成')
global.logger.info(`H5服务器启动成功: ${this.h5ServerUrl}`)
global.logger.info('系统挂起,暂停自动切换壁纸')

// 子进程日志
global.logger.info(`ChildServer START:: serverName => ${this.#serverName}`)
global.logger.info(`ChildServer EXIT:: serverName => ${this.#serverName}`)

// 渲染进程日志
global.logger.info(`[Renderer Console][${level}] ${message} (${sourceId}:${line})`)

日志清理任务:

js 复制代码
// 定时清理旧日志文件
export default class CleanOldLogs {
  #job

  constructor() {
    this.#job = null
  }

  // 启动清理任务(每小时执行一次)
  start(rule = '0 * * * *') {
    this.stop()

    const logDir = process.env.FBW_LOGS_PATH
    if (!logDir) {
      global.logger.error('FBW_LOGS_PATH environment variable is not set.')
      return
    }

    this.#job = schedule.scheduleJob(rule, async () => {
      try {
        await this.cleanOldLogs(logDir)
      } catch (err) {
        global.logger.error(`Failed to clean old logs: ${err.message}`)
      }
    })

    global.logger.info('CleanOldLogs task started.')
  }

  // 清理超过 2 小时的旧日志文件
  async cleanOldLogs(logDir) {
    const now = Date.now()
    const twoHoursAgo = now - 2 * 60 * 60 * 1000

    const files = await fs.promises.readdir(logDir)

    for (const file of files) {
      const filePath = join(logDir, file)

      try {
        const stats = await fs.promises.stat(filePath)

        if (stats.mtimeMs < twoHoursAgo) {
          await fs.promises.unlink(filePath)
          global.logger.info(
            `Deleted old log file: ${file} (last modified: ${new Date(stats.mtimeMs).toISOString()})`
          )
        }
      } catch (err) {
        global.logger.error(`Failed to process file ${file}: ${err.message}`)
      }
    }
  }

  // 停止清理任务
  stop() {
    this.#job?.cancel()
    global.logger.info('CleanOldLogs task stopped.')
  }
}

日志分类与用途:

应用生命周期日志:

  • 启动信息:应用版本、环境变量、初始化状态
  • 关闭信息:资源清理、异常退出

业务操作日志:

  • 壁纸操作:切换、设置、下载状态
  • 用户操作:设置变更、收藏管理、搜索记录
  • 服务状态:H5 服务启停、子进程状态

系统监控日志:

  • 电源管理:系统挂起/恢复、电池模式切换
  • 性能监控:操作耗时、资源使用情况
  • 错误追踪:异常堆栈、错误上下文

调试辅助日志:

  • 开发环境:详细的调试信息
  • 网络请求:API 调用、响应状态
  • 文件操作:文件读写、路径处理

架构设计与最佳实践:

  • 结构化日志:使用 JSON 格式记录结构化数据,便于日志分析
  • 分级记录:根据重要性选择合适的日志级别
  • 上下文信息:记录足够的上下文信息,便于问题定位
  • 性能考虑:异步日志记录,避免阻塞主线程
  • 存储管理:自动轮转和清理,控制日志文件大小
  • 安全考虑:避免记录敏感信息,如密码、密钥等
  • 监控集成:支持日志监控和告警系统集成

日志级别说明:

  • error:错误信息,需要立即关注和处理
  • warn:警告信息,可能存在问题但不影响正常运行
  • info:一般信息,记录重要的业务操作和状态变更
  • debug:调试信息,仅在开发环境使用

性能优化策略:

  • 异步写入:使用 Pino 的异步写入机制,避免阻塞
  • 批量处理:批量写入日志,减少 I/O 操作
  • 内存缓冲:使用内存缓冲,提高写入性能
  • 压缩存储:对历史日志进行压缩,节省存储空间
  • 索引优化:为日志文件建立索引,提高查询效率

监控与告警:

  • 错误率监控:监控错误日志的数量和频率
  • 性能监控:监控日志写入性能,识别瓶颈
  • 存储监控:监控日志文件大小和存储使用情况
  • 异常告警:对严重错误设置告警机制
  • 趋势分析:分析日志趋势,预测潜在问题

缓存管理

缓存管理通过 LRU(最近最少使用)缓存算法提供高效的内存缓存功能。采用多级缓存策略,支持文件缓存、目录缓存和智能缓存清理,显著提升应用性能。

主要功能:

  • LRU 缓存:使用 LRU 算法管理缓存项,自动淘汰最久未使用的项目
  • 多级缓存:支持文件缓存和目录缓存,针对不同数据类型优化
  • 智能大小计算:根据数据内容和头部信息动态计算缓存项大小
  • 自动清理:自动清理过期和超量的缓存项,防止内存泄漏
  • 性能优化:减少重复的文件读取、网络请求和计算操作
  • 缓存命中统计:通过 Server-Timing 头记录缓存命中情况
  • 智能缓存策略:根据文件大小和类型选择不同的缓存策略

缓存配置:

js 复制代码
// LRU 缓存配置
const options = {
  // 缓存的最大条目数。超过此数量时,最久未使用的条目会被淘汰
  max: 1000,
  // 缓存的最大大小(基于 sizeCalculation 计算的值)
  maxSize: 1000 * 1024 * 1024, // 1GB
  // 用于计算缓存项大小的函数
  sizeCalculation: (value) => {
    // 计算 data 的大小
    const dataSize = value.data.length
    // 计算 headers 的大小
    const headersSize = Buffer.byteLength(JSON.stringify(value.headers))
    // 返回总大小
    return dataSize + headersSize
  },
  // 当缓存项被淘汰时调用的回调函数,用于清理资源
  // dispose: (value, key) => {
  //   console.log(`缓存项 ${key} 被淘汰,值为 ${value}`)
  // },
  // 缓存项的默认存活时间(毫秒)。超过此时间后,缓存项会自动失效。
  ttl: 1000 * 60 * 30, // 30分钟
  // 是否自动清理过期的缓存项
  ttlAutopurge: true,
  // 是否允许返回过期的缓存项(即使过期,仍然可以获取到值)
  allowStale: false,
  // 当调用 get 方法时,是否更新缓存项的存活时间(TTL)
  updateAgeOnGet: false,
  // 当调用 has 方法时,是否更新缓存项的存活时间(TTL)
  updateAgeOnHas: false
  // 当缓存未命中时,用于异步获取数据的函数
  // fetchMethod: async (key, staleValue, { options, signal, context }) => {}
}

const cache = new LRUCache(options)

文件缓存策略:

js 复制代码
// 文件响应处理中的缓存逻辑
export const handleFileResponse = async (query) => {
  const ret = { status: 404, headers: {}, data: null }

  try {
    let { filePath, w, compressStartSize } = query
    if (!filePath) return ret

    // 转换文件路径
    filePath = transFilePath(filePath)
    const width = w ? parseInt(w, 10) : null

    // 生成缓存键(基于文件路径和尺寸参数)
    const cacheKey = `filePath=${filePath}&width=${width}`

    // 1. 先查缓存(只缓存小图/小文件)
    if (cache.has(cacheKey)) {
      const cacheData = cache.get(cacheKey)
      // 返回文件内容和 MIME 类型
      ret.status = 200
      ret.headers = {
        ...cacheData.headers,
        'Server-Timing': `cache-hit;dur=${Date.now() - T1}` // 缓存命中统计
      }
      ret.data = cacheData.data
      return ret
    }

    // 2. 未命中缓存,获取文件信息
    const stats = await fs.promises.stat(filePath)
    const originalFileSize = stats.size
    const extension = path.extname(filePath).toLowerCase()
    const mimeType = mimeTypes[extension] || 'application/octet-stream'
    const CACHE_LIMIT = 10 * 1024 * 1024 // 10MB 缓存限制

    // 判断是否需要缩放
    const canResize =
      ['.png', '.jpg', '.jpeg', '.avif', '.webp', '.gif'].includes(extension) && width
    const needResize = canResize && originalFileSize > startSize

    if (originalFileSize < CACHE_LIMIT) {
      // 小图缓存处理
      let fileBuffer
      if (needResize) {
        fileBuffer = await sharp(filePath)
          .resize({
            width,
            fit: 'inside',
            withoutEnlargement: true,
            kernel: 'lanczos3',
            fastShrinkOnLoad: true
          })
          .toBuffer()
      } else {
        fileBuffer = await fs.promises.readFile(filePath)
      }

      const headers = {
        'Content-Type': mimeType,
        'Content-Length': fileBuffer.length.toString(),
        'Original-Size': originalFileSize,
        'Compressed-Size': fileBuffer.length,
        'Cache-Control': 'max-age=3600',
        ETag: `"${stats.mtimeMs}-${originalFileSize}"`,
        'Last-Modified': stats.mtime.toUTCString(),
        'Server-Timing': `file-check;dur=${T2 - T1}, file-stat;dur=${T3 - T2}, resize;dur=${T4 - T3}, total;dur=${T4 - T1}`
      }

      // 缓存处理结果
      cache.set(cacheKey, {
        data: fileBuffer,
        headers
      })

      ret.status = 200
      ret.headers = headers
      ret.data = fileBuffer
    } else {
      // 大图流式处理(不缓存)
      // ... 流式处理逻辑
    }

    return ret
  } catch (err) {
    return ret
  }
}

目录缓存策略:

js 复制代码
// 文件系统缓存配置
const fsCache = {
  // 缓存的目录数据
  directories: {},
  // 最后更新时间
  lastUpdate: Date.now(),
  // 缓存有效期(毫秒)
  ttl: 60000 // 1分钟
}

// 清除文件系统缓存
export const clearFsCache = () => {
  Object.keys(fsCache.directories).forEach((key) => {
    delete fsCache.directories[key]
  })
  fsCache.lastUpdate = Date.now()
}

// 异步读取目录并返回所有文件(包括子目录中的文件)
export const readDirRecursive = async (
  resourceName,
  dirPath,
  allowedFileExt,
  existingFiles = []
) => {
  // 检查缓存是否存在且有效
  const cacheKey = `${dirPath}_${allowedFileExt.join('_')}`

  if (fsCache.directories[cacheKey] && Date.now() - fsCache.lastUpdate < fsCache.ttl) {
    // 使用缓存数据,但仍然进行增量更新检查
    const cachedFiles = fsCache.directories[cacheKey]

    // 如果没有现有文件信息,直接返回缓存
    if (!existingFiles || existingFiles.length === 0) {
      return cachedFiles
    }

    // 否则,过滤出需要更新的文件
    const existingFilesMap = new Map()
    for (const file of existingFiles) {
      if (file.filePath) {
        existingFilesMap.set(file.filePath, file)
      }
    }

    const ret = cachedFiles.filter((file) => {
      const existingFile = existingFilesMap.get(file.filePath)
      return !existingFile || file.mtimeMs > existingFile.mtimeMs
    })
    return ret
  }

  // 缓存未命中,执行目录扫描
  // ... 目录扫描逻辑

  // 更新缓存
  fsCache.directories[cacheKey] = validRows
  fsCache.lastUpdate = Date.now()

  return validRows
}

缓存清理机制:

js 复制代码
// 缓存清理操作
export const clearCache = () => {
  cache.clear()
  global.FBW.sendMsg(global.FBW.mainWindow.win, {
    type: 'success',
    message: t('messages.clearCacheSuccess')
  })
}

// 文件删除时的缓存清理
async deleteFile(item) {
  // ... 文件删除逻辑

  // 删除相关缓存
  const cacheKeys = cache.keys()
  const newFilePath = transFilePath(filePath)
  for (const key of cacheKeys) {
    if (key.startsWith(`filePath=${newFilePath}`)) {
      cache.delete(key)
      global.logger.info(`删除缓存成功: id => ${id}, cacheKey => ${key}`)
    }
  }

  // ... 继续删除逻辑
}

缓存使用场景:

文件缓存场景:

  • 图片处理缓存:缓存经过 Sharp 处理的图片数据,避免重复处理
  • 小文件缓存:缓存小于 10MB 的文件,提升访问速度
  • HTTP 响应缓存:缓存文件响应的头部信息和数据
  • ETag 缓存:基于文件修改时间和大小生成 ETag,支持条件请求

目录缓存场景:

  • 目录扫描缓存:缓存目录扫描结果,减少文件系统访问
  • 增量更新:基于文件修改时间进行增量更新
  • 快速查找:缓存文件元数据,支持快速查找和过滤

缓存优化策略:

智能缓存策略:

  • 大小限制:小文件(<10MB)使用内存缓存,大文件使用流式处理
  • 格式优化:图片文件支持尺寸调整和格式转换缓存
  • 时间控制:文件缓存 30 分钟,目录缓存 1 分钟
  • 空间管理:最大 1000 个条目,总大小限制 1GB

性能优化:

  • 缓存命中统计:通过 Server-Timing 头记录缓存命中情况
  • 批量操作:支持批量缓存操作,减少 I/O 开销
  • 异步处理:缓存操作不阻塞主线程
  • 内存优化:精确计算缓存项大小,避免内存浪费

缓存监控:

性能指标:

  • 缓存命中率:通过 Server-Timing 头统计缓存命中情况
  • 缓存大小:监控缓存条目数和总大小
  • 缓存效率:统计缓存命中对性能的提升
  • 内存使用:监控缓存对内存使用的影响

清理策略:

  • 自动清理:TTL 自动清理过期缓存项
  • 手动清理:支持手动清理所有缓存
  • 关联清理:文件删除时自动清理相关缓存
  • 定期清理:定期清理无效缓存项

架构设计与最佳实践:

  • 分层缓存:文件缓存和目录缓存分离,针对不同数据类型优化
  • 智能淘汰:使用 LRU 算法自动淘汰最久未使用的缓存项
  • 大小控制:精确计算缓存项大小,防止内存溢出
  • 时间管理:合理的 TTL 设置,平衡性能和内存使用
  • 错误处理:缓存操作失败不影响主业务流程
  • 监控集成:支持缓存性能监控和统计
  • 安全考虑:避免缓存敏感数据,及时清理过期缓存

缓存级别说明:

  • L1 缓存(内存缓存):LRU 缓存,存储处理后的文件数据
  • L2 缓存(目录缓存):文件系统缓存,存储目录扫描结果
  • L3 缓存(磁盘缓存):通过文件系统实现的持久化缓存

性能优化策略:

  • 预加载机制:预加载常用文件到缓存
  • 压缩存储:对缓存数据进行压缩,节省内存空间
  • 批量操作:批量处理缓存操作,提高效率
  • 异步更新:异步更新缓存,不阻塞主线程
  • 智能预取:根据访问模式智能预取数据

应用更新

应用更新通过 electron-updater 库实现自动更新功能,支持检查更新、下载和安装。采用 GitHub Releases 作为更新源,提供完整的更新生命周期管理。

主要功能:

  • 自动检查:定期检查应用更新,支持手动触发检查
  • 更新通知:通过系统通知提醒用户有可用更新
  • 开发环境支持:开发环境支持调试更新功能
  • 多平台支持:支持 Windows、macOS 平台的自动更新
  • 错误处理:处理更新过程中的各种错误和异常情况
  • 用户交互:提供更新进度反馈和用户操作界面
  • 回滚支持:支持版本回滚和降级操作
  • 增量更新:支持增量更新包,减少下载大小

更新器配置:

js 复制代码
// 更新器初始化
export default class Updater {
  constructor() {
    this.init()
  }

  init() {
    if (!app.isPackaged) {
      // 开发环境配置
      autoUpdater.forceDevUpdateConfig = true
      autoUpdater.logger = global.logger
      global.logger.info(`更新配置路径: ${JSON.stringify(autoUpdater.configOnDisk)}`)

      // 开发环境忽略代码签名检查
      autoUpdater.disableWebInstaller = true
      // 支持降级
      autoUpdater.allowDowngrade = true
      // 配置自定义更新服务器
      autoUpdater.setFeedURL({
        provider: 'generic',
        url: 'http://localhost:8080'
      })
    }

    // 生产环境配置
    autoUpdater.autoDownload = false // 不自动下载,等待用户确认
    autoUpdater.autoInstallOnAppQuit = true // 应用退出后自动安装

    // 监听渲染进程的检查更新事件
    ipcMain.on('main:checkUpdate', () => {
      autoUpdater.checkForUpdatesAndNotify()
    })
  }

  // 事件监听器
  on(event, callback = () => {}) {
    if (typeof callback !== 'function') {
      throw new Error('callback must be a function')
    }
    autoUpdater.on(event, callback)
  }

  // 手动检查更新
  checkUpdate() {
    autoUpdater.checkForUpdatesAndNotify()
  }
}

更新事件处理:

js 复制代码
// 更新器初始化
updater = new Updater()

// 有可用更新
updater.on('update-available', (info) => {
  global.logger.info('有可用更新', info)

  // 显示系统通知
  const notice = new Notification({
    title: t('actions.checkUpdate'),
    body: t('messages.updateAvailable', {
      version: `v${info.version}`
    })
  })

  // 点击通知打开更新页面
  notice.on('click', () => {
    shell.openExternal(appInfo.github + '/releases')
  })

  notice.show()
})

// 无需更新
updater.on('update-not-available', (info) => {
  global.logger.info('无需更新', info)

  // 显示系统通知
  new Notification({
    title: t('actions.checkUpdate'),
    body: t('messages.updateNotAvailable')
  }).show()
})

// 更新错误
updater.on('error', (err) => {
  global.logger.error(`更新失败: error => ${err}`)

  // 显示错误通知
  new Notification({
    title: t('actions.checkUpdate'),
    body: t('messages.checkUpdateFail')
  }).show()
})

构建配置:

yaml 复制代码
# electron-builder.yml
appId: co.oxoyo.flying-bird-wallpaper
productName: Flying Bird Wallpaper

# 启用更新文件生成
generateUpdatesFilesForAllChannels: true

# 发布配置
publish:
  # GitHub 发布配置
  provider: github
  owner: OXOYO
  repo: Flying-Bird-Wallpaper
  private: false
  releaseType: release
  publishAutoUpdate: true
  # 更新通道配置
  channel: latest
  # 发布文件配置
  publisherName: ['*.exe', '*.dmg', 'latest*.yml', '!*.blockmap']

# Windows 配置
win:
  executableName: Flying Bird Wallpaper
  target:
    - target: nsis
      arch:
        - x64
        - arm64

# macOS 配置
mac:
  artifactName: 'Flying-Bird-Wallpaper-${version}-${arch}.${ext}'
  entitlementsInherit: build/entitlements.mac.plist
  category: public.app-category.utilities
  darkModeSupport: true
  hardenedRuntime: true
  gatekeeperAssess: false
  icon: build/icon.icns
  identity: 'co.oxoyo.flying-bird-wallpaper'
  target:
    - target: dmg
      arch:
        - x64
        - arm64

# DMG 配置
dmg:
  artifactName: 'Flying-Bird-Wallpaper-${version}-${arch}.${ext}'
  icon: build/icon.icns
  window:
    width: 540
    height: 380
  writeUpdateInfo: true

开发环境配置:

yaml 复制代码
# dev-app-update.yml
provider: github
owner: OXOYO
repo: Flying-Bird-Wallpaper
updaterCacheDirName: Flying Bird Wallpaper-updater

更新流程管理:

js 复制代码
// 更新状态管理
class UpdateManager {
  constructor() {
    this.updateStatus = {
      checking: false,
      available: false,
      downloading: false,
      downloaded: false,
      error: null
    }
    this.updateInfo = null
  }

  // 检查更新
  async checkForUpdates() {
    if (this.updateStatus.checking) return

    this.updateStatus.checking = true
    this.updateStatus.error = null

    try {
      await autoUpdater.checkForUpdates()
    } catch (error) {
      this.updateStatus.error = error
      this.handleUpdateError(error)
    } finally {
      this.updateStatus.checking = false
    }
  }

  // 下载更新
  async downloadUpdate() {
    if (!this.updateInfo || this.updateStatus.downloading) return

    this.updateStatus.downloading = true
    this.updateStatus.error = null

    try {
      await autoUpdater.downloadUpdate()
    } catch (error) {
      this.updateStatus.error = error
      this.handleUpdateError(error)
    }
  }

  // 安装更新
  async installUpdate() {
    if (!this.updateStatus.downloaded) return

    try {
      autoUpdater.quitAndInstall()
    } catch (error) {
      this.updateStatus.error = error
      this.handleUpdateError(error)
    }
  }

  // 处理更新错误
  handleUpdateError(error) {
    global.logger.error(`更新错误: ${error.message}`)

    // 显示错误通知
    new Notification({
      title: t('actions.checkUpdate'),
      body: t('messages.updateError', { error: error.message })
    }).show()
  }
}

更新事件类型:

检查更新事件:

  • checking-for-update:开始检查更新
  • update-available:发现可用更新
  • update-not-available:无可用更新
  • error:检查更新时发生错误

下载更新事件:

  • download-progress:下载进度更新
  • update-downloaded:更新下载完成
  • error:下载更新时发生错误

安装更新事件:

  • update-downloaded:更新已下载,准备安装
  • before-quit-for-update:应用即将退出进行更新

用户交互界面:

js 复制代码
// 更新对话框组件
class UpdateDialog {
  constructor() {
    this.dialog = null
    this.updateInfo = null
  }

  // 显示更新对话框
  showUpdateDialog(updateInfo) {
    this.updateInfo = updateInfo

    this.dialog = new BrowserWindow({
      width: 400,
      height: 300,
      modal: true,
      parent: global.FBW.mainWindow.win,
      webPreferences: {
        nodeIntegration: true,
        contextIsolation: false
      }
    })

    this.dialog.loadFile('update-dialog.html')
  }

  // 更新进度显示
  updateProgress(progress) {
    if (this.dialog) {
      this.dialog.webContents.send('update-progress', progress)
    }
  }

  // 关闭对话框
  close() {
    if (this.dialog) {
      this.dialog.close()
      this.dialog = null
    }
  }
}

架构设计与最佳实践:

  • 分层架构:更新器、事件处理、用户界面分离
  • 错误处理:完善的错误处理和用户反馈机制
  • 用户体验:非阻塞更新检查,友好的用户界面
  • 安全性:代码签名验证,安全的更新源
  • 性能优化:增量更新,后台下载
  • 兼容性:支持多平台和不同版本
  • 监控集成:更新状态监控和日志记录

更新策略:

检查策略:

  • 启动检查:应用启动时检查更新
  • 定期检查:定时检查更新(可配置)
  • 手动检查:用户手动触发检查
  • 后台检查:应用运行时后台检查

下载策略:

  • 用户确认:用户确认后开始下载
  • 后台下载:下载过程不阻塞用户操作
  • 进度显示:实时显示下载进度
  • 断点续传:支持下载中断和恢复

安装策略:

  • 退出安装:应用退出时自动安装
  • 用户确认:用户确认后立即安装
  • 回滚支持:安装失败时支持回滚
  • 静默安装:支持静默安装模式

安全机制:

  • 代码签名:验证更新包的代码签名
  • 完整性检查:验证更新包的完整性
  • 来源验证:验证更新源的合法性
  • 权限控制:限制更新操作的权限

监控与调试:

  • 更新日志:记录详细的更新过程日志
  • 错误统计:统计更新错误和失败率
  • 性能监控:监控更新过程的性能指标
  • 用户反馈:收集用户对更新过程的反馈
  • 调试工具:开发环境下的更新调试工具

版本管理:

  • 语义化版本:使用语义化版本号管理
  • 更新通道:支持稳定版和测试版通道
  • 兼容性检查:检查版本兼容性
  • 强制更新:支持强制更新机制
  • 增量更新:支持增量更新包

相关推荐
素界UI设计1 小时前
开源网页生态掘金:从Bootstrap二次开发到行业专属组件库的技术变现
前端·开源·bootstrap
潘小安1 小时前
【译】六个开发高手使用的 css 动画秘诀
前端·css·性能优化
前端开发爱好者1 小时前
尤雨溪官宣:Vite 历史性的一刻!超越 Webpack!
前端·javascript·vite
前端开发爱好者1 小时前
Vue3 "抛弃" Axios !用上了 专属请求库!
前端·javascript·vue.js
前端开发爱好者1 小时前
"Lodash" 的终极版!Vue、React 通杀!
前端·javascript·全栈
前端开发爱好者1 小时前
TanStack:不止于 Vue!一个库,真·通杀所有框架!
前端·javascript·vue.js
curdcv_po1 小时前
Three.js,给纹理,设颜色空间
前端
站大爷IP1 小时前
HTTPS代理抓包完全攻略:工具、配置与高级技巧
前端
洛卡卡了1 小时前
“改个配置还要发版?”搞个配置后台不好吗
前端·后端·架构
林太白2 小时前
CommonJS和ES Modules篇
前端·面试