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-store | Key-Value | < 1MB | ⭐ | 配置、偏好设置 | 极快 |
| LocalStorage | Key-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 / NeDB | JSON 友好 |
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 适用场景 |
| SQLite | better-sqlite3、CRUD、事务、索引 |
| IndexedDB | Dexie.js、缓存、离线存储 |
| electron-store | 配置管理、类型安全、自动持久化 |
| 数据迁移 | 版本管理、迁移脚本、回滚机制 |
| 性能优化 | WAL 模式、批量操作、索引优化 |
| 备份恢复 | 定时备份、数据导出、恢复机制 |
延伸学习方向
- TypeORM – 更强大的 ORM 框架
- Redis – 内存数据库,高速缓存
- Realm – 跨平台移动数据库
- WatermelonDB – React Native 专用数据库
- Prisma – 现代化 ORM,支持 SQLite
暂无评论内容