安装
pnpm install better-sqlite3 --save
配置
开发环境
在根目录下创建个文件夹用来存放生成的sql文件,这个sql文件加到gitignore中,不要把sql文件放到out目录里,out是开发输出,每次重启会清空。
生产环境
生产环境写到用户目录下(这样每次更新历史数据不会清空,同时会引入一个新的问题,当数据表和结构变化时需要做迁移操作)
app.getPath('userData');
不同操作系统下的路径
mac: ~/Library/ApplicationSupport/应用名称/
linux:~/.config/应用名称
windows: C:\Users\用户\AppData\Roaming\应用名称
打包的时候不要把本地的数据库文件和其他开发环境下的文件打到包里
使用
在主进程中
- 初始化文件目录
function createDatabaseDir(dirPath: string) {
// Check if the directory exists, if not, create it
if (!existsSync(dirPath)) {
mkdirSync(dirPath, { recursive: true });
}
}
- 初始化表结构
function initDatabase(db: Database.Database, schemaPath: string) {
// 此处从schema.sql文件中初始化,也可以给字符串初始化
const schema = readFileSync(schemaPath, 'utf8');
db.exec(schema);
logger.info('Database does not exist, creating...');
return db;
}
- 细分数据操作
例如 用户的数据操作,可以给个userservice文件用来处理用户表的操作
import Database from 'better-sqlite3';
export class UserService {
private db: Database.Database;
constructor(db: Database.Database) {
this.db = db;
}
public getUsers() {
const stmt = this.db.prepare('SELECT * FROM users WHERE is_deleted = 0');
return stmt.all();
}
public addUser(name: string, email: string) {
const stmt = this.db.prepare(`
INSERT INTO users (name, email, created_at, updated_at)
VALUES (?, ?, datetime('now'), datetime('now'))
`);
stmt.run(name, email);
}
public updateUser(id: string, newName: string, newEmail: string) {
const stmt = this.db.prepare(`
UPDATE users
SET name = ?, email = ?, updated_at = datetime('now')
WHERE id = ?
`);
stmt.run(newName, newEmail, id);
}
public deleteUserById(id: string) {
const stmt = this.db.prepare(`
UPDATE users
SET is_deleted = 1, updated_at = datetime('now')
WHERE id = ?
`);
stmt.run(id);
}
}
- 创建IPC通讯供renderer进程使用
4.1 创建个userhandle.ts文件并在main/index.ts中初始化handle
import { ipcMain } from 'electron';
import databaseService from '..';
export function setupUserIpcHandlers() {
// IPC 处理
ipcMain.handle('get-users', async () => {
return databaseService.userService.getUsers();
});
ipcMain.handle('add-user', async (_, name: string, email: string) => {
databaseService.userService.addUser(name, email);
});
ipcMain.handle('update-user', async (_, id: string, newName: string, newEmail: string) => {
databaseService.userService.updateUser(id, newName, newEmail);
});
ipcMain.handle('delete-user-by-id', async (_, id: string) => {
databaseService.userService.deleteUserById(id);
});
}
app.whenReady().then(() => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron');
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
app.on('browser-window-created', (_, window) => {
optimizer.watchWindowShortcuts(window);
});
createWindow();
app.on('activate', function () {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
setupLoggerIpcHandlers();// 初始化日志IPC
setupDatabaseIpcHandlers();//初始化dbIPC
});
4.2 在preload中创建invoke
创建userinvoke.ts
import { ipcRenderer } from 'electron';
export const databasePreload = {
getUsers: () => ipcRenderer.invoke('get-users'),
addUser: (name: string, email: string) => ipcRenderer.invoke('add-user', name, email),
deleteUserById: (id: string) => ipcRenderer.invoke('delete-user-by-id', id),
updateUser: (id: string, name: string, email: string) => ipcRenderer.invoke('update-user', id, name, email)
};
4.3 在preload/index.ts引入
import { contextBridge } from 'electron';
import { electronAPI } from '@electron-toolkit/preload';
import { databasePreload } from './database';
import { loggerPreload } from './logger';
// Custom APIs for renderer
const api = {
...loggerPreload,
...databasePreload
};
// Use `contextBridge` APIs to expose Electron APIs to
// renderer only if context isolation is enabled, otherwise
// just add to the DOM global.
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('api', api);
} catch (error) {
console.error(2, error);
}
} else {
// @ts-ignore (define in dts)
window.electron = electronAPI;
// @ts-ignore (define in dts)
window.api = api;
}
在renderer进程中使用
通过window.api的方式调用preload中定义的invoke方法
try {
await window.api.addUser(newUserName.value, newUserEmail.value);
newUserName.value = '';
newUserEmail.value = '';
fetchUsers();
} catch (error) {
window.api.logError(error);
}
其他
- 前文提到的数据库表结构变化涉及到客户端侧的数据库需要做迁移,目前想到的是用knex自动化迁移但是暂时没成功。
- 数据的备份也要加。
- 其他主进程中的操作要供renderer操作的方法都可以用上述思路。