Kỹ Thuật Xử Lý Xuất Nhập Dữ Liệu Và Khắc Phục Lỗi Trên Extension Trình Duyệt

Tổng Quan Kiến Trúc Luồng Dữ Liệu

Cơ chế sao lưu và khôi phục thông tin trong extension trình duyệt phụ thuộc chặt chẽ vào việc serialization và deserialization qua định dạng JSON. Quá trình xuất dữ liệu bao gồm việc truy xuất song song các bảng ghi từ IndexedDB, chuẩn hóa schema, rồi gói gọn thành đối tượng duy nhất để lưu xuống disk thông qua File System Access API hoặc Blob URL. Ngược lại, luồng nhập dữ liệu đọc raw bytes, parse sang cấu trúc cây, xác thực ràng buộc kiểu dữ liệu, sau đó push tuần tự vào cơ sở dữ liệu client-side. Do hoạt động trên môi trường sandbox của trình duyệt, các bước này yêu cầu quản lý bộ nhớ nghiêm ngặt và xử lý bất đồng bộ rõ ràng.

Phân Tích Nguyên Nhân Gốc Rễ Của Các Điểm Tắc

  • Sai lệch trong làm phẳng mảng lồng: Hàm tiền xử lý dữ liệu vector và knowledge ghi nhầm biến đích khi gọi đệ quy, dẫn đến thay đổi trực tiếp tham số đầu vào hoặc trả về mảng rỗng.
  • Vượt ngưỡng giao dịch cơ sở dữ liệu: Việc nhóm tất cả thao tác chèn/xóa vào một transaction đơn lẻ dễ chạm giới hạn bộ nhớ tạm thời của engine WebSQL/IndexedDB, gây fail ngầm và roll back nửa chừng.
  • Tắc nghẽn bộ nhớ chính: Đọc nguyên vẹn nội dung tệp lớn vào memory buffer một lần khiến trình duyệt treo hoặc crash tab, đặc biệt khi backup chứa lịch sử chat và embedding vectors.
  • Hệ thống báo lỗi sơ khai: Khi gặp JSON malformed hoặc schema lệch version, hệ thống chỉ ném lỗi chung chung, không ánh xạ được vị trí vi phạm để developer hay user khắc phục.

Giải Pháp Kỹ Thuật Và Tối Ưu Mã Nguồn

1. Chuẩn Hóa Bộ Chuyển Đổi Dữ Liệu

Thay vì lặp vòng for truyền thống, áp dụng hàm hỗ trợ đệ quy để tách riêng phần logic duyệt cây và phần lọc điều kiện, giúp tránh mutate dữ liệu gốc.

// Tiền xử lý mảng Knowledge
export const filterKnowledgeEntries = (rawList: unknown[]): Record[] => {
  const extracted: Record[] = [];

  const walkTree = (current: unknown) => {
    if (Array.isArray(current)) {
      current.forEach(walkTree);
    } else if (typeof current === 'object' && current !== null && (current as any).db_type === 'knowledge') {
      extracted.push(current as Record);
    }
  };

  walkTree(rawList);
  return extracted;
};

// Tiền xử lý mảng Vectors
export const sanitizeVectorNodes = (rawList: unknown[]): Record[] => {
  const validated: Record[] = [];

  const walkTree = (current: unknown) => {
    if (Array.isArray(current)) {
      current.forEach(walkTree);
    } else if (typeof current === 'object' && current !== null && Array.isArray((current as any).vectors)) {
      validated.push(current as Record);
    }
  };

  walkTree(rawList);
  return validated;
};

2. Thực Thi Nhập Tuần Tự Theo Bước

Tách từng loại entity thành task riêng biệt, mỗi task khởi tạo transaction độc lập. Điều này giảm thiểu rủi ro lock contention và giúp trace log chính xác hơn.

type ImportTask = {
  field: string;
  transform?: (data: unknown[]) => unknown[];
  handler: (payload: unknown[], options: any) => Promise<void>;
  storeName: string;
};

export const executeSequentialImport = async (fileHandle: File): Promise<boolean> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = async (evt: ProgressEvent) => {
      try {
        const jsonText = evt.target?.result as string;
        if (!jsonText.trim()) throw new Error('Không phát hiện nội dung');

        const payload: Record<string, unknown> = JSON.parse(jsonText);

        const pipeline: ImportTask[] = [
          { field: 'knowledge', transform: filterKnowledgeEntries, handler: bulkSaveKnowledge, storeName: 'kb_store' },
          { field: 'chat_history', handler: syncChatLogs, storeName: 'chat_db' },
          { field: 'vectors', transform: sanitizeVectorNodes, handler: ingestVectors, storeName: 'vec_index' },
          { field: 'prompts', handler: savePrompts, storeName: 'prompt_cfg' },
          { field: 'oai_settings', handler: applyOAIConfigs, storeName: 'model_prefs' },
          { field: 'nicknames', handler: mapNicknames, storeName: 'alias_tbl' },
          { field: 'ai_models', handler: registerModels, storeName: 'model_registry' }
        ];

        for (const task of pipeline) {
          if (Array.isArray(payload[task.field])) {
            const sanitized = task.transform
              ? task.transform(payload[task.field] as unknown[])
              : payload[task.field] as unknown[];

            await db.transaction('readwrite', task.storeName, async () => {
              await task.handler(sanitized, {});
            });
          }
        }
        resolve(true);
      } catch (err) {
        console.error('Pipeline execution failed:', err);
        reject(new Error(`Quy trình nhập thất bại: ${(err as Error).message}`));
      }
    };

    reader.onerror = () => reject(reader.error);
    reader.readAsText(fileHandle);
  });
};

3. Ràng Buộc Dung Lượng Và Xác Thực Schema

Thêm lớp kiểm tra kích thước trước khi đọc stream, kết hợp kiểm tra presence của các key bắt buộc. Việc này ngăn chặn tình huống OOM (Out Of Memory) và reject nhanh những file corrupt.

const MAX_ALLOWED_PAYLOAD = 10 * 1024 * 1024; // 10 MB
const REQUIRED_ENTITY_KEYS = ['knowledge', 'chat_history', 'vectors', 'prompts', 'oai_settings', 'nicknames', 'ai_models'];

export const validateAndParseBackup = async (fileHandle: File) => {
  if (fileHandle.size > MAX_ALLOWED_PAYLOAD) {
    throw new RangeError('Tệp vượt ngưỡng 10MB. Vui lòng nén hoặc xuất phiên bản cũ.');
  }

  const textBuffer = await fileHandle.text();
  if (!textBuffer.trim()) throw new Error('Nguồn backup trống');

  let structuredData: Record<string, unknown>;
  try {
    structuredData = JSON.parse(textBuffer);
  } catch (parseErr) {
    throw new SyntaxError('Dấu ngoặc hoặc ký tự escape trong file không chuẩn JSON');
  }

  const hasExpectedStructure = REQUIRED_ENTITY_KEYS.some(
    key => Array.isArray(structuredData[key])
  );

  if (!hasExpectedStructure) {
    throw new TypeError('Thiếu ít nhất một trường dữ liệu yêu cầu trong schema');
  }

  return structuredData;
};

Chiến Lược Triển Khai Và Kiểm Thử

Bổ Sung Thông Tin Phiên Bản & Giám Sát Tiến Độ

Gắn metadata `schemaVersion` và `backupTimestamp` vào root object khi xuất, giúp module import phát hiện nhanh sự chênh lệch giữa hai build extension. Đồng thời, expose callback update tiến trình UI dựa trên số lượng task đã hoàn tất.

const TOTAL_PIPELINE_STEPS = 7;
let completedTasks = 0;

for (const task of pipeline) {
  // ... processing logic ...
  completedTasks++;
  onProgressUpdate(Math.floor((completedTasks / TOTAL_PIPELINE_STEPS) * 100));
}

Kế Hoạch Kiểm Thử Tự Động

  • Unit Test: Xác thực các hàm transformer với đầu vào mảng lồng cấp 3层级, đảm bảo trả về mảng phẳng đúng điều kiện filter và không mutate input.
  • Integration Test: Tạo fixture file JSON hợp lệ, chạy pipeline đầy đủ rồi query ngược lại IndexedDB để verify checksum record count và primary keys.
  • Edge Case: Nhặt file rỗng, file có cấu trúc JSON sai bracket, file vượt kích thước, và file thiếu field bắt buộc. Đảm bảo hệ thống throw error cụ thể thay vì crash tab.
  • Rollback Simulation: Ngắt nguồn đọc giữa chừng hoặc simulate DB constraint violation ở bước thứ 4, kiểm tra xem dữ liệu đã nhập trước đó có bị truncate hay không. Giao dịch cô lập nên chỉ task fail bị revert, task pass giữ nguyên.

Thẻ: IndexedDB JavaScript Browser Extensions Data Serialization Asynchronous Workflows

Đăng vào ngày 24 tháng 10 lúc 16:28