防御 AI 幻觉:基于parcel/watcher的文件双版本备份工具

背景

在日常使用AI进行开发和工作中,经常遇到以下场景:

  • AI修改代码后发现改错了,想回退到之前的版本
  • AI上下文溢出导致文件被删除或内容被清空
  • 重要文档被AI误删或误改,找不到历史版本

虽然 Git 可以解决代码版本问题,但是实际工作中是不可能将每次修改都commit,不仅费时费力而且不规范,此时需要一个更轻量的解决方案。

FileBackupGuardian 就是为解决这些问题而设计的桌面应用,它可以:

  • 实时监听指定目录的文件变更
  • 自动创建备份,保留修改前后两个版本
  • 提供差异对比功能,直观查看修改内容
  • 支持一键恢复到任意历史版本

技术选型

类别 技术 选择理由
桌面框架 Electron 29 跨平台支持,生态成熟
前端框架 Vue 3 + TypeScript 组合式 API,类型安全
状态管理 Pinia 轻量,与 Vue 3 深度集成
构建工具 Vite 5 开发体验好,构建速度快
文件监听 @parcel/watcher 性能优异,原生支持递归监听
差异计算 diff-match-patch Google 开源,算法成熟

核心功能实现

1. 实时文件监听

文件监听是整个应用的核心。使用 @parcel/watcher 替代 Node.js 原生的 fs.watch,因为它:

  • 基于 native 实现,性能更好
  • 支持递归监听,无需手动遍历子目录
  • 事件去重机制,避免重复触发
// watcher-manager.ts
import * as parcelWatcher from '@parcel/watcher';

async startWatching(watchPath: WatchPath): Promise<void> {
  const subscription = await parcelWatcher.subscribe(
    watchRoot,
    async (error, events) => {
      if (error) {
        this.updateStatus(watchPath.id, { error: error.message });
        return;
      }
      await this.handleParcelEvents(watchPath, events);
    }
  );
  this.watchers.set(watchPath.id, subscription);
}

2. 双版本备份机制

这是本工具的特色功能。当文件被修改时,我们需要:

  1. 变更前备份:在文件写入前,保存原始内容
  2. 变更后备份:文件写入完成后,保存新版本

这样用户可以对比任意修改前后差异,而不仅仅是对比当前版本和备份版本。

实现的关键是利用文件内容缓存:

// 使用 LRU 缓存存储文件内容(100MB)
private fileContentCache = new LRUCache<string, CacheEntry>(100 * 1024 * 1024);

// 文件变更前,从缓存获取原始内容
private async handleFileChange(filePath, watchPathId, eventType) {
  if (eventType === 'change') {
    const cachedContent = this.fileContentCache.get(filePath);
    if (cachedContent) {
      // 使用缓存内容创建"变更前备份"
      await this.backupManager.createBackupWithContent(filePath, cachedContent);
    }
  }
  // 文件变更后,创建"变更后备份"
  await this.backupManager.createPostChangeBackup(filePath, preBackupId);
}

3. Worker 线程优化

大文件的差异计算和文件计数是耗时操作,如果在主线程执行会阻塞 UI。使用 Worker 线程来解决:

// worker-manager.ts
async computeDiff(originalContent: string, backupContent: string) {
  return new Promise((resolve, reject) => {
    const worker = new Worker(this.workerPath, {
      workerData: { type: 'diff', originalContent, backupContent }
    });
    
    worker.on('message', (message) => {
      if (message.type === 'result') {
        resolve(message.diffs);
        worker.terminate();
      }
    });
  });
}

4. 恢复防循环机制

恢复文件后,文件系统会触发变更事件,可能导致产生冗余备份。通过标记机制来防止:

// backup-manager.ts
async restoreBackup(backupId: string, targetPath?: string): Promise<void> {
  const destination = path.normalize(targetPath || record.originalPath);
  
  // 标记文件为正在恢复状态
  this.restoringFiles.add(destination);
  // 记录到最近恢复列表,用于延迟窗口内的跳过
  this.recentlyRestoredFiles.set(destination, { timestamp: Date.now() });
  
  try {
    await fs.copy(record.backupPath, destination, { overwrite: true });
  } finally {
    this.restoringFiles.delete(destination);
  }
}

// 检查是否应该跳过备份
shouldSkipBackup(filePath: string): boolean {
  if (this.restoringFiles.has(filePath)) return true;
  
  const restoredInfo = this.recentlyRestoredFiles.get(filePath);
  if (restoredInfo && Date.now() - restoredInfo.timestamp < this.restoreSkipWindow) {
    return true;
  }
  return false;
}

5. IPC 通信架构

Electron 的安全模型要求渲染进程不能直接访问 Node.js API。通过 preload 脚本暴露安全的 API:

// preload.ts
const electronAPI = {
  watcher: {
    addPath: (path: string, recursive?: boolean) =>
      ipcRenderer.invoke(IPC_CHANNELS.WATCHER_ADD_PATH, path, recursive),
    getStatus: () => ipcRenderer.invoke(IPC_CHANNELS.WATCHER_GET_STATUS),
    onStatusUpdate: (callback) => {
      ipcRenderer.on(IPC_CHANNELS.WATCHER_STATUS_UPDATE, callback);
      return () => ipcRenderer.removeListener(...);
    },
  },
  backup: {
    getList: (originalPath?: string) =>
      ipcRenderer.invoke(IPC_CHANNELS.BACKUP_GET_LIST, originalPath),
    restore: (backupId: string) =>
      ipcRenderer.invoke(IPC_CHANNELS.BACKUP_RESTORE, backupId),
  },
  compare: {
    pairedBackups: (preBackupId: string) =>
      ipcRenderer.invoke(IPC_CHANNELS.COMPARE_PAIRED_BACKUPS, preBackupId),
  },
};

contextBridge.exposeInMainWorld('electronAPI', electronAPI);

渲染进程通过 window.electronAPI 调用:

// CompareView.vue
const result = await window.electronAPI.compare.pairedBackups(preBackupId);
if (result.success) {
  compareResult.value = result.data.compareResult;
}

界面设计

应用包含四个主要页面:

监听管理页面

  • 显示监听目录统计(目录数、运行中、文件总数)
  • 支持添加/删除监听目录
  • 配置忽略规则(扩展名、文件名、文件夹、正则)
    image

文件跟踪页面

  • 显示指定文件夹的备份历史
  • 按时间分组显示备份记录
  • 支持搜索过滤、批量恢复
    image

对比页面

  • 显示备份对列表(修改前/修改后)
  • 并排显示两个版本的代码差异
  • 高亮新增、删除、未变更的行
  • 支持跳转到差异位置
    image

设置页面

  • 配置备份路径、延迟时间、保留天数
  • 查看和导出操作日志
    image

打包配置支持:

  • Windows:NSIS 安装器 + 便携版
  • macOS:DMG + ZIP
  • Linux:AppImage + deb

总结

FileBackupGuardian 是一个功能完善的文件备份工具,主要特点:

  1. 双版本备份:保留修改前后两个版本,完整记录变更历史
  2. 实时监听:基于 @parcel/watcher 的高性能文件监听
  3. Worker 优化:耗时操作在后台线程执行,保持界面流畅
  4. 恢复防循环:智能跳过恢复触发的备份事件
  5. 跨平台支持:一次开发,支持 Windows、macOS、Linux

项目地址:https://github.com/jclown/FileBackupGuardian ,欢迎 Star 和贡献代码!

posted @ 2026-04-01 13:39  少年知有  阅读(32)  评论(0)    收藏  举报