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();最佳实践
数据安全:
- 使用加密存储敏感数据
- 主进程验证所有输入数据
- 使用预加载脚本隔离上下文
性能优化:
- 批量处理数据库操作
- 使用分页加载大数据集
- 缓存常用查询结果
错误处理:
- 统一错误处理中间件
- 用户友好的错误消息
- 详细的日志记录
数据迁移:
- 使用迁移脚本管理数据库变更
- 支持版本回滚
- 备份重要数据
测试策略:
- 单元测试数据库服务
- 集成测试IPC通信
- E2E测试用户流程
总结
本架构设计提供了一个完整的 Electron 数据库操作解决方案,关键优势包括:
- 分层架构:清晰的职责分离(渲染进程/主进程/数据库)
- 安全设计:数据加密、输入验证、安全通信
- 可扩展性:模块化设计便于功能扩展
- 数据同步:本地优先策略与云端同步
- 错误恢复:事务支持与数据备份机制
此架构适用于需要本地数据存储的 Electron 应用,如笔记应用、数据管理工具、离线应用等。实际实施时,应根据具体业务需求调整数据模型和同步策略。