首页
/ DeepChat本地存储方案:better-sqlite3与数据持久化实践

DeepChat本地存储方案:better-sqlite3与数据持久化实践

2026-02-05 05:52:17作者:曹令琨Iris

方案背景与技术选型

DeepChat作为连接AI与个人数据的智能助手,采用better-sqlite3作为本地存储解决方案,通过src/main/presenter/sqlitePresenter/index.ts实现数据持久化。该方案基于SQLCipher加密扩展,提供事务支持、数据迁移和损坏恢复机制,确保用户对话历史、消息内容等核心数据的安全存储。

技术栈组成

  • 核心库:better-sqlite3-multiple-ciphers(SQLCipher扩展版)
  • 数据模型:Conversations/Messages/Attachments三表结构
  • 安全特性:数据库文件加密、WAL模式、自动备份
  • 维护机制:版本迁移、损坏恢复、事务支持

数据库架构设计

核心表结构

DeepChat采用模块化表设计,各表职责明确且通过外键关联:

erDiagram
    CONVERSATIONS ||--o{ MESSAGES : contains
    MESSAGES ||--o{ MESSAGE_ATTACHMENTS : has
    ATTACHMENTS }|--|| MESSAGE_ATTACHMENTS : references
  • Conversations表:存储对话元数据(标题、创建时间、设置)
  • Messages表:存储消息内容(文本、角色、状态、序号)
  • Attachments表:存储文件附件元数据
  • MessageAttachments表:消息与附件的多对多关联

初始化流程

数据库连接建立过程包含错误处理与自动恢复机制,关键步骤如下:

// 核心初始化逻辑 [src/main/presenter/sqlitePresenter/index.ts#L32-L92]
constructor(dbPath: string, password?: string) {
  this.dbPath = dbPath
  try {
    // 确保目录存在
    const dbDir = path.dirname(dbPath)
    if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true })
    
    // 初始化加密连接
    this.db = new Database(dbPath)
    this.db.pragma('journal_mode = WAL')
    if (password) {
      this.db.pragma(`cipher='sqlcipher'`)
      this.db.pragma(`key='${password}'`)
    }
    
    // 表初始化与迁移
    this.initTables()
    this.initVersionTable()
    this.migrate()
  } catch (error) {
    // 损坏恢复流程
    this.backupDatabase()
    this.cleanupDatabaseFiles()
    // 重建数据库
    this.db = new Database(dbPath)
    // ...重新初始化
  }
}

安全存储实现

加密机制

采用SQLCipher实现数据库透明加密,加密密钥通过配置系统安全管理:

// 加密配置 [src/main/presenter/sqlitePresenter/index.ts#L45-L48]
if (password) {
  this.db.pragma(`cipher='sqlcipher'`)
  this.db.pragma(`key='${password}'`)
}

数据备份策略

数据库损坏时自动创建时间戳备份,并清理损坏文件:

// 备份逻辑 [src/main/presenter/sqlitePresenter/index.ts#L98-L110]
private backupDatabase(): void {
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
  const backupPath = `${this.dbPath}.${timestamp}.bak`
  if (fs.existsSync(this.dbPath)) {
    fs.copyFileSync(this.dbPath, backupPath)
    console.log(`Database backed up to: ${backupPath}`)
  }
}

数据操作接口

核心CRUD实现

SQLitePresenter封装了完整的数据操作API,以下为常用操作示例:

创建对话

// [src/main/presenter/sqlitePresenter/index.ts#L215-L220]
public async createConversation(
  title: string,
  settings: Partial<CONVERSATION_SETTINGS> = {}
): Promise<string> {
  return this.conversationsTable.create(title, settings)
}

插入消息

// [src/main/presenter/sqlitePresenter/index.ts#L254-L278]
public async insertMessage(
  conversationId: string,
  content: string,
  role: string,
  parentId: string,
  metadata: string = '{}',
  orderSeq: number = 0,
  tokenCount: number = 0,
  status: string = 'pending',
  isContextEdge: number = 0,
  isVariant: number = 0
): Promise<string> {
  return this.messagesTable.insert(/* 参数 */)
}

事务支持

通过better-sqlite3的事务API确保多操作原子性:

// [src/main/presenter/sqlitePresenter/index.ts#L330-L332]
public async runTransaction(operations: () => void): Promise<void> {
  await this.db.transaction(operations)()
}

版本迁移与维护

增量迁移机制

系统通过版本表追踪 schema 变更,支持多表协同迁移:

// [src/main/presenter/sqlitePresenter/index.ts#L160-L207]
private migrate() {
  const migrations = new Map<number, string[]>()
  // 收集各表迁移脚本
  tables.forEach((table) => {
    for (let version = this.currentVersion + 1; version <= latestVersion; version++) {
      const sql = table.getMigrationSQL?.(version)
      if (sql) {
        migrations.set(version, [...migrations.get(version) || [], sql])
      }
    }
  })
  // 按版本顺序执行迁移
  const versions = Array.from(migrations.keys()).sort((a, b) => a - b)
  for (const version of versions) {
    this.db.transaction(() => {
      migrationSQLs.forEach(sql => this.db.exec(sql))
      this.db.prepare('INSERT INTO schema_versions VALUES (?, ?)')
        .run(version, Date.now())
    })()
  }
}

表版本管理

每个表独立维护版本信息,通过getLatestVersiongetMigrationSQL接口提供迁移脚本:

// 版本管理示例 [src/main/presenter/sqlitePresenter/index.ts#L171-L174]
const latestVersion = tables.reduce((maxVersion, table) => {
  const tableMaxVersion = table.getLatestVersion?.() || 0
  return Math.max(maxVersion, tableMaxVersion)
}, 0)

性能优化策略

WAL模式

默认启用Write-Ahead Logging模式提升并发性能:

// [src/main/presenter/sqlitePresenter/index.ts#L43]
this.db.pragma('journal_mode = WAL')

WAL模式优势:

  • 读操作不阻塞写操作
  • 写操作只阻塞其他写操作
  • 更小的I/O开销

索引设计

各表通过合理索引优化查询性能,例如Messages表的复合索引:

-- 示例索引设计(推测)
CREATE INDEX idx_messages_conversation_order ON messages(conversation_id, order_seq);
CREATE INDEX idx_messages_parent ON messages(parent_id);

实际应用场景

典型数据流向

以用户发送消息为例,完整数据存储流程:

sequenceDiagram
    participant U as User
    participant V as ViewModel
    participant P as SQLitePresenter
    participant M as MessagesTable
    
    U->>V: 发送消息
    V->>P: createConversation(标题)
    P->>P: 生成conversation_id
    V->>P: insertMessage(conversation_id, 内容, "user")
    P->>M: insert(消息数据)
    M-->>P: 返回message_id
    P-->>V: 存储成功

数据恢复案例

当数据库文件损坏时,系统自动执行恢复流程:

1. 创建备份文件(如chat.db.2025-10-23T02-26-17.bak)
2. 删除损坏文件及WAL/SHM文件
3. 重建空数据库并初始化表结构
4. 记录错误日志便于问题排查

扩展与最佳实践

自定义SQL操作

如需执行复杂查询,可通过底层连接直接操作:

// 扩展示例
const customQuery = (conversationId: string) => {
  return this.db.prepare(`
    SELECT m.* FROM messages m
    JOIN conversations c ON m.conversation_id = c.id
    WHERE c.id = ? AND m.status = 'completed'
    ORDER BY m.order_seq DESC LIMIT 1
  `).get(conversationId)
}

备份策略建议

  • 定期完整备份(如每日)
  • 关键操作前触发增量备份
  • 备份文件加密存储
  • 定期测试恢复流程

总结

DeepChat的SQLite存储方案通过模块化设计、安全加密和性能优化,为AI助手提供了可靠的数据持久化基础。核心优势包括:

  1. 安全性:全程加密保护用户隐私数据
  2. 可靠性:完善的错误处理与自动恢复机制
  3. 扩展性:版本化迁移支持持续功能迭代
  4. 性能:WAL模式与索引优化保障操作流畅

完整实现代码参见src/main/presenter/sqlitePresenter/目录,包含表定义、迁移脚本和核心业务逻辑。

登录后查看全文
热门项目推荐
相关项目推荐