Electron 本地数据库集成

1. 数据库选型与对比

1.1 Electron 适用数据库

┌─────────────────────────────────────────────────────────────┐
│                  Electron 数据存储方案                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                 │
│  │    轻量级        │  │    关系型        │                 │
│  │  electron-store │  │     SQLite       │                 │
│  │  JSON/Key-Value │  │   better-sqlite3│                 │
│  └─────────────────┘  └─────────────────┘                 │
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                 │
│  │   浏览器原生      │  │    浏览器原生     │                 │
│  │    IndexedDB     │  │    LocalStorage │                 │
│  │    Dexie.js     │  │    SessionStorage│                 │
│  └─────────────────┘  └─────────────────┘                 │
│                                                             │
│  ┌─────────────────┐  ┌─────────────────┐                 │
│  │   嵌入式文档型    │  │    重量级        │                 │
│  │    lowdb/json   │  │   MongoDB/Redis │                 │
│  │    NeDB         │  │   (需服务端)     │                 │
│  └─────────────────┘  └─────────────────┘                 │
│                                                             │
└─────────────────────────────────────────────────────────────┘

1.2 数据库对比表

数据库类型数据量复杂度适用场景性能
electron-storeKey-Value< 1MB配置、偏好设置极快
LocalStorageKey-Value< 5MB简单数据存储
IndexedDB文档存储< 50MB⭐⭐⭐结构化数据中等
SQLite关系型< 1GB⭐⭐复杂查询、多表关联
lowdb文档存储< 10MB⭐⭐JSON 文档存储
NeDB文档存储< 1GB⭐⭐MongoDB 兼容接口
MongoDB文档存储无限制⭐⭐⭐⭐大型应用

1.3 选型建议

场景推荐方案理由
应用配置electron-store简单、自动持久化
用户偏好设置electron-store键值对存储
游戏列表管理SQLite / IndexedDB结构化、多表关联
大文件缓存IndexedDB支持 Blob
离线优先应用IndexedDB + SQLite组合使用
临时数据LocalStorage页面级存储
日志存储SQLite高写入性能
文档存储lowdb / NeDBJSON 友好

2. 环境准备与依赖安装

2.1 项目结构

gamebox-app/
├── src/
│   ├── main/
│   │   ├── index.ts
│   │   ├── database/
│   │   │   ├── index.ts       # 数据库入口
│   │   │   ├── sqlite.ts      # SQLite 操作
│   │   │   ├── migrations/    # 迁移脚本
│   │   │   └── models/        # 数据模型
│   │   └── ipc.ts
│   └── renderer/
│       └── src/
│           ├── database/
│           │   ├── indexeddb.ts
│           │   └── models/
│           └── stores/
├── package.json
└── tsconfig.json

2.2 安装依赖

# SQLite(主进程使用)
npm install better-sqlite3 --save
npm install @types/better-sqlite3 --save-dev

# electron-store(配置存储)
npm install electron-store --save

# IndexedDB 封装库
npm install dexie --save

# lowdb(JSON 数据库)
npm install lowdb --save

# NeDB(嵌入式 MongoDB)
npm install nedb --save

# TypeORM(可选,ORM 框架)
npm install typeorm reflect-metadata --save
npm install better-sqlite3 --save

2.3 依赖版本兼容性

{
  "dependencies": {
    "better-sqlite3": "^9.2.0",
    "electron-store": "^8.1.0",
    "dexie": "^3.2.0",
    "lowdb": "^5.1.0",
    "nedb": "^1.8.0"
  }
}

⚠️ 注意:Node.js 原生模块(如 better-sqlite3)需要在打包后重新编译。请确保配置了 postinstall 脚本。


3. SQLite 集成

3.1 为什么选择 SQLite

优势说明
零配置无需安装数据库服务器
跨平台Windows/macOS/Linux 全支持
高性能本地读写,极低延迟
ACID事务支持,数据安全
SQL 支持强大的查询能力
体积小仅几百 KB

3.2 数据库初始化

// src/main/database/sqlite.ts
import Database from 'better-sqlite3'
import { app } from 'electron'
import * as path from 'path'
import * as fs from 'fs'
import log from 'electron-log'

export interface Game {
  id: string
  name: string
  path: string
  cover: string
  category: string
  category_id: string
  rating: number
  featured: boolean
  is_new: boolean
  description: string
  size: string
  play_time: number
  created_at: string
  updated_at: string
}

export interface Category {
  id: string
  name: string
  icon: string
  sort_order: number
  created_at: string
}

export interface UserSettings {
  key: string
  value: string
}

class SQLiteDatabase {
  private db: Database.Database | null = null
  private dbPath: string

  constructor() {
    // 数据库文件放在用户数据目录下
    const userDataPath = app.getPath('userData')
    this.dbPath = path.join(userDataPath, 'gamebox.db')
    log.info(`数据库路径: ${this.dbPath}`)
  }

  /**
   * 初始化数据库
   */
  initialize(): void {
    try {
      // 确保目录存在
      const dir = path.dirname(this.dbPath)
      if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true })
      }

      // 创建数据库连接
      this.db = new Database(this.dbPath)

      // 启用 WAL 模式(提高并发性能)
      this.db.pragma('journal_mode = WAL')

      // 设置超时
      this.db.pragma('busy_timeout = 5000')

      log.info('✅ SQLite 数据库初始化成功')

      // 创建表
      this.createTables()
    } catch (error) {
      log.error('❌ 数据库初始化失败:', error)
      throw error
    }
  }

  /**
   * 创建数据表
   */
  private createTables(): void {
    if (!this.db) throw new Error('数据库未初始化')

    // 游戏表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS games (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        path TEXT NOT NULL,
        cover TEXT DEFAULT '',
        category TEXT DEFAULT '未分类',
        category_id TEXT DEFAULT 'uncategorized',
        rating REAL DEFAULT 0,
        featured INTEGER DEFAULT 0,
        is_new INTEGER DEFAULT 0,
        description TEXT DEFAULT '',
        size TEXT DEFAULT '',
        play_time INTEGER DEFAULT 0,
        created_at TEXT DEFAULT (datetime('now')),
        updated_at TEXT DEFAULT (datetime('now'))
      )
    `)

    // 分类表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS categories (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        icon TEXT DEFAULT '',
        sort_order INTEGER DEFAULT 0,
        created_at TEXT DEFAULT (datetime('now'))
      )
    `)

    // 用户设置表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS settings (
        key TEXT PRIMARY KEY,
        value TEXT
      )
    `)

    // 最近游戏表
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS recent_games (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        game_id TEXT NOT NULL,
        played_at TEXT DEFAULT (datetime('now')),
        FOREIGN KEY (game_id) REFERENCES games(id)
      )
    `)

    // 创建索引
    this.db.exec(`
      CREATE INDEX IF NOT EXISTS idx_games_category ON games(category_id);
      CREATE INDEX IF NOT EXISTS idx_games_featured ON games(featured);
      CREATE INDEX IF NOT EXISTS idx_games_rating ON games(rating DESC);
      CREATE INDEX IF NOT EXISTS idx_recent_games ON recent_games(played_at DESC);
    `)

    log.info('✅ 数据表创建完成')
  }

  /**
   * 关闭数据库连接
   */
  close(): void {
    if (this.db) {
      this.db.close()
      this.db = null
      log.info('✅ 数据库连接已关闭')
    }
  }

  // ==================== 游戏操作 ====================

  /**
   * 获取所有游戏
   */
  getAllGames(): Game[] {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT * FROM games ORDER BY rating DESC')
    return stmt.all() as Game[]
  }

  /**
   * 根据分类获取游戏
   */
  getGamesByCategory(categoryId: string): Game[] {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT * FROM games WHERE category_id = ? ORDER BY rating DESC')
    return stmt.all(categoryId) as Game[]
  }

  /**
   * 获取单个游戏
   */
  getGameById(id: string): Game | undefined {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT * FROM games WHERE id = ?')
    return stmt.get(id) as Game | undefined
  }

  /**
   * 搜索游戏
   */
  searchGames(keyword: string): Game[] {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare(`
      SELECT * FROM games 
      WHERE name LIKE ? OR description LIKE ?
      ORDER BY rating DESC
    `)
    const pattern = `%${keyword}%`
    return stmt.all(pattern, pattern) as Game[]
  }

  /**
   * 添加游戏
   */
  addGame(game: Game): void {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare(`
      INSERT INTO games (id, name, path, cover, category, category_id, rating, featured, is_new, description, size)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)
    stmt.run(
      game.id,
      game.name,
      game.path,
      game.cover,
      game.category,
      game.category_id,
      game.rating,
      game.featured ? 1 : 0,
      game.is_new ? 1 : 0,
      game.description,
      game.size
    )
  }

  /**
   * 更新游戏
   */
  updateGame(id: string, updates: Partial<Game>): void {
    if (!this.db) throw new Error('数据库未初始化')

    const fields: string[] = []
    const values: any[] = []

    Object.entries(updates).forEach(([key, value]) => {
      if (key !== 'id' && key !== 'created_at') {
        // 处理布尔值
        if (typeof value === 'boolean') {
          value = value ? 1 : 0
        }
        fields.push(`${key} = ?`)
        values.push(value)
      }
    })

    if (fields.length > 0) {
      fields.push('updated_at = datetime("now")')
      values.push(id)

      const sql = `UPDATE games SET ${fields.join(', ')} WHERE id = ?`
      const stmt = this.db.prepare(sql)
      stmt.run(...values)
    }
  }

  /**
   * 删除游戏
   */
  deleteGame(id: string): void {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('DELETE FROM games WHERE id = ?')
    stmt.run(id)
  }

  /**
   * 批量添加游戏
   */
  addGames(games: Game[]): void {
    if (!this.db) throw new Error('数据库未初始化')

    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO games (id, name, path, cover, category, category_id, rating, featured, is_new, description, size)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    const transaction = this.db.transaction((games: Game[]) => {
      for (const game of games) {
        stmt.run(
          game.id,
          game.name,
          game.path,
          game.cover,
          game.category,
          game.category_id,
          game.rating,
          game.featured ? 1 : 0,
          game.is_new ? 1 : 0,
          game.description,
          game.size
        )
      }
    })

    transaction(games)
    log.info(`✅ 批量添加 ${games.length} 个游戏`)
  }

  // ==================== 分类操作 ====================

  /**
   * 获取所有分类
   */
  getAllCategories(): Category[] {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT * FROM categories ORDER BY sort_order')
    return stmt.all() as Category[]
  }

  /**
   * 添加分类
   */
  addCategory(category: Category): void {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare(`
      INSERT INTO categories (id, name, icon, sort_order)
      VALUES (?, ?, ?, ?)
    `)
    stmt.run(category.id, category.name, category.icon, category.sort_order)
  }

  // ==================== 设置操作 ====================

  /**
   * 获取设置
   */
  getSetting(key: string): string | undefined {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT value FROM settings WHERE key = ?')
    const result = stmt.get(key) as { value: string } | undefined
    return result?.value
  }

  /**
   * 设置设置
   */
  setSetting(key: string, value: string): void {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare(`
      INSERT OR REPLACE INTO settings (key, value)
      VALUES (?, ?)
    `)
    stmt.run(key, value)
  }

  // ==================== 统计操作 ====================

  /**
   * 获取游戏统计
   */
  getStats(): { totalGames: number; totalCategories: number; totalPlayTime: number } {
    if (!this.db) throw new Error('数据库未初始化')

    const stats = this.db.prepare(`
      SELECT 
        (SELECT COUNT(*) FROM games) as totalGames,
        (SELECT COUNT(*) FROM categories) as totalCategories,
        (SELECT COALESCE(SUM(play_time), 0) FROM games) as totalPlayTime
    `).get() as any

    return stats
  }

  /**
   * 获取热门游戏
   */
  getFeaturedGames(limit: number = 5): Game[] {
    if (!this.db) throw new Error('数据库未初始化')
    const stmt = this.db.prepare('SELECT * FROM games WHERE featured = 1 ORDER BY rating DESC LIMIT ?')
    return stmt.all(limit) as Game[]
  }
}

// 导出单例
export const database = new SQLiteDatabase()

3.3 IPC 处理程序集成

// src/main/ipc.ts
import { ipcMain } from 'electron'
import { database, Game } from './database/sqlite'

export function setupDatabaseHandlers(): void {
  console.log('📡 设置数据库 IPC 处理程序')

  // ========== 游戏操作 ==========

  ipcMain.handle('db-get-games', async () => {
    return database.getAllGames()
  })

  ipcMain.handle('db-get-games-by-category', async (_, categoryId: string) => {
    return database.getGamesByCategory(categoryId)
  })

  ipcMain.handle('db-get-game', async (_, id: string) => {
    return database.getGameById(id)
  })

  ipcMain.handle('db-search-games', async (_, keyword: string) => {
    return database.searchGames(keyword)
  })

  ipcMain.handle('db-add-game', async (_, game: Game) => {
    database.addGame(game)
    return { success: true }
  })

  ipcMain.handle('db-update-game', async (_, id: string, updates: Partial<Game>) => {
    database.updateGame(id, updates)
    return { success: true }
  })

  ipcMain.handle('db-delete-game', async (_, id: string) => {
    database.deleteGame(id)
    return { success: true }
  })

  ipcMain.handle('db-batch-add-games', async (_, games: Game[]) => {
    database.addGames(games)
    return { success: true, count: games.length }
  })

  // ========== 分类操作 ==========

  ipcMain.handle('db-get-categories', async () => {
    return database.getAllCategories()
  })

  ipcMain.handle('db-add-category', async (_, category) => {
    database.addCategory(category)
    return { success: true }
  })

  // ========== 设置操作 ==========

  ipcMain.handle('db-get-setting', async (_, key: string) => {
    return database.getSetting(key)
  })

  ipcMain.handle('db-set-setting', async (_, key: string, value: string) => {
    database.setSetting(key, value)
    return { success: true }
  })

  // ========== 统计操作 ==========

  ipcMain.handle('db-get-stats', async () => {
    return database.getStats()
  })

  ipcMain.handle('db-get-featured-games', async (_, limit: number = 5) => {
    return database.getFeaturedGames(limit)
  })
}

3.4 主进程入口集成

// src/main/index.ts
import { app, BrowserWindow } from 'electron'
import { database } from './database/sqlite'
import { setupDatabaseHandlers } from './ipc'

let mainWindow: BrowserWindow | null = null

function createWindow(): void {
  // ...
}

app.whenReady().then(() => {
  // 初始化数据库
  database.initialize()

  // 设置 IPC 处理程序
  setupDatabaseHandlers()

  // 创建窗口
  createWindow()
})

// 应用退出时关闭数据库
app.on('before-quit', () => {
  database.close()
})

4. IndexedDB 集成

4.1 IndexedDB 适用场景

场景说明
浏览器原生无需额外依赖
Blob 存储支持大文件、图片
离线优先前端数据缓存
复杂查询索引支持

4.2 Dexie.js 封装

// src/renderer/src/database/dexie.ts
import Dexie, { Table } from 'dexie'

// 数据模型
export interface GameRecord {
  id?: number
  gameId: string       // 外部游戏 ID
  name: string
  cover: Blob | string
  rating: number
  lastPlayed: Date
  playCount: number
}

export interface UserPreference {
  key: string
  value: any
  updatedAt: Date
}

export interface CacheEntry {
  key: string
  data: any
  expiresAt: Date
}

// 数据库类
class GameBoxDB extends Dexie {
  games!: Table<GameRecord>
  preferences!: Table<UserPreference>
  cache!: Table<CacheEntry>

  constructor() {
    super('GameBoxDB')

    this.version(1).stores({
      games: '++id, gameId, name, rating, lastPlayed',
      preferences: 'key',
      cache: 'key, expiresAt'
    })
  }
}

export const db = new GameBoxDB()

// 数据库操作工具类
export class GameBoxDBService {
  // ==================== 游戏缓存 ====================

  /**
   * 缓存游戏到 IndexedDB
   */
  static async cacheGame(game: GameRecord): Promise<void> {
    await db.games.put(game)
  }

  /**
   * 批量缓存游戏
   */
  static async cacheGames(games: GameRecord[]): Promise<void> {
    await db.games.bulkPut(games)
  }

  /**
   * 获取缓存的游戏列表
   */
  static async getCachedGames(): Promise<GameRecord[]> {
    return db.games.toArray()
  }

  /**
   * 获取单个游戏
   */
  static async getGame(gameId: string): Promise<GameRecord | undefined> {
    return db.games.where('gameId').equals(gameId).first()
  }

  /**
   * 更新游戏播放记录
   */
  static async updatePlayRecord(gameId: string): Promise<void> {
    const game = await this.getGame(gameId)
    if (game) {
      game.lastPlayed = new Date()
      game.playCount = (game.playCount || 0) + 1
      await db.games.put(game)
    }
  }

  // ==================== 用户偏好 ====================

  /**
   * 保存用户偏好
   */
  static async setPreference(key: string, value: any): Promise<void> {
    await db.preferences.put({
      key,
      value,
      updatedAt: new Date()
    })
  }

  /**
   * 获取用户偏好
   */
  static async getPreference<T>(key: string, defaultValue?: T): Promise<T | undefined> {
    const pref = await db.preferences.get(key)
    return pref?.value as T ?? defaultValue
  }

  // ==================== 缓存管理 ====================

  /**
   * 设置缓存(带过期时间)
   */
  static async setCache(key: string, data: any, ttlSeconds: number = 3600): Promise<void> {
    const expiresAt = new Date(Date.now() + ttlSeconds * 1000)
    await db.cache.put({ key, data, expiresAt })
  }

  /**
   * 获取缓存
   */
  static async getCache<T>(key: string): Promise<T | null> {
    const entry = await db.cache.get(key)

    if (!entry) return null

    // 检查是否过期
    if (entry.expiresAt < new Date()) {
      await db.cache.delete(key)
      return null
    }

    return entry.data as T
  }

  /**
   * 清理过期缓存
   */
  static async cleanExpiredCache(): Promise<void> {
    const now = new Date()
    await db.cache.where('expiresAt').below(now).delete()
  }

  /**
   * 清空所有缓存
   */
  static async clearCache(): Promise<void> {
    await db.cache.clear()
  }

  // ==================== 统计 ====================

  /**
   * 获取最近玩过的游戏
   */
  static async getRecentlyPlayed(limit: number = 10): Promise<GameRecord[]> {
    return db.games
      .orderBy('lastPlayed')
      .reverse()
      .limit(limit)
      .toArray()
  }

  /**
   * 获取游戏统计
   */
  static async getGameStats(): Promise<{
    totalGames: number
    totalPlayTime: number
    mostPlayed: GameRecord | undefined
  }> {
    const games = await db.games.toArray()
    const totalGames = games.length
    const totalPlayTime = games.reduce((sum, g) => sum + (g.playCount || 0), 0)
    const mostPlayed = games.reduce((max, g) => 
      (g.playCount || 0) > (max?.playCount || 0) ? g : max
    , undefined as GameRecord | undefined)

    return { totalGames, totalPlayTime, mostPlayed }
  }
}

4.3 渲染进程使用示例

<!-- src/renderer/src/components/GameCacheDemo.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { db, GameBoxDBService, type GameRecord } from '@renderer/database/dexie'

const recentGames = ref<GameRecord[]>([])
const stats = ref({ totalGames: 0, totalPlayTime: 0, mostPlayed: null as GameRecord | null })

onMounted(async () => {
  // 加载最近游戏
  recentGames.value = await GameBoxDBService.getRecentlyPlayed(5)

  // 获取统计
  stats.value = await GameBoxDBService.getGameStats()

  // 清理过期缓存
  await GameBoxDBService.cleanExpiredCache()
})

async function addGameToCache() {
  const game: GameRecord = {
    gameId: 'game-001',
    name: '我的世界',
    cover: '',  // 可以是 Blob
    rating: 4.9,
    lastPlayed: new Date(),
    playCount: 1
  }

  await GameBoxDBService.cacheGame(game)
  console.log('游戏已缓存')
}

async function updatePlayRecord() {
  await GameBoxDBService.updatePlayRecord('game-001')
  console.log('播放记录已更新')
}
</script>

<template>
  <div class="cache-demo">
    <h2>IndexedDB 缓存示例</h2>

    <div class="stats">
      <p>总游戏数: {{ stats.totalGames }}</p>
      <p>总游戏时长: {{ stats.totalPlayTime }} 次</p>
      <p v-if="stats.mostPlayed">最常玩: {{ stats.mostPlayed.name }}</p>
    </div>

    <div class="recent">
      <h3>最近玩过的游戏</h3>
      <ul>
        <li v-for="game in recentGames" :key="game.id">
          {{ game.name }} - {{ game.playCount }} 次
        </li>
      </ul>
    </div>

    <button @click="addGameToCache">添加游戏到缓存</button>
    <button @click="updatePlayRecord">更新播放记录</button>
  </div>
</template>

5. electron-store 轻量存储

5.1 electron-store 优势

特性说明
零配置开箱即用
自动持久化数据自动保存到磁盘
类型安全TypeScript 支持
加密支持可选密码加密
Schema 验证数据校验

5.2 基础配置

// src/main/store.ts
import Store from 'electron-store'

// 定义配置接口
interface StoreSchema {
  // 应用设置
  settings: {
    theme: 'light' | 'dark' | 'system'
    language: string
    autoLaunch: boolean
    minimizeToTray: boolean
  }

  // 游戏设置
  gameSettings: {
    defaultGamePath: string
    scanOnStartup: boolean
    autoUpdate: boolean
  }

  // 窗口状态
  windowState: {
    width: number
    height: number
    x: number
    y: number
    isMaximized: boolean
  }

  // 用户数据
  favorites: string[]
  recentGames: string[]

  // 版本信息
  version: string
}

const defaults: StoreSchema = {
  settings: {
    theme: 'dark',
    language: 'zh-CN',
    autoLaunch: false,
    minimizeToTray: true
  },
  gameSettings: {
    defaultGamePath: '',
    scanOnStartup: true,
    autoUpdate: true
  },
  windowState: {
    width: 1200,
    height: 800,
    x: -1,
    y: -1,
    isMaximized: false
  },
  favorites: [],
  recentGames: [],
  version: '1.0.0'
}

// 创建 Store 实例
export const store = new Store<StoreSchema>({
  name: 'gamebox-config',
  defaults,
  // 可选:加密存储
  // encryptionKey: 'your-secret-key',
  // 可选:Schema 验证
  // schema: {
  //   settings: {
  //     type: 'object',
  //     properties: {
  //       theme: { type: 'string', enum: ['light', 'dark', 'system'] }
  //     }
  //   }
  // }
})

// 便捷访问方法
export const storeHelper = {
  // 设置
  get settings() {
    return store.get('settings')
  },

  set settings(value: StoreSchema['settings']) {
    store.set('settings', value)
  },

  // 游戏设置
  get gameSettings() {
    return store.get('gameSettings')
  },

  set gameSettings(value: StoreSchema['gameSettings']) {
    store.set('gameSettings', value)
  },

  // 窗口状态
  get windowState() {
    return store.get('windowState')
  },

  set windowState(value: StoreSchema['windowState']) {
    store.set('windowState', value)
  },

  // 收藏夹
  get favorites() {
    return store.get('favorites')
  },

  addFavorite(gameId: string) {
    const favorites = store.get('favorites')
    if (!favorites.includes(gameId)) {
      store.set('favorites', [...favorites, gameId])
    }
  },

  removeFavorite(gameId: string) {
    const favorites = store.get('favorites')
    store.set('favorites', favorites.filter(id => id !== gameId))
  },

  toggleFavorite(gameId: string) {
    const favorites = store.get('favorites')
    if (favorites.includes(gameId)) {
      this.removeFavorite(gameId)
    } else {
      this.addFavorite(gameId)
    }
  },

  // 最近游戏
  get recentGames() {
    return store.get('recentGames')
  },

  addRecentGame(gameId: string) {
    const recent = store.get('recentGames').filter(id => id !== gameId)
    recent.unshift(gameId)
    // 只保留最近 10 个
    store.set('recentGames', recent.slice(0, 10))
  },

  clearRecentGames() {
    store.set('recentGames', [])
  },

  // 版本
  get version() {
    return store.get('version')
  },

  set version(value: string) {
    store.set('version', value)
  }
}

5.3 IPC 集成

// src/main/ipc-store.ts
import { ipcMain } from 'electron'
import { store, storeHelper } from './store'

export function setupStoreHandlers(): void {
  console.log('📡 设置 Store IPC 处理程序')

  // 获取设置
  ipcMain.handle('store-get-settings', () => {
    return storeHelper.settings
  })

  ipcMain.handle('store-set-settings', (_, settings) => {
    storeHelper.settings = settings
    return { success: true }
  })

  // 获取游戏设置
  ipcMain.handle('store-get-game-settings', () => {
    return storeHelper.gameSettings
  })

  ipcMain.handle('store-set-game-settings', (_, settings) => {
    storeHelper.gameSettings = settings
    return { success: true }
  })

  // 收藏夹
  ipcMain.handle('store-get-favorites', () => {
    return storeHelper.favorites
  })

  ipcMain.handle('store-add-favorite', (_, gameId: string) => {
    storeHelper.addFavorite(gameId)
    return { success: true }
  })

  ipcMain.handle('store-remove-favorite', (_, gameId: string) => {
    storeHelper.removeFavorite(gameId)
    return { success: true }
  })

  ipcMain.handle('store-toggle-favorite', (_, gameId: string) => {
    storeHelper.toggleFavorite(gameId)
    return { success: true }
  })

  // 最近游戏
  ipcMain.handle('store-get-recent-games', () => {
    return storeHelper.recentGames
  })

  ipcMain.handle('store-add-recent-game', (_, gameId: string) => {
    storeHelper.addRecentGame(gameId)
    return { success: true }
  })

  ipcMain.handle('store-clear-recent-games', () => {
    storeHelper.clearRecentGames()
    return { success: true }
  })

  // 窗口状态
  ipcMain.handle('store-get-window-state', () => {
    return storeHelper.windowState
  })

  ipcMain.handle('store-set-window-state', (_, state) => {
    storeHelper.windowState = state
    return { success: true }
  })

  // 直接获取/设置任意键
  ipcMain.handle('store-get', (_, key: string) => {
    return store.get(key)
  })

  ipcMain.handle('store-set', (_, key: string, value: any) => {
    store.set(key, value)
    return { success: true }
  })

  ipcMain.handle('store-delete', (_, key: string) => {
    store.delete(key)
    return { success: true }
  })

  ipcMain.handle('store-clear', () => {
    store.clear()
    return { success: true }
  })

  // 获取 Store 文件路径(调试用)
  ipcMain.handle('store-get-path', () => {
    return store.path
  })
}

5.4 渲染进程使用

// src/renderer/src/composables/useStore.ts
import { ref, onMounted } from 'vue'

// 类型定义
interface Settings {
  theme: 'light' | 'dark' | 'system'
  language: string
  autoLaunch: boolean
  minimizeToTray: boolean
}

interface GameSettings {
  defaultGamePath: string
  scanOnStartup: boolean
  autoUpdate: boolean
}

// Composable
export function useStore() {
  const settings = ref<Settings>({
    theme: 'dark',
    language: 'zh-CN',
    autoLaunch: false,
    minimizeToTray: true
  })

  const gameSettings = ref<GameSettings>({
    defaultGamePath: '',
    scanOnStartup: true,
    autoUpdate: true
  })

  const favorites = ref<string[]>([])
  const recentGames = ref<string[]>([])
  const loading = ref(true)

  // 加载数据
  async function loadAll() {
    loading.value = true
    try {
      settings.value = await window.electron.getSettings()
      gameSettings.value = await window.electron.getGameSettings()
      favorites.value = await window.electron.getFavorites()
      recentGames.value = await window.electron.getRecentGames()
    } finally {
      loading.value = false
    }
  }

  // 保存设置
  async function saveSettings(newSettings: Settings) {
    await window.electron.setSettings(newSettings)
    settings.value = newSettings
  }

  // 切换收藏
  async function toggleFavorite(gameId: string) {
    await window.electron.toggleFavorite(gameId)
    favorites.value = await window.electron.getFavorites()
  }

  // 添加最近游戏
  async function addRecentGame(gameId: string) {
    await window.electron.addRecentGame(gameId)
    recentGames.value = await window.electron.getRecentGames()
  }

  // 检查是否收藏
  function isFavorite(gameId: string): boolean {
    return favorites.value.includes(gameId)
  }

  // 初始化
  onMounted(() => {
    loadAll()
  })

  return {
    settings,
    gameSettings,
    favorites,
    recentGames,
    loading,
    loadAll,
    saveSettings,
    toggleFavorite,
    addRecentGame,
    isFavorite
  }
}
<!-- 使用示例 -->
<script setup lang="ts">
import { useStore } from '@renderer/composables/useStore'

const {
  settings,
  gameSettings,
  favorites,
  recentGames,
  loading,
  toggleFavorite,
  addRecentGame,
  isFavorite
} = useStore()

async function handleToggleFavorite(gameId: string) {
  await toggleFavorite(gameId)
}
</script>

6. 实战:GameBox 数据持久化

6.1 项目结构

gamebox-app/
├── src/
│   ├── main/
│   │   ├── index.ts
│   │   ├── ipc.ts
│   │   ├── database/
│   │   │   ├── index.ts          # 数据库入口
│   │   │   ├── sqlite.ts         # SQLite 操作
│   │   │   ├── migrations.ts     # 数据迁移
│   │   │   └── seed.ts           # 初始数据
│   │   └── store.ts              # electron-store
│   └── renderer/
│       └── src/
│           ├── database/
│           │   ├── indexeddb.ts   # IndexedDB
│           │   └── sync.ts       # 数据同步
│           ├── stores/
│           │   └── gameStore.ts  # Pinia Store
│           └── composables/
│               └── useStore.ts
├── data/                         # 外部数据目录
│   ├── games.json               # 游戏数据备份
│   └── exports/                  # 导出目录
└── package.json

6.2 完整数据层架构

// src/main/database/index.ts
import { database } from './sqlite'
import { store, storeHelper } from '../store'
import log from 'electron-log'

/**
 * 数据库服务入口
 */
export class DatabaseService {
  private static instance: DatabaseService

  private constructor() {}

  static getInstance(): DatabaseService {
    if (!DatabaseService.instance) {
      DatabaseService.instance = new DatabaseService()
    }
    return DatabaseService.instance
  }

  /**
   * 初始化所有数据服务
   */
  initialize(): void {
    log.info('📦 初始化数据服务...')

    // 初始化 SQLite
    database.initialize()

    // 初始化默认分类
    this.initializeDefaultCategories()

    // 检查并执行数据迁移
    this.checkMigrations()

    log.info('✅ 数据服务初始化完成')
  }

  /**
   * 初始化默认分类
   */
  private initializeDefaultCategories(): void {
    const categories = database.getAllCategories()
    if (categories.length === 0) {
      const defaultCategories = [
        { id: 'sandbox', name: '沙盒建造', icon: '🏗️', sort_order: 1 },
        { id: 'moba', name: 'MOBA', icon: '⚔️', sort_order: 2 },
        { id: 'rpg', name: '角色扮演', icon: '🎭', sort_order: 3 },
        { id: 'fps', name: '射击游戏', icon: '🔫', sort_order: 4 },
        { id: 'strategy', name: '策略游戏', icon: '♟️', sort_order: 5 },
        { id: 'simulation', name: '模拟经营', icon: '🏢', sort_order: 6 },
        { id: 'sports', name: '体育竞技', icon: '⚽', sort_order: 7 },
        { id: 'puzzle', name: '益智休闲', icon: '🧩', sort_order: 8 },
        { id: 'adventure', name: '冒险解谜', icon: '🗺️', sort_order: 9 },
        { id: 'action', name: '动作游戏', icon: '👊', sort_order: 10 }
      ]

      defaultCategories.forEach(cat => database.addCategory(cat as any))
      log.info('✅ 默认分类已创建')
    }
  }

  /**
   * 检查并执行数据迁移
   */
  private checkMigrations(): void {
    const currentVersion = store.get('dbVersion') || '0.0.0'
    const targetVersion = '1.0.0'

    if (currentVersion !== targetVersion) {
      log.info(`🔄 执行数据迁移: ${currentVersion} -> ${targetVersion}`)
      this.runMigrations(currentVersion, targetVersion)
      store.set('dbVersion', targetVersion)
    }
  }

  /**
   * 执行迁移
   */
  private runMigrations(from: string, to: string): void {
    // 迁移逻辑
    if (from === '0.0.0') {
      // 初始化迁移
      log.info('执行初始化迁移...')
    }

    // 可以添加更多迁移...
  }

  /**
   * 关闭所有数据服务
   */
  close(): void {
    database.close()
    log.info('✅ 数据服务已关闭')
  }

  /**
   * 导出数据
   */
  exportData(): object {
    return {
      games: database.getAllGames(),
      categories: database.getAllCategories(),
      settings: store.store,
      exportedAt: new Date().toISOString()
    }
  }

  /**
   * 导入数据
   */
  importData(data: any): void {
    if (data.games) {
      database.addGames(data.games)
    }
    if (data.categories) {
      data.categories.forEach((cat: any) => database.addCategory(cat))
    }
    if (data.settings) {
      Object.entries(data.settings).forEach(([key, value]) => {
        store.set(key, value)
      })
    }
  }

  /**
   * 备份数据到文件
   */
  backupToFile(filePath: string): void {
    const data = this.exportData()
    const fs = require('fs')
    fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
    log.info(`✅ 数据已备份到: ${filePath}`)
  }
}

// 导出单例
export const dbService = DatabaseService.getInstance()

6.3 渲染进程 Store

// src/renderer/src/stores/gameStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Game, Category } from '@shared/types'

export const useGameStore = defineStore('game', () => {
  // 状态
  const games = ref<Game[]>([])
  const categories = ref<Category[]>([])
  const loading = ref(false)
  const error = ref<string | null>(null)

  // 计算属性
  const totalGames = computed(() => games.value.length)
  const featuredGames = computed(() => games.value.filter(g => g.featured))
  const newGames = computed(() => games.value.filter(g => g.is_new))

  // Actions
  async function fetchGames() {
    loading.value = true
    error.value = null
    try {
      games.value = await window.electron.getGames()
    } catch (e) {
      error.value = (e as Error).message
    } finally {
      loading.value = false
    }
  }

  async function fetchCategories() {
    try {
      categories.value = await window.electron.getCategories()
    } catch (e) {
      error.value = (e as Error).message
    }
  }

  async function addGame(game: Game) {
    try {
      await window.electron.addGame(game)
      games.value.push(game)
    } catch (e) {
      error.value = (e as Error).message
      throw e
    }
  }

  async function updateGame(id: string, updates: Partial<Game>) {
    try {
      await window.electron.updateGame(id, updates)
      const index = games.value.findIndex(g => g.id === id)
      if (index > -1) {
        games.value[index] = { ...games.value[index], ...updates }
      }
    } catch (e) {
      error.value = (e as Error).message
      throw e
    }
  }

  async function deleteGame(id: string) {
    try {
      await window.electron.deleteGame(id)
      games.value = games.value.filter(g => g.id !== id)
    } catch (e) {
      error.value = (e as Error).message
      throw e
    }
  }

  function getGamesByCategory(categoryId: string) {
    return computed(() => 
      games.value.filter(g => g.category_id === categoryId)
    )
  }

  function searchGames(keyword: string) {
    const lower = keyword.toLowerCase()
    return computed(() =>
      games.value.filter(g => 
        g.name.toLowerCase().includes(lower) ||
        g.description.toLowerCase().includes(lower)
      )
    )
  }

  return {
    games,
    categories,
    loading,
    error,
    totalGames,
    featuredGames,
    newGames,
    fetchGames,
    fetchCategories,
    addGame,
    updateGame,
    deleteGame,
    getGamesByCategory,
    searchGames
  }
})

7. 数据库迁移与版本管理

7.1 迁移系统设计

// src/main/database/migrations.ts
import { database } from './sqlite'
import log from 'electron-log'

interface Migration {
  version: string
  name: string
  up: () => void
  down: () => void
}

const migrations: Migration[] = [
  {
    version: '1.0.1',
    name: 'add_play_time_index',
    up: () => {
      database.getDatabase().exec(`
        CREATE INDEX IF NOT EXISTS idx_games_play_time ON games(play_time DESC)
      `)
    },
    down: () => {
      database.getDatabase().exec(`
        DROP INDEX IF EXISTS idx_games_play_time
      `)
    }
  },
  {
    version: '1.0.2',
    name: 'add_game_favorites_table',
    up: () => {
      database.getDatabase().exec(`
        CREATE TABLE IF NOT EXISTS favorites (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          game_id TEXT NOT NULL UNIQUE,
          created_at TEXT DEFAULT (datetime('now')),
          FOREIGN KEY (game_id) REFERENCES games(id)
        )
      `)
    },
    down: () => {
      database.getDatabase().exec(`DROP TABLE IF EXISTS favorites`)
    }
  }
]

export class MigrationManager {
  private currentVersion: string = '0.0.0'

  /**
   * 获取当前数据库版本
   */
  getCurrentVersion(): string {
    try {
      const result = database.getDatabase().prepare(
        "SELECT value FROM settings WHERE key = 'db_version'"
      ).get() as { value: string } | undefined
      return result?.value || '0.0.0'
    } catch {
      return '0.0.0'
    }
  }

  /**
   * 设置数据库版本
   */
  setVersion(version: string): void {
    database.getDatabase().prepare(`
      INSERT OR REPLACE INTO settings (key, value)
      VALUES ('db_version', ?)
    `).run(version)
  }

  /**
   * 检查是否需要迁移
   */
  needsMigration(): boolean {
    this.currentVersion = this.getCurrentVersion()
    const latestVersion = migrations[migrations.length - 1]?.version || '0.0.0'
    return this.compareVersions(this.currentVersion, latestVersion) < 0
  }

  /**
   * 运行所有待执行迁移
   */
  runPendingMigrations(): void {
    const pending = migrations.filter(m => 
      this.compareVersions(m.version, this.currentVersion) > 0
    )

    if (pending.length === 0) {
      log.info('✅ 数据库已是最新版本')
      return
    }

    log.info(`📦 发现 ${pending.length} 个待执行迁移`)

    for (const migration of pending) {
      log.info(`🔄 执行迁移: ${migration.version} - ${migration.name}`)
      try {
        migration.up()
        this.setVersion(migration.version)
        log.info(`✅ 迁移完成: ${migration.version}`)
      } catch (error) {
        log.error(`❌ 迁移失败: ${migration.version}`, error)
        throw error
      }
    }

    log.info('✅ 所有迁移执行完成')
  }

  /**
   * 回滚到指定版本
   */
  rollbackTo(targetVersion: string): void {
    const toRollback = migrations.filter(m =>
      this.compareVersions(m.version, targetVersion) > 0
    ).reverse()

    for (const migration of toRollback) {
      log.info(`🔄 回滚迁移: ${migration.version}`)
      migration.down()
    }

    this.setVersion(targetVersion)
    log.info(`✅ 已回滚到版本: ${targetVersion}`)
  }

  /**
   * 比较版本号
   */
  private compareVersions(v1: string, v2: string): number {
    const parts1 = v1.split('.').map(Number)
    const parts2 = v2.split('.').map(Number)

    for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
      const p1 = parts1[i] || 0
      const p2 = parts2[i] || 0
      if (p1 < p2) return -1
      if (p1 > p2) return 1
    }
    return 0
  }
}

export const migrationManager = new MigrationManager()

7.2 数据备份与恢复

// src/main/database/backup.ts
import { database } from './sqlite'
import { store } from '../store'
import * as fs from 'fs'
import * as path from 'path'
import { app, dialog } from 'electron'
import log from 'electron-log'

export class BackupManager {
  private backupDir: string

  constructor() {
    this.backupDir = path.join(app.getPath('userData'), 'backups')
    this.ensureBackupDir()
  }

  private ensureBackupDir(): void {
    if (!fs.existsSync(this.backupDir)) {
      fs.mkdirSync(this.backupDir, { recursive: true })
    }
  }

  /**
   * 创建备份
   */
  async createBackup(name?: string): Promise<string> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
    const backupName = name || `gamebox-backup-${timestamp}`
    const backupPath = path.join(this.backupDir, `${backupName}.json`)

    const data = {
      version: store.get('version'),
      dbVersion: store.get('dbVersion'),
      games: database.getAllGames(),
      categories: database.getAllCategories(),
      settings: store.store,
      backupAt: new Date().toISOString()
    }

    fs.writeFileSync(backupPath, JSON.stringify(data, null, 2), 'utf-8')
    log.info(`✅ 备份已创建: ${backupPath}`)

    // 清理旧备份(保留最近 10 个)
    this.cleanOldBackups(10)

    return backupPath
  }

  /**
   * 恢复备份
   */
  async restoreBackup(backupPath: string): Promise<void> {
    if (!fs.existsSync(backupPath)) {
      throw new Error(`备份文件不存在: ${backupPath}`)
    }

    const data = JSON.parse(fs.readFileSync(backupPath, 'utf-8'))

    // 确认恢复
    const result = await dialog.showMessageBox({
      type: 'warning',
      title: '确认恢复',
      message: '确定要恢复此备份吗?当前数据将被覆盖。',
      buttons: ['取消', '确认恢复'],
      defaultId: 0,
      cancelId: 0
    })

    if (result.response === 1) {
      // 恢复数据
      if (data.games) {
        database.addGames(data.games)
      }
      if (data.categories) {
        data.categories.forEach((cat: any) => database.addCategory(cat))
      }

      log.info(`✅ 备份已恢复: ${backupPath}`)
    }
  }

  /**
   * 获取备份列表
   */
  getBackupList(): Array<{ name: string; path: string; date: Date; size: number }> {
    const files = fs.readdirSync(this.backupDir)
      .filter(f => f.endsWith('.json'))
      .map(name => {
        const filePath = path.join(this.backupDir, name)
        const stats = fs.statSync(filePath)
        const content = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
        return {
          name,
          path: filePath,
          date: new Date(content.backupAt),
          size: stats.size
        }
      })
      .sort((a, b) => b.date.getTime() - a.date.getTime())

    return files
  }

  /**
   * 清理旧备份
   */
  private cleanOldBackups(keep: number): void {
    const backups = this.getBackupList()
    if (backups.length > keep) {
      backups.slice(keep).forEach(backup => {
        fs.unlinkSync(backup.path)
        log.info(`🗑️ 删除旧备份: ${backup.name}`)
      })
    }
  }

  /**
   * 选择备份文件
   */
  async selectBackupFile(): Promise<string | null> {
    const result = await dialog.showOpenDialog({
      title: '选择备份文件',
      defaultPath: this.backupDir,
      filters: [{ name: 'JSON', extensions: ['json'] }],
      properties: ['openFile']
    })

    return result.canceled ? null : result.filePaths[0]
  }

  /**
   * 选择保存位置
   */
  async selectSaveLocation(): Promise<string | null> {
    const result = await dialog.showSaveDialog({
      title: '保存备份',
      defaultPath: `gamebox-backup-${Date.now()}.json`,
      filters: [{ name: 'JSON', extensions: ['json'] }]
    })

    return result.canceled ? null : result.filePath
  }

  /**
   * 导出到指定位置
   */
  async exportToFile(filePath: string): Promise<void> {
    const backupPath = await this.createBackup()
    fs.copyFileSync(backupPath, filePath)
    log.info(`✅ 已导出备份到: ${filePath}`)
  }
}

export const backupManager = new BackupManager()

8. 性能优化与最佳实践

8.1 SQLite 性能优化

// 性能优化配置
const performanceConfig = {
  // WAL 模式:提高并发读写性能
  pragma: {
    'journal_mode': 'WAL',
    'synchronous': 'NORMAL',
    'cache_size': -64000,  // 64MB 缓存
    'temp_store': 'MEMORY',
    'mmap_size': 268435456  // 256MB 内存映射
  }
}

// 应用优化配置
function applyPerformanceOptimizations(db: Database): void {
  Object.entries(performanceConfig.pragma).forEach(([key, value]) => {
    db.pragma(`${key} = ${value}`)
  })
}

// 批量操作优化
function batchInsertOptimized(db: Database, games: Game[]): void {
  // 使用事务
  const transaction = db.transaction((games: Game[]) => {
    const stmt = db.prepare(`
      INSERT OR REPLACE INTO games (id, name, path, ...)
      VALUES (?, ?, ...)
    `)

    for (const game of games) {
      stmt.run(game.id, game.name, game.path, ...)
    }
  })

  transaction(games)
}

8.2 IndexedDB 性能优化

// Dexie 性能优化
const db = new Dexie('GameBoxDB')

db.version(1).stores({
  games: '++id, gameId, name, rating, lastPlayed',
  // 只索引必要字段
})

// 批量操作
async function bulkInsertOptimized(games: GameRecord[]): Promise<void> {
  await db.transaction('rw', db.games, async () => {
    await db.games.bulkAdd(games)
  })
}

// 分页查询
async function getGamesPaginated(
  page: number, 
  pageSize: number
): Promise<GameRecord[]> {
  const offset = (page - 1) * pageSize
  return db.games
    .orderBy('rating')
    .reverse()
    .offset(offset)
    .limit(pageSize)
    .toArray()
}

8.3 electron-store 性能优化

import Store from 'electron-store'

// 禁用自动写入磁盘(手动控制)
const store = new Store({
  // ...
  watch: false  // 禁用监听
})

// 手动保存
function saveWithDebounce() {
  let timeout: NodeJS.Timeout

  return (key: string, value: any) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      store.set(key, value)
    }, 500)  // 防抖
  }
}

8.4 最佳实践清单

✅ 数据库操作:
- [ ] 使用 prepared statements 防止 SQL 注入
- [ ] 批量操作使用事务
- [ ] 合理创建索引
- [ ] 定期清理过期数据

✅ 错误处理:
- [ ] 捕获并记录数据库错误
- [ ] 实现数据回滚机制
- [ ] 提供用户友好的错误提示

✅ 性能优化:
- [ ] 启用 WAL 模式
- [ ] 使用连接池(多连接场景)
- [ ] 实现数据缓存
- [ ] 分页加载大量数据

✅ 数据安全:
- [ ] 敏感数据加密存储
- [ ] 定期备份
- [ ] 实现数据迁移机制

9. 排错指南

9.1 常见错误与解决方案

错误类型错误信息原因解决方案
模块加载失败Cannot find module 'better-sqlite3'native 模块问题npm rebuild + postinstall
数据库锁定database is locked多进程同时访问使用 WAL 模式
表不存在no such table迁移未执行检查初始化逻辑
数据类型错误SQLITE_MISMATCH类型不匹配检查字段类型
路径问题ENOENT: no such file路径错误使用 app.getPath('userData')
IndexedDB 错误QuotaExceededError存储空间满清理旧数据
事务失败transaction aborted死锁/冲突重试机制

9.2 better-sqlite3 打包问题

# 问题:打包后 better-sqlite3 无法加载
# 原因:native 模块未正确编译

# 解决方案1:使用 electron-rebuild
npm install @electron/rebuild --save-dev
npx @electron/rebuild

# 解决方案2:配置 postinstall
{
  "scripts": {
    "postinstall": "electron-builder install-app-deps"
  }
}

# 解决方案3:使用 electron-rebuild 钩子
{
  "build": {
    "afterPack": "./scripts/afterPack.js"
  }
}
// scripts/afterPack.js
const { execSync } = require('child_process')

exports.default = async function afterPack(context) {
  const { electronPlatformName, appOutDir } = context

  const platform = {
    darwin: 'mac',
    win32: 'win',
    linux: 'linux'
  }[electronPlatformName]

  console.log(`Rebuilding native modules for ${platform}...`)

  execSync('npm rebuild better-sqlite3', {
    cwd: appOutDir,
    stdio: 'inherit'
  })
}

9.3 数据库锁定问题

// 问题:database is locked
// 原因:多个进程同时写入

// 解决方案1:使用 WAL 模式
db.pragma('journal_mode = WAL')

// 解决方案2:设置超时
db.pragma('busy_timeout = 5000')

// 解决方案3:确保关闭连接
app.on('before-quit', () => {
  database.close()
})

9.4 数据迁移问题

// 问题:迁移后数据不一致
// 解决方案:完整的迁移回滚机制

interface Migration {
  version: string
  name: string
  up: () => void
  down: () => void
  validate?: () => boolean
}

// 迁移前验证
async function safeMigrate(migration: Migration): Promise<boolean> {
  try {
    // 备份当前状态
    const backup = database.getAllGames()

    // 执行迁移
    migration.up()

    // 验证结果
    if (migration.validate && !migration.validate()) {
      // 回滚
      migration.down()
      database.addGames(backup)
      return false
    }

    return true
  } catch (error) {
    // 回滚
    migration.down()
    throw error
  }
}

9.5 调试技巧

// 开启 SQL 日志
db.pragma('sql_log = ON')
db.pragma('query_only = OFF')

// 导出数据库结构
function exportSchema(): string {
  return database.getDatabase()
    .prepare("SELECT sql FROM sqlite_master WHERE type='table'")
    .all()
    .map((row: any) => row.sql)
    .join('\n')
}

// 调试查询
function debugQuery(sql: string, params: any[] = []): void {
  console.log('SQL:', sql)
  console.log('Params:', params)

  const stmt = database.getDatabase().prepare(sql)
  const result = stmt.all(...params)
  console.log('Result:', result)

  return result
}

📚 总结

核心知识点回顾

模块核心技能
数据库选型electron-store / SQLite / IndexedDB 适用场景
SQLitebetter-sqlite3、CRUD、事务、索引
IndexedDBDexie.js、缓存、离线存储
electron-store配置管理、类型安全、自动持久化
数据迁移版本管理、迁移脚本、回滚机制
性能优化WAL 模式、批量操作、索引优化
备份恢复定时备份、数据导出、恢复机制

延伸学习方向

  1. TypeORM – 更强大的 ORM 框架
  2. Redis – 内存数据库,高速缓存
  3. Realm – 跨平台移动数据库
  4. WatermelonDB – React Native 专用数据库
  5. Prisma – 现代化 ORM,支持 SQLite
© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容