Skip to content

Electron开发手册

Electron 数据库操作与数据持久化架构设计

作为前端架构师,我将设计一个完整的 Electron 数据库操作架构,确保数据安全、高效和可维护。以下是完整的架构设计方案:

整体架构设计

技术选型

  • 本地数据库:SQLite(关系型) + Dexie.js(IndexedDB封装)
  • 状态管理:MobX 或 Zustand
  • 数据加密:Node.js crypto 模块
  • 数据同步:PouchDB + CouchDB
  • ORM:TypeORM 或 Sequelize

文件结构设计

src/
├── main/                  # 主进程代码
│   ├── database/          # 数据库核心
│   │   ├── models/        # 数据模型
│   │   ├── services/      # 数据库服务
│   │   ├── migrations/    # 数据库迁移
│   │   └── index.ts       # 数据库初始化
│   ├── ipc/              # IPC通信处理
│   ├── security/         # 安全模块
│   └── main.ts           # 主进程入口

├── renderer/             # 渲染进程代码
│   ├── stores/           # 状态管理
│   ├── services/         # API服务
│   ├── components/       # UI组件
│   └── App.tsx           # 主应用组件

├── preload/              # 预加载脚本
│   └── index.ts          

├── resources/            # 静态资源
└── types/                # 类型定义

数据库服务层设计

1. 数据库初始化 (main/database/index.ts)

typescript
import { app } from 'electron';
import path from 'path';
import { Sequelize } from 'sequelize';

class Database {
  private sequelize: Sequelize;
  
  constructor() {
    const dbPath = path.join(app.getPath('userData'), 'app-database.sqlite');
    
    this.sequelize = new Sequelize({
      dialect: 'sqlite',
      storage: dbPath,
      logging: process.env.NODE_ENV === 'development',
    });
  }

  async init() {
    try {
      await this.sequelize.authenticate();
      console.log('Database connection established');
      
      // 运行数据库迁移
      await this.runMigrations();
      
      // 初始化模型
      this.initializeModels();
      
      return this.sequelize;
    } catch (error) {
      console.error('Database initialization failed:', error);
      throw error;
    }
  }

  private async runMigrations() {
    // 数据库迁移逻辑
  }

  private initializeModels() {
    // 注册所有数据模型
  }

  get instance() {
    return this.sequelize;
  }
}

export default new Database();

2. 数据模型设计 (main/database/models/User.ts)

typescript
import { DataTypes, Model } from 'sequelize';
import db from '../index';

class User extends Model {
  public id!: number;
  public username!: string;
  public email!: string;
  public passwordHash!: string;
  public createdAt!: Date;
  public updatedAt!: Date;
}

User.init(
  {
    id: {
      type: DataTypes.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    username: {
      type: DataTypes.STRING(50),
      allowNull: false,
      unique: true,
    },
    email: {
      type: DataTypes.STRING(100),
      allowNull: false,
      unique: true,
      validate: {
        isEmail: true,
      },
    },
    passwordHash: {
      type: DataTypes.STRING(128),
      allowNull: false,
    },
  },
  {
    sequelize: db.instance,
    tableName: 'users',
    timestamps: true,
    indexes: [
      {
        unique: true,
        fields: ['username'],
      },
      {
        unique: true,
        fields: ['email'],
      },
    ],
  }
);

export default User;

3. 数据库服务层 (main/database/services/UserService.ts)

typescript
import User from '../models/User';
import { hashPassword } from '../../security/crypto';
import { IpcMainInvokeEvent } from 'electron';

interface CreateUserParams {
  username: string;
  email: string;
  password: string;
}

interface UpdateUserParams {
  id: number;
  username?: string;
  email?: string;
  password?: string;
}

class UserService {
  async createUser(event: IpcMainInvokeEvent, params: CreateUserParams) {
    try {
      const passwordHash = await hashPassword(params.password);
      
      const user = await User.create({
        username: params.username,
        email: params.email,
        passwordHash,
      });
      
      return {
        success: true,
        data: user.toJSON(),
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  async updateUser(event: IpcMainInvokeEvent, params: UpdateUserParams) {
    try {
      const user = await User.findByPk(params.id);
      
      if (!user) {
        throw new Error('User not found');
      }
      
      if (params.username) user.username = params.username;
      if (params.email) user.email = params.email;
      
      if (params.password) {
        user.passwordHash = await hashPassword(params.password);
      }
      
      await user.save();
      
      return {
        success: true,
        data: user.toJSON(),
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  async getUserById(event: IpcMainInvokeEvent, id: number) {
    try {
      const user = await User.findByPk(id, {
        attributes: { exclude: ['passwordHash'] },
      });
      
      if (!user) {
        throw new Error('User not found');
      }
      
      return {
        success: true,
        data: user.toJSON(),
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }

  async listUsers(event: IpcMainInvokeEvent, page = 1, pageSize = 20) {
    try {
      const offset = (page - 1) * pageSize;
      
      const { count, rows } = await User.findAndCountAll({
        attributes: { exclude: ['passwordHash'] },
        offset,
        limit: pageSize,
        order: [['createdAt', 'DESC']],
      });
      
      return {
        success: true,
        data: {
          total: count,
          page,
          pageSize,
          users: rows.map(u => u.toJSON()),
        },
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
      };
    }
  }
}

export default new UserService();

IPC 通信架构

1. IPC 服务端 (main/ipc/index.ts)

typescript
import { ipcMain } from 'electron';
import userService from '../database/services/UserService';

function setupDatabaseIPC() {
  // 用户管理
  ipcMain.handle('db:createUser', userService.createUser);
  ipcMain.handle('db:updateUser', userService.updateUser);
  ipcMain.handle('db:getUserById', userService.getUserById);
  ipcMain.handle('db:listUsers', userService.listUsers);
  
  // 其他数据库操作...
}

export function initializeIPC() {
  setupDatabaseIPC();
  // 其他IPC设置...
}

2. 预加载脚本 (preload/index.ts)

typescript
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('electronAPI', {
  // 数据库操作
  createUser: (params) => ipcRenderer.invoke('db:createUser', params),
  updateUser: (params) => ipcRenderer.invoke('db:updateUser', params),
  getUserById: (id) => ipcRenderer.invoke('db:getUserById', id),
  listUsers: (page, pageSize) => ipcRenderer.invoke('db:listUsers', page, pageSize),
  
  // 其他API...
});

3. 渲染进程服务封装 (renderer/services/api.ts)

typescript
class DatabaseAPI {
  async createUser(userData) {
    try {
      const result = await window.electronAPI.createUser(userData);
      if (!result.success) throw new Error(result.error);
      return result.data;
    } catch (error) {
      console.error('Create user failed:', error);
      throw error;
    }
  }

  async updateUser(userData) {
    try {
      const result = await window.electronAPI.updateUser(userData);
      if (!result.success) throw new Error(result.error);
      return result.data;
    } catch (error) {
      console.error('Update user failed:', error);
      throw error;
    }
  }

  async getUserById(id) {
    try {
      const result = await window.electronAPI.getUserById(id);
      if (!result.success) throw new Error(result.error);
      return result.data;
    } catch (error) {
      console.error('Get user failed:', error);
      throw error;
    }
  }

  async listUsers(page = 1, pageSize = 20) {
    try {
      const result = await window.electronAPI.listUsers(page, pageSize);
      if (!result.success) throw new Error(result.error);
      return result.data;
    } catch (error) {
      console.error('List users failed:', error);
      throw error;
    }
  }
}

export const dbAPI = new DatabaseAPI();

安全模块设计

1. 数据加密 (main/security/crypto.ts)

typescript
import crypto from 'crypto';
import { app } from 'electron';
import path from 'path';
import fs from 'fs';

const ALGORITHM = 'aes-256-cbc';
const IV_LENGTH = 16;

// 安全获取加密密钥
function getEncryptionKey(): Buffer {
  const keyPath = path.join(app.getPath('userData'), 'secret.key');
  
  if (fs.existsSync(keyPath)) {
    return fs.readFileSync(keyPath);
  }
  
  const key = crypto.randomBytes(32);
  fs.writeFileSync(keyPath, key);
  return key;
}

export function encrypt(text: string): string {
  const iv = crypto.randomBytes(IV_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, getEncryptionKey(), iv);
  let encrypted = cipher.update(text, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  return `${iv.toString('hex')}:${encrypted}`;
}

export function decrypt(text: string): string {
  const [ivHex, encryptedText] = text.split(':');
  const iv = Buffer.from(ivHex, 'hex');
  const decipher = crypto.createDecipheriv(ALGORITHM, getEncryptionKey(), iv);
  let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
}

export async function hashPassword(password: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const salt = crypto.randomBytes(16).toString('hex');
    
    crypto.scrypt(password, salt, 64, (err, derivedKey) => {
      if (err) reject(err);
      resolve(`${salt}:${derivedKey.toString('hex')}`);
    });
  });
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return new Promise((resolve, reject) => {
    const [salt, key] = hash.split(':');
    crypto.scrypt(password, salt, 64, (err, derivedKey) => {
      if (err) reject(err);
      resolve(key === derivedKey.toString('hex'));
    });
  });
}

状态管理集成 (渲染进程)

用户状态管理 (renderer/stores/userStore.ts)

typescript
import { makeAutoObservable } from 'mobx';
import { dbAPI } from '../services/api';

class UserStore {
  users = [];
  currentUser = null;
  loading = false;
  error = null;
  
  constructor() {
    makeAutoObservable(this);
  }
  
  async loadUsers(page = 1, pageSize = 20) {
    this.loading = true;
    this.error = null;
    
    try {
      const result = await dbAPI.listUsers(page, pageSize);
      this.users = result.users;
    } catch (error) {
      this.error = error.message;
    } finally {
      this.loading = false;
    }
  }
  
  async createUser(userData) {
    this.loading = true;
    this.error = null;
    
    try {
      const newUser = await dbAPI.createUser(userData);
      this.users = [newUser, ...this.users];
      return newUser;
    } catch (error) {
      this.error = error.message;
      throw error;
    } finally {
      this.loading = false;
    }
  }
  
  async updateUser(userData) {
    this.loading = true;
    this.error = null;
    
    try {
      const updatedUser = await dbAPI.updateUser(userData);
      this.users = this.users.map(u => 
        u.id === updatedUser.id ? updatedUser : u
      );
      return updatedUser;
    } catch (error) {
      this.error = error.message;
      throw error;
    } finally {
      this.loading = false;
    }
  }
  
  async login(username, password) {
    this.loading = true;
    this.error = null;
    
    try {
      // 实际项目中会有专门的认证服务
      const user = await dbAPI.getUserByUsername(username);
      const isValid = await verifyPassword(password, user.passwordHash);
      
      if (!isValid) {
        throw new Error('Invalid credentials');
      }
      
      this.currentUser = user;
      return user;
    } catch (error) {
      this.error = error.message;
      throw error;
    } finally {
      this.loading = false;
    }
  }
  
  logout() {
    this.currentUser = null;
  }
}

export default new UserStore();

数据流架构图

数据同步机制

1. 本地-云端同步设计

2. 同步服务实现 (main/database/syncService.ts)

typescript
import PouchDB from 'pouchdb';
import PouchDBFind from 'pouchdb-find';
import { remoteDBUrl } from '../../config';

PouchDB.plugin(PouchDBFind);

class SyncService {
  private localDB: PouchDB.Database;
  private remoteDB: PouchDB.Database;
  
  constructor() {
    this.localDB = new PouchDB('local-sync-db');
    this.remoteDB = new PouchDB(remoteDBUrl);
  }
  
  async startSync() {
    try {
      // 初始同步
      await this.initialSync();
      
      // 设置实时同步
      this.localDB.sync(this.remoteDB, {
        live: true,
        retry: true,
      })
      .on('change', (info) => {
        console.log('Sync change:', info);
      })
      .on('error', (err) => {
        console.error('Sync error:', err);
      });
    } catch (error) {
      console.error('Sync initialization failed:', error);
    }
  }
  
  private async initialSync() {
    // 获取最后同步时间
    const lastSync = await this.getLastSync();
    
    // 执行初始同步
    await this.localDB.replicate.from(this.remoteDB, {
      since: lastSync,
      batch_size: 100,
      batches_limit: 5,
    });
    
    // 更新最后同步时间
    await this.setLastSync();
  }
  
  private async getLastSync(): Promise<string> {
    // 从本地存储获取最后同步时间
    // 实现省略...
    return '0';
  }
  
  private async setLastSync() {
    // 保存当前同步时间
    // 实现省略...
  }
}

export default new SyncService();

最佳实践

  1. 数据安全

    • 使用加密存储敏感数据
    • 主进程验证所有输入数据
    • 使用预加载脚本隔离上下文
  2. 性能优化

    • 批量处理数据库操作
    • 使用分页加载大数据集
    • 缓存常用查询结果
  3. 错误处理

    • 统一错误处理中间件
    • 用户友好的错误消息
    • 详细的日志记录
  4. 数据迁移

    • 使用迁移脚本管理数据库变更
    • 支持版本回滚
    • 备份重要数据
  5. 测试策略

    • 单元测试数据库服务
    • 集成测试IPC通信
    • E2E测试用户流程

总结

本架构设计提供了一个完整的 Electron 数据库操作解决方案,关键优势包括:

  1. 分层架构:清晰的职责分离(渲染进程/主进程/数据库)
  2. 安全设计:数据加密、输入验证、安全通信
  3. 可扩展性:模块化设计便于功能扩展
  4. 数据同步:本地优先策略与云端同步
  5. 错误恢复:事务支持与数据备份机制

此架构适用于需要本地数据存储的 Electron 应用,如笔记应用、数据管理工具、离线应用等。实际实施时,应根据具体业务需求调整数据模型和同步策略。