乐闻世界logo
搜索文章和话题

面试题手册

Electron 菜单和托盘的实现

Electron 提供了强大的菜单和系统托盘功能,可以让应用更好地集成到操作系统中。本文将详细介绍如何在 Electron 中实现菜单和托盘功能。应用菜单1. 创建应用菜单// main.jsconst { app, Menu, BrowserWindow } = require('electron')let mainWindowapp.whenReady().then(() => { createWindow() createMenu()})function createMenu() { const template = [ { label: 'File', submenu: [ { label: 'New', accelerator: 'CmdOrCtrl+N', click: () => { console.log('New file') } }, { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => { console.log('Open file') } }, { type: 'separator' }, { label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => { console.log('Save file') } }, { type: 'separator' }, { label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: () => app.quit() } ] }, { label: 'Edit', submenu: [ { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: 'Redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' } ] }, { label: 'View', submenu: [ { label: 'Reload', accelerator: 'CmdOrCtrl+R', role: 'reload' }, { label: 'Toggle Developer Tools', accelerator: 'CmdOrCtrl+Shift+I', role: 'toggleDevTools' }, { type: 'separator' }, { label: 'Actual Size', accelerator: 'CmdOrCtrl+0', role: 'resetZoom' }, { label: 'Zoom In', accelerator: 'CmdOrCtrl+Plus', role: 'zoomIn' }, { label: 'Zoom Out', accelerator: 'CmdOrCtrl+-', role: 'zoomOut' }, { type: 'separator' }, { label: 'Toggle Full Screen', accelerator: 'F11', role: 'togglefullscreen' } ] }, { label: 'Window', submenu: [ { label: 'Minimize', accelerator: 'CmdOrCtrl+M', role: 'minimize' }, { label: 'Close', accelerator: 'CmdOrCtrl+W', role: 'close' } ] }, { label: 'Help', submenu: [ { label: 'About', click: () => { console.log('About') } }, { label: 'Documentation', click: () => { shell.openExternal('https://electronjs.org/docs') } } ] } ] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)}2. 动态菜单// main.jslet recentFiles = []function updateRecentFilesMenu() { const recentMenu = recentFiles.map(file => ({ label: file, click: () => { openFile(file) } })) const template = [ { label: 'File', submenu: [ { label: 'New', accelerator: 'CmdOrCtrl+N', click: () => newFile() }, { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => openFile() }, { type: 'separator' }, { label: 'Open Recent', submenu: recentMenu.length > 0 ? recentMenu : [{ label: 'No recent files', enabled: false }] }, { type: 'separator' }, { label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: () => app.quit() } ] } ] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)}function addRecentFile(filePath) { recentFiles = recentFiles.filter(f => f !== filePath) recentFiles.unshift(filePath) recentFiles = recentFiles.slice(0, 10) // 最多保留10个 updateRecentFilesMenu()}3. 上下文菜单// main.jsconst { contextMenu } = require('electron')app.whenReady().then(() => { // 启用上下文菜单 contextMenu({ prepend: (params, browserWindow) => [ { label: 'Custom Action', click: () => { console.log('Custom action clicked') } } ] })})// 或者手动创建上下文菜单function createContextMenu() { const template = [ { label: 'Copy', accelerator: 'CmdOrCtrl+C', click: () => { const win = BrowserWindow.getFocusedWindow() win.webContents.copy() } }, { label: 'Paste', accelerator: 'CmdOrCtrl+V', click: () => { const win = BrowserWindow.getFocusedWindow() win.webContents.paste() } }, { type: 'separator' }, { label: 'Inspect Element', click: () => { const win = BrowserWindow.getFocusedWindow() win.webContents.inspectElement() } } ] const menu = Menu.buildFromTemplate(template) return menu}// 在渲染进程中使用// renderer.jsconst { remote } = require('electron')document.addEventListener('contextmenu', (event) => { event.preventDefault() const menu = remote.getGlobal('contextMenu') menu.popup({ window: remote.getCurrentWindow() })})系统托盘1. 创建托盘图标// main.jsconst { app, Tray, Menu, nativeImage } = require('electron')const path = require('path')let tray = nullapp.whenReady().then(() => { createTray()})function createTray() { // 创建托盘图标 const iconPath = path.join(__dirname, 'icon.png') const icon = nativeImage.createFromPath(iconPath) // 设置图标大小 tray = new Tray(icon.resize({ width: 16, height: 16 })) // 设置工具提示 tray.setToolTip('My Application') // 创建托盘菜单 const contextMenu = Menu.buildFromTemplate([ { label: 'Show App', click: () => { showWindow() } }, { label: 'Hide App', click: () => { hideWindow() } }, { type: 'separator' }, { label: 'Settings', click: () => { openSettings() } }, { type: 'separator' }, { label: 'Quit', click: () => { app.quit() } } ]) tray.setContextMenu(contextMenu) // 双击托盘图标 tray.on('double-click', () => { showWindow() }) // 单击托盘图标 tray.on('click', () => { toggleWindow() })}2. 托盘图标动画// main.jslet trayAnimationInterval = nullfunction startTrayAnimation() { const icons = [ path.join(__dirname, 'icon1.png'), path.join(__dirname, 'icon2.png'), path.join(__dirname, 'icon3.png') ] let currentIndex = 0 trayAnimationInterval = setInterval(() => { const icon = nativeImage.createFromPath(icons[currentIndex]) tray.setImage(icon.resize({ width: 16, height: 16 })) currentIndex = (currentIndex + 1) % icons.length }, 500)}function stopTrayAnimation() { if (trayAnimationInterval) { clearInterval(trayAnimationInterval) trayAnimationInterval = null } // 恢复默认图标 const defaultIcon = nativeImage.createFromPath(path.join(__dirname, 'icon.png')) tray.setImage(defaultIcon.resize({ width: 16, height: 16 }))}3. 托盘通知// main.jsconst { Notification } = require('electron')function showTrayNotification(title, body) { if (Notification.isSupported()) { const notification = new Notification({ title, body, icon: path.join(__dirname, 'icon.png'), silent: false }) notification.on('click', () => { showWindow() }) notification.show() }}// 使用示例ipcMain.on('show-notification', (event, { title, body }) => { showTrayNotification(title, body)})窗口控制1. 最小化到托盘// main.jslet mainWindowfunction createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600 }) mainWindow.on('minimize', (event) => { event.preventDefault() mainWindow.hide() }) mainWindow.on('close', (event) => { if (!app.isQuitting) { event.preventDefault() mainWindow.hide() } })}function showWindow() { if (mainWindow) { mainWindow.show() mainWindow.focus() }}function hideWindow() { if (mainWindow) { mainWindow.hide() }}function toggleWindow() { if (mainWindow.isVisible()) { hideWindow() } else { showWindow() }}app.on('before-quit', () => { app.isQuitting = true})2. 托盘菜单动态更新// main.jsfunction updateTrayMenu() { const isPlaying = getPlaybackState() const template = [ { label: isPlaying ? 'Pause' : 'Play', click: () => { togglePlayback() updateTrayMenu() } }, { label: 'Next Track', click: () => { nextTrack() } }, { label: 'Previous Track', click: () => { previousTrack() } }, { type: 'separator' }, { label: 'Show App', click: () => { showWindow() } }, { label: 'Quit', click: () => { app.quit() } } ] const contextMenu = Menu.buildFromTemplate(template) tray.setContextMenu(contextMenu)}跨平台兼容1. 平台特定菜单// main.jsfunction createMenu() { const template = [ { label: 'File', submenu: [ { label: 'New', accelerator: 'CmdOrCtrl+N', click: () => newFile() }, { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => openFile() }, { type: 'separator' }, { label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => saveFile() } ] } ] // macOS 特定菜单 if (process.platform === 'darwin') { template.unshift({ label: app.getName(), submenu: [ { label: 'About ' + app.getName(), role: 'about' }, { type: 'separator' }, { label: 'Preferences', accelerator: 'Cmd+,', click: () => openPreferences() }, { type: 'separator' }, { label: 'Services', role: 'services', submenu: [] }, { type: 'separator' }, { label: 'Hide ' + app.getName(), accelerator: 'Command+H', role: 'hide' }, { label: 'Hide Others', accelerator: 'Command+Shift+H', role: 'hideothers' }, { label: 'Show All', role: 'unhide' }, { type: 'separator' }, { label: 'Quit', accelerator: 'Command+Q', click: () => app.quit() } ] }) } const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)}2. 平台特定快捷键// main.jsfunction getAccelerator(action) { const accelerators = { 'new': 'CmdOrCtrl+N', 'open': 'CmdOrCtrl+O', 'save': 'CmdOrCtrl+S', 'quit': process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+Q', 'preferences': process.platform === 'darwin' ? 'Cmd+,' : 'Ctrl+,' } return accelerators[action] || ''}3. 托盘图标适配// main.jsfunction createTray() { let iconPath if (process.platform === 'darwin') { // macOS 使用模板图标 iconPath = path.join(__dirname, 'iconTemplate.png') } else if (process.platform === 'win32') { // Windows 使用 ICO 格式 iconPath = path.join(__dirname, 'icon.ico') } else { // Linux 使用 PNG 格式 iconPath = path.join(__dirname, 'icon.png') } const icon = nativeImage.createFromPath(iconPath) // macOS 需要设置模板属性 if (process.platform === 'darwin') { icon.setTemplateImage(true) } tray = new Tray(icon) tray.setToolTip('My Application')}最佳实践1. 菜单组织// main.js// 遵循平台约定function createMenu() { const template = [ // macOS: 应用名称菜单 // Windows/Linux: 文件菜单 { label: process.platform === 'darwin' ? app.getName() : 'File', submenu: [ { label: 'New', accelerator: 'CmdOrCtrl+N', click: () => newFile() }, { label: 'Open', accelerator: 'CmdOrCtrl+O', click: () => openFile() }, { type: 'separator' }, { label: 'Save', accelerator: 'CmdOrCtrl+S', click: () => saveFile() }, { type: 'separator' }, { label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: () => app.quit() } ] }, // 编辑菜单 { label: 'Edit', submenu: [ { label: 'Undo', accelerator: 'CmdOrCtrl+Z', role: 'undo' }, { label: 'Redo', accelerator: 'CmdOrCtrl+Y', role: 'redo' }, { type: 'separator' }, { label: 'Cut', accelerator: 'CmdOrCtrl+X', role: 'cut' }, { label: 'Copy', accelerator: 'CmdOrCtrl+C', role: 'copy' }, { label: 'Paste', accelerator: 'CmdOrCtrl+V', role: 'paste' } ] } ] const menu = Menu.buildFromTemplate(template) Menu.setApplicationMenu(menu)}2. 托盘图标设计// main.js// 使用高对比度图标function createTray() { const iconPath = path.join(__dirname, 'icon.png') const icon = nativeImage.createFromPath(iconPath) // 确保图标在不同背景下都可见 const iconSize = process.platform === 'win32' ? 16 : 22 tray = new Tray(icon.resize({ width: iconSize, height: iconSize })) tray.setToolTip('My Application')}3. 用户体验// main.js// 提供清晰的菜单项function createTrayMenu() { const template = [ { label: 'Show Window', click: () => showWindow() }, { type: 'separator' }, { label: 'Settings', click: () => openSettings() }, { label: 'Help', click: () => openHelp() }, { type: 'separator' }, { label: 'Exit', click: () => app.quit() } ] const menu = Menu.buildFromTemplate(template) tray.setContextMenu(menu)}常见问题Q: 如何在 macOS 上创建模板图标?A: 使用 setTemplateImage(true) 方法:const icon = nativeImage.createFromPath(iconPath)icon.setTemplateImage(true)tray = new Tray(icon)Q: 如何禁用默认菜单?A: 设置空菜单:Menu.setApplicationMenu(null)Q: 如何在托盘菜单中添加复选框?A: 使用 type: 'checkbox':{ label: 'Enable Feature', type: 'checkbox', checked: true, click: (menuItem) => { console.log('Checked:', menuItem.checked) }}Q: 如何在 Windows 上创建系统托盘气球提示?A: 使用 display-balloon:tray.displayBalloon({ title: 'Notification', content: 'This is a balloon notification'})​
阅读 0·2月18日 10:35

Electron 数据持久化方案

在 Electron 应用中,数据持久化是常见需求,包括用户设置、应用数据、缓存等。本文将详细介绍 Electron 中的各种数据持久化方案。本地存储方案1. localStoragelocalStorage 是最简单的存储方案,适合存储少量数据。// renderer.js// 存储数据localStorage.setItem('userSettings', JSON.stringify({ theme: 'dark', language: 'zh-CN', fontSize: 14}))// 读取数据const settings = JSON.parse(localStorage.getItem('userSettings'))console.log(settings)// 删除数据localStorage.removeItem('userSettings')// 清空所有数据localStorage.clear()优点:简单易用同步 API数据持久化缺点:存储容量限制(通常 5-10MB)只能存储字符串不适合大量数据2. sessionStoragesessionStorage 类似于 localStorage,但数据在页面会话结束时清除。// renderer.js// 存储临时数据sessionStorage.setItem('tempData', JSON.stringify({ token: 'abc123', timestamp: Date.now()}))// 读取数据const tempData = JSON.parse(sessionStorage.getItem('tempData'))适用场景:临时会话数据页面间传递数据不需要持久化的数据3. IndexedDBIndexedDB 是浏览器提供的强大数据库,适合存储大量结构化数据。// renderer.js// 打开数据库const request = indexedDB.open('MyAppDB', 1)request.onerror = (event) => { console.error('Database error:', event.target.error)}request.onsuccess = (event) => { const db = event.target.result console.log('Database opened successfully')}request.onupgradeneeded = (event) => { const db = event.target.result // 创建对象存储 const objectStore = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true }) // 创建索引 objectStore.createIndex('name', 'name', { unique: false }) objectStore.createIndex('email', 'email', { unique: true })}// 添加数据function addUser(db, userData) { const transaction = db.transaction(['users'], 'readwrite') const objectStore = transaction.objectStore('users') const request = objectStore.add(userData) request.onsuccess = () => { console.log('User added successfully') } request.onerror = () => { console.error('Error adding user:', request.error) }}// 查询数据function getUser(db, userId) { const transaction = db.transaction(['users'], 'readonly') const objectStore = transaction.objectStore('users') const request = objectStore.get(userId) request.onsuccess = () => { const user = request.result console.log('User:', user) }}// 更新数据function updateUser(db, userData) { const transaction = db.transaction(['users'], 'readwrite') const objectStore = transaction.objectStore('users') const request = objectStore.put(userData) request.onsuccess = () => { console.log('User updated successfully') }}// 删除数据function deleteUser(db, userId) { const transaction = db.transaction(['users'], 'readwrite') const objectStore = transaction.objectStore('users') const request = objectStore.delete(userId) request.onsuccess = () => { console.log('User deleted successfully') }}优点:存储容量大(通常几百 MB)支持事务支持索引查询异步操作,不阻塞 UI缺点:API 相对复杂异步操作需要回调或 Promise4. 使用 Dexie.js 简化 IndexedDBDexie.js 是 IndexedDB 的封装库,提供了更简洁的 API。// renderer.jsimport Dexie from 'dexie'// 创建数据库const db = new Dexie('MyAppDB')// 定义表结构db.version(1).stores({ users: '++id, name, email', posts: '++id, title, userId', comments: '++id, content, postId'})// 添加数据async function addUser(name, email) { try { const id = await db.users.add({ name, email }) console.log('User added with ID:', id) return id } catch (error) { console.error('Error adding user:', error) }}// 查询数据async function getAllUsers() { try { const users = await db.users.toArray() return users } catch (error) { console.error('Error fetching users:', error) }}// 条件查询async function getUserByEmail(email) { try { const user = await db.users.where('email').equals(email).first() return user } catch (error) { console.error('Error fetching user:', error) }}// 更新数据async function updateUser(id, updates) { try { await db.users.update(id, updates) console.log('User updated successfully') } catch (error) { console.error('Error updating user:', error) }}// 删除数据async function deleteUser(id) { try { await db.users.delete(id) console.log('User deleted successfully') } catch (error) { console.error('Error deleting user:', error) }}文件系统存储1. 使用 Node.js fs 模块在主进程中可以使用 Node.js 的 fs 模块进行文件操作。// main.jsconst fs = require('fs').promisesconst path = require('path')const { app } = require('electron')// 获取用户数据目录const userDataPath = app.getPath('userData')const dataFilePath = path.join(userDataPath, 'data.json')// 保存数据async function saveData(data) { try { await fs.writeFile(dataFilePath, JSON.stringify(data, null, 2), 'utf-8') console.log('Data saved successfully') } catch (error) { console.error('Error saving data:', error) }}// 读取数据async function loadData() { try { const data = await fs.readFile(dataFilePath, 'utf-8') return JSON.parse(data) } catch (error) { if (error.code === 'ENOENT') { // 文件不存在,返回默认数据 return {} } console.error('Error loading data:', error) return null }}// 删除数据async function deleteData() { try { await fs.unlink(dataFilePath) console.log('Data deleted successfully') } catch (error) { console.error('Error deleting data:', error) }}2. 使用 electron-storeelectron-store 是专门为 Electron 设计的简单数据存储库。npm install electron-store// main.jsconst Store = require('electron-store')const store = new Store({ defaults: { settings: { theme: 'light', language: 'en', fontSize: 14 }, userData: {} }})// 保存数据store.set('settings.theme', 'dark')store.set('userData.name', 'John Doe')// 读取数据const theme = store.get('settings.theme')const userData = store.get('userData')// 获取所有数据const allData = store.store// 删除数据store.delete('userData.name')// 清空数据store.clear()// 检查数据是否存在const hasTheme = store.has('settings.theme')// 监听数据变化store.onDidChange('settings.theme', (newValue, oldValue) => { console.log('Theme changed from', oldValue, 'to', newValue)})3. 使用 lowdblowdb 是一个轻量级的 JSON 数据库。npm install lowdb// main.jsconst Low = require('lowdb')const FileSync = require('lowdb/adapters/FileSync')const path = require('path')const { app } = require('electron')// 创建数据库const adapter = new FileSync(path.join(app.getPath('userData'), 'db.json'))const db = new Low(adapter)// 初始化默认数据db.defaults({ users: [], posts: [], settings: { theme: 'light', language: 'en' }}).write()// 添加数据db.get('users') .push({ id: 1, name: 'John', email: 'john@example.com' }) .write()// 查询数据const users = db.get('users').value()const user = db.get('users').find({ id: 1 }).value()// 更新数据db.get('users') .find({ id: 1 }) .assign({ name: 'John Doe' }) .write()// 删除数据db.get('users') .remove({ id: 1 }) .write()// 链式查询const activeUsers = db.get('users') .filter({ status: 'active' }) .orderBy('name') .value()SQLite 数据库1. 使用 better-sqlite3better-sqlite3 是一个同步的 SQLite 绑定,性能优异。npm install better-sqlite3// main.jsconst Database = require('better-sqlite3')const path = require('path')const { app } = require('electron')// 创建数据库连接const dbPath = path.join(app.getPath('userData'), 'app.db')const db = new Database(dbPath)// 创建表db.exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, user_id INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) )`)// 插入数据function insertUser(name, email) { const stmt = db.prepare('INSERT INTO users (name, email) VALUES (?, ?)') const result = stmt.run(name, email) return result.lastInsertRowid}// 查询数据function getAllUsers() { const stmt = db.prepare('SELECT * FROM users') return stmt.all()}function getUserById(id) { const stmt = db.prepare('SELECT * FROM users WHERE id = ?') return stmt.get(id)}function getUserByEmail(email) { const stmt = db.prepare('SELECT * FROM users WHERE email = ?') return stmt.get(email)}// 更新数据function updateUser(id, updates) { const stmt = db.prepare('UPDATE users SET name = ?, email = ? WHERE id = ?') const result = stmt.run(updates.name, updates.email, id) return result.changes}// 删除数据function deleteUser(id) { const stmt = db.prepare('DELETE FROM users WHERE id = ?') const result = stmt.run(id) return result.changes}// 事务处理function transferFunds(fromUserId, toUserId, amount) { const insertStmt = db.prepare('INSERT INTO transactions (from_user, to_user, amount) VALUES (?, ?, ?)') const updateStmt = db.prepare('UPDATE users SET balance = balance - ? WHERE id = ?') const transaction = db.transaction((from, to, amt) => { updateStmt.run(amt, from) updateStmt.run(-amt, to) insertStmt.run(from, to, amt) }) transaction(fromUserId, toUserId, amount)}2. 使用 Knex.jsKnex.js 是一个 SQL 查询构建器,支持多种数据库。npm install knex sqlite3// main.jsconst knex = require('knex')({ client: 'sqlite3', connection: { filename: path.join(app.getPath('userData'), 'app.db') }, useNullAsDefault: true})// 创建表knex.schema .createTableIfNotExists('users', (table) => { table.increments('id').primary() table.string('name').notNullable() table.string('email').unique().notNullable() table.timestamps(true, true) }) .createTableIfNotExists('posts', (table) => { table.increments('id').primary() table.string('title').notNullable() table.text('content') table.integer('user_id').references('id').inTable('users') table.timestamps(true, true) }) .then(() => { console.log('Tables created successfully') })// 插入数据async function insertUser(name, email) { try { const [id] = await knex('users').insert({ name, email }) return id } catch (error) { console.error('Error inserting user:', error) }}// 查询数据async function getAllUsers() { try { const users = await knex('users').select('*') return users } catch (error) { console.error('Error fetching users:', error) }}async function getUserById(id) { try { const user = await knex('users').where({ id }).first() return user } catch (error) { console.error('Error fetching user:', error) }}// 更新数据async function updateUser(id, updates) { try { const count = await knex('users').where({ id }).update(updates) return count } catch (error) { console.error('Error updating user:', error) }}// 删除数据async function deleteUser(id) { try { const count = await knex('users').where({ id }).del() return count } catch (error) { console.error('Error deleting user:', error) }}// 关联查询async function getUserWithPosts(userId) { try { const user = await knex('users') .leftJoin('posts', 'users.id', 'posts.user_id') .where('users.id', userId) .select('users.*', 'posts.title as post_title') return user } catch (error) { console.error('Error fetching user with posts:', error) }}数据同步方案1. 本地数据同步到云端// main.jsconst axios = require('axios')async function syncDataToCloud(localData) { try { const response = await axios.post('https://api.example.com/sync', { data: localData, timestamp: Date.now() }) return response.data } catch (error) { console.error('Sync failed:', error) throw error }}async function syncDataFromCloud() { try { const response = await axios.get('https://api.example.com/sync') return response.data } catch (error) { console.error('Sync failed:', error) throw error }}2. 冲突解决// main.jsfunction resolveConflict(localData, remoteData) { const localTimestamp = localData.timestamp || 0 const remoteTimestamp = remoteData.timestamp || 0 if (localTimestamp > remoteTimestamp) { return localData } else if (remoteTimestamp > localTimestamp) { return remoteData } else { // 时间戳相同,使用合并策略 return { ...localData, ...remoteData } }}最佳实践1. 数据加密// main.jsconst crypto = require('crypto')function encryptData(data, key) { const algorithm = 'aes-256-cbc' const iv = crypto.randomBytes(16) const cipher = crypto.createCipheriv(algorithm, key, iv) let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex') encrypted += cipher.final('hex') return iv.toString('hex') + ':' + encrypted}function decryptData(encryptedData, key) { const algorithm = 'aes-256-cbc' const parts = encryptedData.split(':') const iv = Buffer.from(parts.shift(), 'hex') const encrypted = parts.join(':') const decipher = crypto.createDecipheriv(algorithm, key, iv) let decrypted = decipher.update(encrypted, 'hex', 'utf8') decrypted += decipher.final('utf8') return JSON.parse(decrypted)}2. 数据备份// main.jsconst fs = require('fs').promisesconst path = require('path')const { app } = require('electron')async function backupData() { const userDataPath = app.getPath('userData') const backupPath = path.join(userDataPath, 'backups', Date.now().toString()) try { await fs.mkdir(backupPath, { recursive: true }) // 复制数据文件 const files = await fs.readdir(userDataPath) for (const file of files) { if (!file.startsWith('backup')) { await fs.copyFile( path.join(userDataPath, file), path.join(backupPath, file) ) } } console.log('Backup created successfully') } catch (error) { console.error('Backup failed:', error) }}3. 数据迁移// main.jsconst migrations = [ { version: 1, migrate: (data) => { // 版本 1 的迁移逻辑 return { ...data, version: 1 } } }, { version: 2, migrate: (data) => { // 版本 2 的迁移逻辑 data.users = data.users || [] return { ...data, version: 2 } } }]async function runMigrations(data) { const currentVersion = data.version || 0 for (const migration of migrations) { if (migration.version > currentVersion) { data = migration.migrate(data) } } return data}常见问题Q: 如何选择合适的数据持久化方案?A: 根据数据量和复杂度选择:少量简单数据: localStorage中等结构化数据: IndexedDB 或 electron-store大量复杂数据: SQLite 或其他数据库Q: 如何处理数据加密?A: 使用 Node.js 的 crypto 模块进行加密,确保密钥安全存储。Q: 如何实现数据同步?A: 使用时间戳和版本号实现冲突检测和解决,定期同步本地和云端数据。Q: 如何优化数据库性能?A: 使用索引、批量操作、事务处理、连接池等技术优化数据库性能。
阅读 0·2月17日 23:54

Electron IPC 进程间通信的实现方式

IPC(Inter-Process Communication,进程间通信)是 Electron 架构的核心机制,用于在主进程和渲染进程之间传递消息和数据。IPC 基础概念Electron 的 IPC 模块包含两个主要部分:ipcMain: 在主进程中使用,用于接收来自渲染进程的消息ipcRenderer: 在渲染进程中使用,用于向主进程发送消息基本通信方式1. 渲染进程 → 主进程(单向通信)发送消息(无响应)// 渲染进程const { ipcRenderer } = require('electron')ipcRenderer.send('channel-name', { data: 'Hello from renderer' })// 主进程const { ipcMain } = require('electron')ipcMain.on('channel-name', (event, data) => { console.log('Received:', data)})发送消息并等待响应// 渲染进程ipcRenderer.invoke('channel-name', { data: 'Hello' }) .then(response => { console.log('Response:', response) })// 主进程ipcMain.handle('channel-name', async (event, data) => { // 处理逻辑 return { result: 'Success' }})2. 主进程 → 渲染进程(单向通信)// 主进程mainWindow.webContents.send('channel-name', { data: 'Hello from main' })// 渲染进程ipcRenderer.on('channel-name', (event, data) => { console.log('Received:', data)})3. 双向通信// 渲染进程ipcRenderer.send('request-data', { id: 123 })ipcRenderer.on('response-data', (event, data) => { console.log('Response:', data)})// 主进程ipcMain.on('request-data', (event, data) => { // 处理请求 const result = processData(data) event.reply('response-data', result)})高级通信模式1. 使用 invoke/handle 进行异步通信这种方式返回 Promise,更适合异步操作:// 渲染进程async function fetchData() { try { const result = await ipcRenderer.invoke('fetch-data', { id: 1 }) return result } catch (error) { console.error('Error:', error) }}// 主进程ipcMain.handle('fetch-data', async (event, { id }) => { // 可以执行异步操作 const data = await database.query(id) return data})2. 使用 contextBridge 安全暴露 API// preload.jsconst { contextBridge, ipcRenderer } = require('electron')contextBridge.exposeInMainWorld('electronAPI', { // 发送消息 send: (channel, data) => ipcRenderer.send(channel, data), // 发送并等待响应 invoke: (channel, data) => ipcRenderer.invoke(channel, data), // 监听消息 on: (channel, callback) => { const subscription = (event, ...args) => callback(...args) ipcRenderer.on(channel, subscription) return () => ipcRenderer.removeListener(channel, subscription) }, // 移除监听器 removeListener: (channel, callback) => { ipcRenderer.removeListener(channel, callback) }})// 渲染进程window.electronAPI.send('channel-name', data)const response = await window.electronAPI.invoke('async-channel', data)3. 使用 Event 进行窗口间通信// 主进程 - 从窗口 A 发送到窗口 Bconst windowA = BrowserWindow.fromWebContents(event.sender)const windowB = BrowserWindow.getAllWindows()[1]windowA.webContents.send('message-to-window-b', { data: 'Hello' })// 窗口 B 的渲染进程ipcRenderer.on('message-to-window-b', (event, data) => { console.log('Received from window A:', data)})通信数据类型IPC 支持传递的数据类型包括:基本类型(String, Number, Boolean, null, undefined)对象和数组BufferError 对象Date 对象注意: 不能传递函数、DOM 节点、循环引用的对象最佳实践1. 使用明确的通道名称// 好的做法ipcRenderer.send('user:login', credentials)ipcRenderer.send('file:read', filePath)// 不好的做法ipcRenderer.send('message1', data)ipcRenderer.send('message2', data)2. 错误处理// 渲染进程try { const result = await ipcRenderer.invoke('async-operation', data)} catch (error) { console.error('Operation failed:', error)}// 主进程ipcMain.handle('async-operation', async (event, data) => { try { const result = await process(data) return result } catch (error) { throw new Error(`Processing failed: ${error.message}`) }})3. 清理监听器// 渲染进程const cleanup = window.electronAPI.on('channel-name', (data) => { console.log(data)})// 组件卸载时清理componentWillUnmount() { cleanup()}4. 使用 TypeScript 类型定义// types.tsinterface IpcChannels { 'user:login': (credentials: Credentials) => Promise<User> 'file:read': (path: string) => Promise<string>}// preload.tscontextBridge.exposeInMainWorld('electronAPI', { invoke: <K extends keyof IpcChannels>( channel: K, ...args: Parameters<IpcChannels[K]> ): ReturnType<IpcChannels[K]> => ipcRenderer.invoke(channel, ...args)})性能优化1. 减少通信频率// 不好的做法 - 频繁通信for (let i = 0; i < 1000; i++) { ipcRenderer.send('process-item', items[i])}// 好的做法 - 批量处理ipcRenderer.send('process-items', items)2. 使用 Worker Threads 处理密集任务// 主进程ipcMain.handle('heavy-computation', async (event, data) => { const worker = new Worker('./worker.js') return new Promise((resolve, reject) => { worker.on('message', resolve) worker.on('error', reject) worker.postMessage(data) })})安全考虑验证输入数据: 在主进程中验证来自渲染进程的所有数据限制通道访问: 只暴露必要的 IPC 通道使用 contextIsolation: 确保渲染进程无法直接访问 Node.js API避免敏感数据: 不要在 IPC 消息中传递敏感信息常见问题Q: invoke 和 send 有什么区别?A: invoke 返回 Promise,适合需要响应的场景;send 是单向通信,不返回结果。Q: 如何在多个窗口之间通信?A: 通过主进程作为中介,使用 webContents.send() 向特定窗口发送消息。Q: IPC 通信有大小限制吗?A: 理论上没有硬性限制,但建议传递大数据时使用文件系统或共享内存。
阅读 0·2月17日 23:53

Electron 是什么及其核心架构

Electron 是一个开源框架,允许开发者使用 JavaScript、HTML 和 CSS 构建跨平台桌面应用程序。它由 GitHub 开发,被广泛应用于 Atom、VS Code、Discord、Slack 等知名应用中。核心架构Electron 的架构主要由以下三个核心组件组成:1. Main Process(主进程)每个 Electron 应用都有一个单一的主进程主进程运行在 Node.js 环境中,可以使用所有 Node.js API负责创建和管理渲染进程(Renderer Process)控制应用程序的生命周期(启动、退出等)管理原生窗口和菜单处理操作系统级别的交互2. Renderer Process(渲染进程)每个窗口都有一个独立的渲染进程渲染进程运行在 Chromium 环境中,负责渲染 Web 页面可以使用 HTML、CSS 和 JavaScript 构建 UI默认情况下无法访问 Node.js API,需要通过 preload 脚本或 contextBridge 进行安全通信3. IPC(进程间通信)主进程和渲染进程之间通过 IPC 进行通信使用 ipcMain 和 ipcRenderer 模块实现消息传递支持同步和异步通信方式是 Electron 应用中数据交换的核心机制工作原理应用启动: 主进程启动,读取 package.json 配置创建窗口: 主进程创建 BrowserWindow 实例加载页面: 窗口加载 HTML 文件,启动渲染进程渲染内容: 渲染进程使用 Chromium 引擎渲染 Web 页面交互通信: 通过 IPC 实现主进程和渲染进程之间的数据交换技术栈Chromium: 提供渲染引擎,支持现代 Web 标准Node.js: 提供后端能力,访问文件系统、网络等系统资源Native APIs: 提供操作系统级别的功能,如通知、菜单、托盘等优势跨平台支持(Windows、macOS、Linux)使用熟悉的 Web 技术栈活跃的社区和丰富的生态系统可以访问原生系统功能开发效率高,维护成本低注意事项应用包体积较大(包含完整的 Chromium 和 Node.js)内存占用相对较高需要注意安全性,避免暴露敏感的 Node.js API需要处理不同平台的兼容性问题
阅读 0·2月17日 23:53

TypeORM 的订阅者 (Subscriber) 是什么?如何使用订阅者监听实体事件

订阅者是 TypeORM 中用于监听和响应实体事件的强大机制。它允许开发者在实体生命周期的关键时刻执行自定义逻辑,类似于数据库触发器,但更加灵活和类型安全。订阅者基础概念什么是订阅者订阅者是一个类,它监听特定实体的生命周期事件,并在这些事件发生时执行相应的逻辑。订阅者可以监听的事件包括:beforeInsert: 插入前afterInsert: 插入后beforeUpdate: 更新前afterUpdate: 更新后beforeRemove: 删除前afterRemove: 删除后beforeSoftRemove: 软删除前afterSoftRemove: 软删除后beforeRecover: 恢复前afterRecover: 恢复后订阅者 vs 监听器订阅者: 监听所有实体实例的事件,适合全局逻辑监听器: 在实体内部定义,只监听该实体的事件,适合实体特定的逻辑创建订阅者基本订阅者示例import { EntitySubscriberInterface, EventSubscriber, InsertEvent, UpdateEvent } from 'typeorm';import { User } from '../entity/User';@EventSubscriber()export class UserSubscriber implements EntitySubscriberInterface<User> { // 指定要监听的实体 listenTo() { return User; } // 插入前 beforeInsert(event: InsertEvent<User>) { console.log('Before insert user:', event.entity); // 自动生成用户名 if (!event.entity.username) { event.entity.username = event.entity.email.split('@')[0]; } // 自动设置创建时间 if (!event.entity.createdAt) { event.entity.createdAt = new Date(); } } // 插入后 afterInsert(event: InsertEvent<User>) { console.log('After insert user:', event.entity); // 发送欢迎邮件 this.sendWelcomeEmail(event.entity); // 记录审计日志 this.logAudit('INSERT', event.entity); } // 更新前 beforeUpdate(event: UpdateEvent<User>) { console.log('Before update user:', event.entity); // 自动更新修改时间 if (event.entity) { event.entity.updatedAt = new Date(); } } // 更新后 afterUpdate(event: UpdateEvent<User>) { console.log('After update user:', event.entity); // 记录变更历史 this.recordChangeHistory(event); } // 删除前 beforeRemove(event: any) { console.log('Before remove user:', event.entity); // 检查是否可以删除 if (event.entity.hasActiveOrders()) { throw new Error('Cannot delete user with active orders'); } } // 删除后 afterRemove(event: any) { console.log('After remove user:', event.entity); // 清理关联数据 this.cleanupRelatedData(event.entity.id); } private sendWelcomeEmail(user: User) { // 发送欢迎邮件的逻辑 console.log(`Sending welcome email to ${user.email}`); } private logAudit(action: string, user: User) { // 记录审计日志的逻辑 console.log(`Audit log: ${action} user ${user.id}`); } private recordChangeHistory(event: UpdateEvent<User>) { // 记录变更历史的逻辑 console.log('Recording change history:', event.databaseEntity, event.entity); } private cleanupRelatedData(userId: number) { // 清理关联数据的逻辑 console.log(`Cleaning up data for user ${userId}`); }}注册订阅者在 DataSource 中注册import { DataSource } from 'typeorm';import { UserSubscriber } from './subscriber/UserSubscriber';export const AppDataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post, Comment], subscribers: [UserSubscriber], // 注册订阅者 synchronize: false, logging: true,});动态注册订阅者import { DataSource } from 'typeorm';const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: [User, Post, Comment], synchronize: false, logging: true,});// 初始化后动态添加订阅者await dataSource.initialize();const userSubscriber = new UserSubscriber();dataSource.subscribers.push(userSubscriber);高级订阅者用法数据验证@EventSubscriber()export class UserSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } beforeInsert(event: InsertEvent<User>) { this.validateUser(event.entity); } beforeUpdate(event: UpdateEvent<User>) { if (event.entity) { this.validateUser(event.entity); } } private validateUser(user: User) { // 验证邮箱格式 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(user.email)) { throw new Error('Invalid email format'); } // 验证年龄 if (user.age && (user.age < 18 || user.age > 120)) { throw new Error('Age must be between 18 and 120'); } // 验证用户名长度 if (user.username && user.username.length < 3) { throw new Error('Username must be at least 3 characters'); } }}自动填充字段@EventSubscriber()export class BaseEntitySubscriber implements EntitySubscriberInterface { listenTo() { return Object; // 监听所有实体 } beforeInsert(event: InsertEvent<any>) { const entity = event.entity; const now = new Date(); // 自动设置创建时间 if (entity.hasOwnProperty('createdAt') && !entity.createdAt) { entity.createdAt = now; } // 自动设置更新时间 if (entity.hasOwnProperty('updatedAt') && !entity.updatedAt) { entity.updatedAt = now; } // 自动设置创建者 if (entity.hasOwnProperty('createdBy') && !entity.createdBy) { entity.createdBy = this.getCurrentUserId(); } } beforeUpdate(event: UpdateEvent<any>) { const entity = event.entity; if (entity) { // 自动更新更新时间 if (entity.hasOwnProperty('updatedAt')) { entity.updatedAt = new Date(); } // 自动设置更新者 if (entity.hasOwnProperty('updatedBy')) { entity.updatedBy = this.getCurrentUserId(); } } } private getCurrentUserId(): number { // 获取当前用户 ID 的逻辑 return 1; // 示例 }}审计日志@EventSubscriber()export class AuditLogSubscriber implements EntitySubscriberInterface { listenTo() { return Object; // 监听所有实体 } async afterInsert(event: InsertEvent<any>) { await this.createAuditLog('INSERT', event.entity); } async afterUpdate(event: UpdateEvent<any>) { await this.createAuditLog('UPDATE', event.entity, event.databaseEntity); } async afterRemove(event: any) { await this.createAuditLog('DELETE', event.entity); } private async createAuditLog( action: string, entity: any, oldEntity?: any ) { const auditLog = { action, entityType: entity.constructor.name, entityId: entity.id, userId: this.getCurrentUserId(), timestamp: new Date(), changes: oldEntity ? this.getChanges(oldEntity, entity) : null, ipAddress: this.getCurrentIpAddress(), }; // 保存审计日志 console.log('Creating audit log:', auditLog); // await this.auditLogRepository.save(auditLog); } private getChanges(oldEntity: any, newEntity: any): any { const changes: any = {}; for (const key in newEntity) { if (oldEntity[key] !== newEntity[key]) { changes[key] = { old: oldEntity[key], new: newEntity[key], }; } } return changes; } private getCurrentUserId(): number { return 1; // 示例 } private getCurrentIpAddress(): string { return '127.0.0.1'; // 示例 }}缓存失效@EventSubscriber()export class CacheInvalidationSubscriber implements EntitySubscriberInterface { private cacheService: CacheService; constructor() { this.cacheService = new CacheService(); } listenTo() { return Object; // 监听所有实体 } async afterInsert(event: InsertEvent<any>) { await this.invalidateCache(event.entity); } async afterUpdate(event: UpdateEvent<any>) { if (event.entity) { await this.invalidateCache(event.entity); } } async afterRemove(event: any) { await this.invalidateCache(event.entity); } private async invalidateCache(entity: any) { const entityType = entity.constructor.name.toLowerCase(); const entityId = entity.id; // 使单个实体的缓存失效 await this.cacheService.delete(`${entityType}:${entityId}`); // 使列表缓存失效 await this.cacheService.delete(`${entityType}:list:*`); console.log(`Cache invalidated for ${entityType}:${entityId}`); }}通知和事件@EventSubscriber()export class NotificationSubscriber implements EntitySubscriberInterface { private notificationService: NotificationService; constructor() { this.notificationService = new NotificationService(); } listenTo() { return Object; // 监听所有实体 } async afterInsert(event: InsertEvent<any>) { await this.handleInsertEvent(event); } async afterUpdate(event: UpdateEvent<any>) { await this.handleUpdateEvent(event); } private async handleInsertEvent(event: InsertEvent<any>) { const entity = event.entity; // 根据实体类型发送不同的通知 switch (entity.constructor.name) { case 'Order': await this.notificationService.sendOrderCreatedNotification(entity); break; case 'Comment': await this.notificationService.sendCommentNotification(entity); break; case 'Message': await this.notificationService.sendMessageNotification(entity); break; } } private async handleUpdateEvent(event: UpdateEvent<any>) { const entity = event.entity; if (!entity) return; // 根据实体类型和变更发送通知 switch (entity.constructor.name) { case 'Order': if (entity.status !== event.databaseEntity.status) { await this.notificationService.sendOrderStatusChangedNotification(entity); } break; case 'User': if (entity.email !== event.databaseEntity.email) { await this.notificationService.sendEmailChangedNotification(entity); } break; } }}订阅者最佳实践1. 单一职责原则每个订阅者应该只负责一个特定的功能领域。// ✅ 好的做法:每个订阅者负责一个功能@EventSubscriber()export class UserValidationSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } beforeInsert(event: InsertEvent<User>) { /* 验证逻辑 */ }}@EventSubscriber()export class UserAuditSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } afterInsert(event: InsertEvent<User>) { /* 审计逻辑 */ }}// ❌ 不好的做法:一个订阅者负责多个功能@EventSubscriber()export class UserSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } beforeInsert(event: InsertEvent<User>) { /* 验证逻辑 */ } afterInsert(event: InsertEvent<User>) { /* 审计逻辑 */ } afterUpdate(event: UpdateEvent<User>) { /* 通知逻辑 */ }}2. 避免循环依赖订阅者不应该触发会导致其他订阅者无限循环的操作。@EventSubscriber()export class UserSubscriber implements EntitySubscriberInterface<User> { constructor( private userRepository: Repository<User> ) {} listenTo() { return User; } async afterInsert(event: InsertEvent<User>) { // ❌ 不好的做法:可能导致循环 // await this.userRepository.save(event.entity); // ✅ 好的做法:使用 EntityManager 避免触发订阅者 await this.userRepository.manager.save(User, event.entity); }}3. 错误处理在订阅者中妥善处理错误,避免影响主操作。@EventSubscriber()export class SafeSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } async afterInsert(event: InsertEvent<User>) { try { await this.sendNotification(event.entity); } catch (error) { // 记录错误但不影响主操作 console.error('Failed to send notification:', error); // 可以将错误发送到错误监控系统 } } private async sendNotification(user: User) { // 发送通知的逻辑 }}4. 性能考虑避免在订阅者中执行耗时操作。@EventSubscriber()export class PerformanceAwareSubscriber implements EntitySubscriberInterface<User> { listenTo() { return User; } async afterInsert(event: InsertEvent<User>) { // ❌ 不好的做法:同步执行耗时操作 // await this.sendEmail(event.entity); // await this.generateReport(event.entity); // ✅ 好的做法:异步执行耗时操作 setImmediate(() => { this.sendEmail(event.entity).catch(console.error); this.generateReport(event.entity).catch(console.error); }); // 或者使用消息队列 // await this.queueService.add('send-email', { userId: event.entity.id }); } private async sendEmail(user: User) { // 发送邮件的逻辑 } private async generateReport(user: User) { // 生成报告的逻辑 }}5. 测试订阅者为订阅者编写单元测试。import { InsertEvent } from 'typeorm';import { UserSubscriber } from './UserSubscriber';import { User } from '../entity/User';describe('UserSubscriber', () => { let subscriber: UserSubscriber; beforeEach(() => { subscriber = new UserSubscriber(); }); it('should auto-generate username before insert', () => { const user = new User(); user.email = 'test@example.com'; const event: InsertEvent<User> = { entity: user, metadata: {} as any, queryRunner: {} as any, manager: {} as any, }; subscriber.beforeInsert(event); expect(user.username).toBe('test'); }); it('should set createdAt before insert', () => { const user = new User(); user.email = 'test@example.com'; const event: InsertEvent<User> = { entity: user, metadata: {} as any, queryRunner: {} as any, manager: {} as any, }; subscriber.beforeInsert(event); expect(user.createdAt).toBeInstanceOf(Date); });});订阅者 vs 监听器监听器示例@Entity()export class User { @PrimaryGeneratedColumn() id: number; @Column() name: string; @Column() email: string; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; @BeforeInsert() beforeInsert() { console.log('Before insert in entity'); this.createdAt = new Date(); } @AfterInsert() afterInsert() { console.log('After insert in entity'); } @BeforeUpdate() beforeUpdate() { console.log('Before update in entity'); this.updatedAt = new Date(); } @AfterUpdate() afterUpdate() { console.log('After update in entity'); }}选择订阅者还是监听器使用订阅者的情况:需要监听多个实体的事件需要注入其他服务需要实现跨实体的业务逻辑需要保持实体类的简洁使用监听器的情况:逻辑只与单个实体相关逻辑简单,不需要外部依赖希望逻辑与实体紧密关联TypeORM 的订阅者机制提供了强大而灵活的事件处理能力,合理使用订阅者可以实现复杂的业务逻辑,同时保持代码的整洁和可维护性。
阅读 0·2月17日 23:52

Python 装饰器的工作原理是什么?

基本概念装饰器(Decorator)是 Python 中一种强大的设计模式,它允许在不修改原函数代码的情况下,为函数添加额外的功能。装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。工作原理1. 装饰器的语法糖@decoratordef my_function(): pass上面的代码等价于:my_function = decorator(my_function)2. 简单装饰器示例def simple_decorator(func): def wrapper(): print("函数执行前") func() print("函数执行后") return wrapper@simple_decoratordef say_hello(): print("Hello, World!")say_hello()输出:函数执行前Hello, World!函数执行后3. 带参数的装饰器def decorator_with_args(func): def wrapper(*args, **kwargs): print(f"参数: args={args}, kwargs={kwargs}") result = func(*args, **kwargs) return result return wrapper@decorator_with_argsdef add(a, b): return a + bprint(add(3, 5)) # 输出: 参数: args=(3, 5), kwargs={} \n 84. 带自定义参数的装饰器def repeat(times): def decorator(func): def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator@repeat(3)def greet(name): print(f"Hello, {name}!")greet("Python") # 会打印三次5. 使用 functools.wraps 保留元数据from functools import wrapsdef logging_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"调用函数: {func.__name__}") return func(*args, **kwargs) return wrapper@logging_decoratordef calculate(x, y): """计算两个数的和""" return x + yprint(calculate.__name__) # 输出: calculateprint(calculate.__doc__) # 输出: 计算两个数的和实际应用场景1. 计时装饰器import timefrom functools import wrapsdef timer(func): @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"{func.__name__} 执行时间: {end_time - start_time:.4f} 秒") return result return wrapper@timerdef slow_function(): time.sleep(2) return "完成"slow_function()2. 缓存装饰器from functools import wrapsdef memoize(func): cache = {} @wraps(func) def wrapper(*args): if args not in cache: cache[args] = func(*args) return cache[args] return wrapper@memoizedef fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)print(fibonacci(100)) # 快速计算3. 权限验证装饰器from functools import wrapsdef require_permission(permission): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): user_permissions = kwargs.get('permissions', []) if permission in user_permissions: return func(*args, **kwargs) else: raise PermissionError(f"需要 {permission} 权限") return wrapper return decorator@require_permission('admin')def delete_user(user_id, permissions): print(f"删除用户 {user_id}")delete_user(1, permissions=['admin']) # 正常执行delete_user(1, permissions=['user']) # 抛出权限错误类装饰器class CountCalls: def __init__(self, func): self.func = func self.count = 0 def __call__(self, *args, **kwargs): self.count += 1 print(f"调用次数: {self.count}") return self.func(*args, **kwargs)@CountCallsdef say_hi(): print("Hi!")say_hi() # 调用次数: 1say_hi() # 调用次数: 2多个装饰器的执行顺序def decorator1(func): def wrapper(): print("装饰器1 - 前") func() print("装饰器1 - 后") return wrapperdef decorator2(func): def wrapper(): print("装饰器2 - 前") func() print("装饰器2 - 后") return wrapper@decorator1@decorator2def my_function(): print("函数执行")my_function()输出:装饰器1 - 前装饰器2 - 前函数执行装饰器2 - 后装饰器1 - 后关键要点装饰器本质:是一个接受函数并返回新函数的函数语法糖:@decorator 是 func = decorator(func) 的简写参数传递:使用 *args, **kwargs 确保装饰器适用于任何函数元数据保留:使用 @wraps(func) 保留原函数的元数据执行顺序:多个装饰器时,从下往上应用,从上往下执行闭包:装饰器利用闭包特性访问外部变量类装饰器:通过实现 __call__ 方法实现装饰器是 Python 中实现横切关注点(如日志、缓存、权限验证)的优雅方式,遵循开放封闭原则,使代码更加模块化和可维护。
阅读 0·2月17日 23:50

Python 装饰器的高级应用有哪些?

装饰器进阶概念装饰器不仅可以用于简单的函数包装,还可以实现更复杂的功能,如参数化装饰器、类装饰器、装饰器链等。装饰器的基本回顾from functools import wrapsdef simple_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"调用函数: {func.__name__}") result = func(*args, **kwargs) print(f"函数返回: {result}") return result return wrapper@simple_decoratordef add(a, b): return a + badd(3, 5)# 输出:# 调用函数: add# 函数返回: 8参数化装饰器带参数的装饰器from functools import wrapsdef repeat(times): """重复执行函数指定次数的装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): results = [] for _ in range(times): result = func(*args, **kwargs) results.append(result) return results return wrapper return decorator@repeat(3)def greet(name): return f"Hello, {name}!"results = greet("Alice")print(results)# 输出: ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']带可选参数的装饰器from functools import wrapsdef logged(level="INFO"): """可配置日志级别的装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"[{level}] 调用函数: {func.__name__}") try: result = func(*args, **kwargs) print(f"[{level}] 函数返回: {result}") return result except Exception as e: print(f"[{level}] 函数异常: {e}") raise return wrapper return decorator# 使用默认日志级别@logged()def function1(): return "Success"# 使用自定义日志级别@logged(level="DEBUG")def function2(): return "Debug info"function1()function2()类装饰器基本类装饰器def add_class_method(cls): """为类添加类方法的装饰器""" @classmethod def class_method(cls): return f"类方法: {cls.__name__}" cls.class_method = class_method return cls@add_class_methodclass MyClass: passprint(MyClass.class_method()) # 类方法: MyClass单例装饰器def singleton(cls): """单例模式装饰器""" instances = {} def get_instance(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return get_instance@singletonclass Database: def __init__(self): self.connection = "Connected"db1 = Database()db2 = Database()print(db1 is db2) # True注册装饰器class PluginRegistry: """插件注册器""" def __init__(self): self.plugins = {} def register(self, name): def decorator(cls): self.plugins[name] = cls return cls return decorator def get_plugin(self, name): return self.plugins.get(name)registry = PluginRegistry()@registry.register("email")class EmailPlugin: def send(self, message): return f"发送邮件: {message}"@registry.register("sms")class SMSPlugin: def send(self, message): return f"发送短信: {message}"# 使用插件email_plugin = registry.get_plugin("email")print(email_plugin().send("Hello"))装饰器链多个装饰器叠加from functools import wrapsdef decorator1(func): @wraps(func) def wrapper(*args, **kwargs): print("装饰器1 - 前") result = func(*args, **kwargs) print("装饰器1 - 后") return result return wrapperdef decorator2(func): @wraps(func) def wrapper(*args, **kwargs): print("装饰器2 - 前") result = func(*args, **kwargs) print("装饰器2 - 后") return result return wrapper@decorator1@decorator2def my_function(): print("执行函数")my_function()# 输出:# 装饰器1 - 前# 装饰器2 - 前# 执行函数# 装饰器2 - 后# 装饰器1 - 后装饰器顺序的重要性from functools import wrapsdef uppercase(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return result.upper() return wrapperdef exclamation(func): @wraps(func) def wrapper(*args, **kwargs): result = func(*args, **kwargs) return f"{result}!" return wrapper# 不同的装饰器顺序产生不同的结果@exclamation@uppercasedef greet1(): return "hello"@uppercase@exclamationdef greet2(): return "hello"print(greet1()) # HELLO!print(greet2()) # HELLO!有状态的装饰器计数装饰器from functools import wrapsdef count_calls(func): """统计函数调用次数的装饰器""" @wraps(func) def wrapper(*args, **kwargs): wrapper.call_count += 1 print(f"函数 {func.__name__} 被调用了 {wrapper.call_count} 次") return func(*args, **kwargs) wrapper.call_count = 0 return wrapper@count_callsdef calculate(x): return x * 2calculate(5)calculate(10)calculate(15)# 输出:# 函数 calculate 被调用了 1 次# 函数 calculate 被调用了 2 次# 函数 calculate 被调用了 3 次缓存装饰器from functools import wrapsdef memoize(func): """记忆化装饰器""" cache = {} @wraps(func) def wrapper(*args, **kwargs): # 创建缓存键 key = (args, frozenset(kwargs.items())) if key not in cache: print(f"计算结果: {args}, {kwargs}") cache[key] = func(*args, **kwargs) else: print(f"使用缓存: {args}, {kwargs}") return cache[key] wrapper.cache = cache return wrapper@memoizedef fibonacci(n): if n < 2: return n return fibonacci(n-1) + fibonacci(n-2)print(fibonacci(10))print(fibonacci(10)) # 第二次使用缓存方法装饰器实例方法装饰器from functools import wrapsdef method_decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): print(f"调用方法: {func.__name__} 在实例 {self} 上") return func(self, *args, **kwargs) return wrapperclass MyClass: @method_decorator def method(self, value): return value * 2obj = MyClass()print(obj.method(5))类方法装饰器from functools import wrapsdef class_method_decorator(func): @wraps(func) def wrapper(cls, *args, **kwargs): print(f"调用类方法: {func.__name__} 在类 {cls} 上") return func(cls, *args, **kwargs) return wrapperclass MyClass: @classmethod @class_method_decorator def class_method(cls): return f"类方法: {cls.__name__}"print(MyClass.class_method())静态方法装饰器from functools import wrapsdef static_method_decorator(func): @wraps(func) def wrapper(*args, **kwargs): print(f"调用静态方法: {func.__name__}") return func(*args, **kwargs) return wrapperclass MyClass: @staticmethod @static_method_decorator def static_method(value): return value * 2print(MyClass.static_method(5))装饰器工厂动态创建装饰器from functools import wrapsdef create_validator(**validators): """创建验证装饰器的工厂函数""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 验证参数 for param_name, validator in validators.items(): if param_name in kwargs: value = kwargs[param_name] if not validator(value): raise ValueError(f"参数 {param_name} 验证失败: {value}") return func(*args, **kwargs) return wrapper return decorator# 定义验证器def is_positive(x): return x > 0def is_string(x): return isinstance(x, str)# 使用工厂创建装饰器@create_validator(age=is_positive, name=is_string)def process_user(name, age): return f"用户: {name}, 年龄: {age}"print(process_user("Alice", 25))# process_user("Alice", -5) # ValueError: 参数 age 验证失败: -5装饰器与异步函数异步函数装饰器import asynciofrom functools import wrapsdef async_decorator(func): """异步函数装饰器""" @wraps(func) async def wrapper(*args, **kwargs): print(f"异步调用函数: {func.__name__}") result = await func(*args, **kwargs) print(f"异步函数返回: {result}") return result return wrapper@async_decoratorasync def async_function(): await asyncio.sleep(1) return "异步完成"async def main(): result = await async_function() print(result)asyncio.run(main())异步上下文管理器装饰器import asynciofrom functools import wrapsdef async_context_manager(func): """异步上下文管理器装饰器""" @wraps(func) async def wrapper(*args, **kwargs): print("进入异步上下文") try: result = await func(*args, **kwargs) return result finally: print("退出异步上下文") return wrapper@async_context_managerasync def async_operation(): print("执行异步操作") await asyncio.sleep(0.5) return "操作完成"asyncio.run(async_operation())实际应用场景1. 权限验证装饰器from functools import wrapsdef require_permission(permission): """权限验证装饰器""" def decorator(func): @wraps(func) def wrapper(self, *args, **kwargs): if not hasattr(self, 'permissions'): raise PermissionError("没有权限信息") if permission not in self.permissions: raise PermissionError(f"需要 {permission} 权限") return func(self, *args, **kwargs) return wrapper return decoratorclass User: def __init__(self, name, permissions): self.name = name self.permissions = permissions @require_permission('admin') def delete_user(self, user_id): return f"删除用户 {user_id}" @require_permission('write') def edit_post(self, post_id): return f"编辑文章 {post_id}"admin = User("Admin", ['admin', 'write'])user = User("User", ['read'])print(admin.delete_user(1)) # 删除用户 1# user.delete_user(1) # PermissionError: 需要 admin 权限2. 性能监控装饰器import timefrom functools import wrapsdef performance_monitor(func): """性能监控装饰器""" @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time print(f"函数 {func.__name__} 执行时间: {execution_time:.4f} 秒") return result return wrapper@performance_monitordef calculate_fibonacci(n): if n < 2: return n return calculate_fibonacci(n-1) + calculate_fibonacci(n-2)calculate_fibonacci(30)3. 重试装饰器import timefrom functools import wrapsdef retry(max_attempts=3, delay=1): """重试装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): last_exception = None for attempt in range(max_attempts): try: return func(*args, **kwargs) except Exception as e: last_exception = e if attempt < max_attempts - 1: print(f"尝试 {attempt + 1} 失败,{delay} 秒后重试...") time.sleep(delay) raise last_exception return wrapper return decorator@retry(max_attempts=3, delay=2)def unstable_function(): import random if random.random() < 0.7: raise ValueError("随机失败") return "成功"result = unstable_function()print(result)4. 日志装饰器import loggingfrom functools import wrapsdef logged(func): """日志装饰器""" @wraps(func) def wrapper(*args, **kwargs): logging.info(f"调用函数: {func.__name__} 参数: args={args}, kwargs={kwargs}") try: result = func(*args, **kwargs) logging.info(f"函数 {func.__name__} 返回: {result}") return result except Exception as e: logging.error(f"函数 {func.__name__} 异常: {e}") raise return wrapper# 配置日志logging.basicConfig(level=logging.INFO)@loggeddef divide(a, b): return a / bdivide(10, 2)# divide(10, 0) # 会记录错误日志5. 缓存装饰器from functools import lru_cacheimport time@lru_cache(maxsize=128)def expensive_computation(n): """耗时计算""" print(f"计算 {n}...") time.sleep(1) return n ** 2# 第一次调用print(expensive_computation(5)) # 计算 5... 25# 第二次调用(使用缓存)print(expensive_computation(5)) # 25(直接返回,不计算)最佳实践1. 使用 functools.wrapsfrom functools import wraps# 好的做法 - 保留函数元数据def good_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper# 不好的做法 - 不保留函数元数据def bad_decorator(func): def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper2. 处理装饰器参数from functools import wrapsdef decorator_with_args(*decorator_args, **decorator_kwargs): """支持参数的装饰器""" def decorator(func): @wraps(func) def wrapper(*args, **kwargs): # 使用装饰器参数 print(f"装饰器参数: {decorator_args}, {decorator_kwargs}") return func(*args, **kwargs) return wrapper return decorator@decorator_with_args("arg1", "arg2", kwarg1="value1")def my_function(): return "Hello"my_function()3. 装饰器应该是无副作用的from functools import wraps# 好的做法 - 无副作用def pure_decorator(func): @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) return wrapper# 不好的做法 - 有副作用counter = 0def impure_decorator(func): @wraps(func) def wrapper(*args, **kwargs): global counter counter += 1 return func(*args, **kwargs) return wrapper4. 提供清晰的文档from functools import wrapsdef timing_decorator(func): """计时装饰器 这个装饰器用于测量函数的执行时间。 执行时间会打印到控制台。 示例: @timing_decorator def my_function(): time.sleep(1) return "Done" """ @wraps(func) def wrapper(*args, **kwargs): import time start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"{func.__name__} 执行时间: {end_time - start_time:.4f} 秒") return result return wrapper总结Python 装饰器高级应用的核心概念:参数化装饰器:接受参数的装饰器,提供更多灵活性类装饰器:用于装饰类,添加类级别的功能装饰器链:多个装饰器叠加使用,注意执行顺序有状态的装饰器:维护内部状态的装饰器方法装饰器:用于装饰实例方法、类方法、静态方法装饰器工厂:动态创建装饰器的函数异步装饰器:用于装饰异步函数装饰器的实际应用:权限验证性能监控错误处理和重试日志记录缓存和记忆化参数验证装饰器的最佳实践:使用 functools.wraps 保留函数元数据正确处理装饰器参数保持装饰器的无副作用提供清晰的文档和示例考虑装饰器的性能影响掌握装饰器的高级应用,能够编写出更强大、更灵活的 Python 代码。
阅读 0·2月17日 23:49

TypeORM 的迁移系统如何工作?如何创建、运行和管理数据库迁移

迁移系统是 TypeORM 中用于管理数据库结构变更的重要工具,它允许开发者以版本化的方式追踪和应用数据库结构的变化,确保团队协作时数据库结构的一致性。迁移基础概念什么是迁移迁移是数据库结构变更的脚本,用于:创建或删除表添加或删除列修改列类型创建或删除索引添加或删除外键约束每个迁移都有唯一的版本号和时间戳,确保迁移可以按顺序执行。迁移文件结构import { MigrationInterface, QueryRunner, Table } from 'typeorm';export class CreateUserTable1234567890123 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { // 执行迁移:创建表、添加列等 await queryRunner.createTable( new Table({ name: 'user', columns: [ { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment', }, { name: 'name', type: 'varchar', }, { name: 'email', type: 'varchar', isUnique: true, }, { name: 'createdAt', type: 'timestamp', default: 'CURRENT_TIMESTAMP', }, ], }), true ); } public async down(queryRunner: QueryRunner): Promise<void> { // 回滚迁移:删除表、移除列等 await queryRunner.dropTable('user'); }}创建迁移使用 CLI 创建迁移# 创建新迁移npm run typeorm migration:generate -- -n CreateUserTable# 或者使用 npxnpx typeorm migration:generate -n CreateUserTable# 创建空迁移npm run typeorm migration:create -- -n CreateUserTable配置 DataSourceimport { DataSource } from 'typeorm';export const AppDataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: ['src/entity/**/*.ts'], migrations: ['src/migration/**/*.ts'], subscribers: ['src/subscriber/**/*.ts'], synchronize: false, // 生产环境必须设为 false logging: true,});运行迁移使用 CLI 运行迁移# 运行所有待执行的迁移npm run typeorm migration:run# 回滚最后一次迁移npm run typeorm migration:revert# 显示迁移状态npm run typeorm migration:show# 清空数据库(慎用)npm run typeorm schema:drop在代码中运行迁移import { DataSource } from 'typeorm';async function runMigrations() { const dataSource = new DataSource({ type: 'mysql', host: 'localhost', port: 3306, username: 'root', password: 'password', database: 'myapp', entities: ['src/entity/**/*.ts'], migrations: ['src/migration/**/*.ts'], }); await dataSource.initialize(); // 运行所有待执行的迁移 await dataSource.runMigrations(); // 回滚最后一次迁移 // await dataSource.undoLastMigration(); await dataSource.destroy();}runMigrations().catch(console.error);迁移操作示例创建表public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.createTable( new Table({ name: 'user', columns: [ { name: 'id', type: 'int', isPrimary: true, isGenerated: true, generationStrategy: 'increment', }, { name: 'name', type: 'varchar', length: '100', }, { name: 'email', type: 'varchar', length: '255', isUnique: true, }, { name: 'age', type: 'int', nullable: true, }, { name: 'isActive', type: 'boolean', default: true, }, { name: 'createdAt', type: 'timestamp', default: 'CURRENT_TIMESTAMP', }, { name: 'updatedAt', type: 'timestamp', default: 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP', }, ], indices: [ { name: 'IDX_USER_EMAIL', columnNames: ['email'], }, ], }), true );}添加列public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.addColumn( 'user', new TableColumn({ name: 'avatar', type: 'varchar', length: '255', isNullable: true, }) );}public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropColumn('user', 'avatar');}修改列public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.changeColumn( 'user', 'name', new TableColumn({ name: 'name', type: 'varchar', length: '200', // 修改长度 }) );}public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.changeColumn( 'user', 'name', new TableColumn({ name: 'name', type: 'varchar', length: '100', }) );}创建索引public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.createIndex( 'user', new TableIndex({ name: 'IDX_USER_EMAIL', columnNames: ['email'], isUnique: true, }) );}public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropIndex('user', 'IDX_USER_EMAIL');}添加外键public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.createForeignKey( 'post', new TableForeignKey({ columnNames: ['authorId'], referencedColumnNames: ['id'], referencedTableName: 'user', onDelete: 'CASCADE', }) );}public async down(queryRunner: QueryRunner): Promise<void> { const table = await queryRunner.getTable('post'); const foreignKey = table.foreignKeys.find( fk => fk.columnNames.indexOf('authorId') !== -1 ); await queryRunner.dropForeignKey('post', foreignKey);}执行原生 SQLpublic async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(` CREATE TRIGGER update_user_timestamp BEFORE UPDATE ON user FOR EACH ROW SET NEW.updatedAt = CURRENT_TIMESTAMP `);}public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(`DROP TRIGGER update_user_timestamp`);}数据迁移迁移现有数据public async up(queryRunner: QueryRunner): Promise<void> { // 添加新列 await queryRunner.addColumn( 'user', new TableColumn({ name: 'fullName', type: 'varchar', length: '200', isNullable: true, }) ); // 迁移数据 await queryRunner.query(` UPDATE user SET fullName = CONCAT(firstName, ' ', lastName) `); // 删除旧列 await queryRunner.dropColumn('user', 'firstName'); await queryRunner.dropColumn('user', 'lastName');}批量插入数据public async up(queryRunner: QueryRunner): Promise<void> { const users = [ { name: 'John', email: 'john@example.com' }, { name: 'Jane', email: 'jane@example.com' }, ]; for (const user of users) { await queryRunner.query( `INSERT INTO user (name, email) VALUES (?, ?)`, [user.name, user.email] ); }}迁移最佳实践1. 版本控制// 迁移文件命名格式: {timestamp}-{name}.ts// 例如: 1234567890123-CreateUserTable.tsexport class CreateUserTable1234567890123 implements MigrationInterface { // 迁移内容}2. 可逆性确保每个迁移都可以完全回滚:export class AddUserAvatar1234567890123 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.addColumn('user', new TableColumn({ name: 'avatar', type: 'varchar', isNullable: true, })); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.dropColumn('user', 'avatar'); }}3. 事务支持public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.startTransaction(); try { await queryRunner.createTable(/* ... */); await queryRunner.addColumn(/* ... */); await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; }}4. 环境区分export class AddProductionIndex1234567890123 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { // 只在生产环境执行 if (process.env.NODE_ENV === 'production') { await queryRunner.createIndex(/* ... */); } } public async down(queryRunner: QueryRunner): Promise<void> { if (process.env.NODE_ENV === 'production') { await queryRunner.dropIndex(/* ... */); } }}常见问题1. 迁移冲突当多个开发者同时创建迁移时可能出现冲突:# 解决方法:重新生成迁移npm run typeorm migration:generate -- -n FixMigrationConflict2. 数据丢失风险在修改列类型或删除列前备份数据:public async up(queryRunner: QueryRunner): Promise<void> { // 备份数据 await queryRunner.query(`CREATE TABLE user_backup AS SELECT * FROM user`); // 执行迁移 await queryRunner.changeColumn(/* ... */);}3. 性能问题对于大数据表的迁移,考虑分批处理:public async up(queryRunner: QueryRunner): Promise<void> { const batchSize = 1000; let offset = 0; while (true) { const users = await queryRunner.query( `SELECT id FROM user LIMIT ${batchSize} OFFSET ${offset}` ); if (users.length === 0) break; for (const user of users) { await queryRunner.query(`UPDATE user SET ... WHERE id = ?`, [user.id]); } offset += batchSize; }}生产环境建议禁用 synchronize: 生产环境必须设置 synchronize: false备份策略: 执行迁移前备份数据库测试环境: 先在测试环境验证迁移回滚计划: 准备好回滚方案监控日志: 监控迁移执行日志分步执行: 对于大型迁移,分步骤执行TypeORM 的迁移系统提供了强大而灵活的数据库结构管理能力,掌握迁移系统的使用对于维护大型应用的数据库结构至关重要。
阅读 0·2月17日 23:49