在跨平台应用开发中,实现 Web 端与 Native(原生)端的数据共享与一致体验,核心在于构建统一的本地存储架构。以下是基于 SQLite 与 IndexedDB 实现跨端数据共享的完整架构:

一、 核心架构:统一存储与分层抽象

为了屏蔽不同平台的底层差异,推荐采用分层抽象的设计理念,构建多端通用的存储解决方案:

  • 统一 API 层:对外提供标准化的数据操作接口(如 setItemgetItem),使业务层无需关心底层实现。
  • 适配层(Adapter):根据当前运行环境自动探测并切换存储引擎。例如,在 H5 环境中使用 IndexedDB 引擎,在 React Native 或鸿蒙原生环境中使用 SQLite 引擎。
  • 存储引擎层:封装底层数据库的具体实现。对于 Native 端,直接调用原生 SQLite;对于 Web 端,则借助 WebAssembly 技术在浏览器中运行 SQLite。
1. 标准化存储接口定义
// storage/IStorageEngine.ts
export interface IStorageEngine {
  // 初始化数据库连接
  initialize(): Promise<void>;
  // 执行不返回结果集的 SQL(INSERT, UPDATE, DELETE)
  run(sql: string, params?: any[]): Promise<void>;
  // 执行查询并返回对象数组
  query(sql: string, params?: any[]): Promise<Record<string, any>[]>;
  // 关闭数据库连接
  close(): Promise<void>;
}
2. 存储引擎工厂(环境探测与路由)
// storage/StorageFactory.ts
import { IStorageEngine } from './IStorageEngine';
import { WebSqliteEngine } from './WebSqliteEngine';
// import { NativeSqliteEngine } from './NativeSqliteEngine'; // Native端引入

export class StorageFactory {
  static async createEngine(): Promise<IStorageEngine> {
    // 探测当前运行环境
    if (typeof window !== 'undefined' && window.indexedDB) {
      console.log('[Storage] 检测到 H5 环境,加载 Web SQLite 引擎');
      const engine = new WebSqliteEngine();
      await engine.initialize();
      return engine;
    }
    // 其他环境(如 React Native / 鸿蒙)路由至原生 SQLite
    // return new NativeSqliteEngine();
    
    throw new Error('当前环境不支持数据库存储');
  }
}

二、 Web 端方案:SQLite 虚拟化与 WebAssembly

传统 SQLite 依赖原生 C 编译,无法直接在浏览器中运行。现代跨端架构通过 WebAssembly (WASM) 解决了这一痛点:

  • sql.js 引擎:将 SQLite 的 C 源码通过 Emscripten 编译为 .wasm 文件,配合一层 JS Wrapper 暴露 API。这使得同一份 SQL 引擎可以零原生依赖地跑在浏览器环境中。
  • 内存与持久化:WASM 版的 SQLite 默认在内存中操作数据库以保证极快的读写速度。由于浏览器无法直接访问本地文件系统,需通过 IndexedDB 将导出的二进制数据进行持久化存储,从而实现离线查看与断网恢复。
  • 跨环境一致性:这种“虚拟化”处理方式统一了前端与原生端的数据库使用模式,开发者可以使用原生 SQL 形式进行读写,大幅减少了多端差异带来的维护成本。
1. Web 端 SQLite 引擎完整实现
// storage/WebSqliteEngine.ts
import initSqlJs, { type Database } from 'sql.js';

const DB_NAME = 'cross_platform_db';
const DB_STORE = 'sqlite_data';

export class WebSqliteEngine implements IStorageEngine {
  private db: Database | null = null;

  // 1. 初始化:加载 WASM 并从 IndexedDB 恢复数据
  async initialize(): Promise<void> {
    const SQL = await initSqlJs();
    
    // 尝试从 IndexedDB 读取已持久化的二进制数据
    const savedBuffer = await this.loadFromIndexedDB();
    
    if (savedBuffer) {
      this.db = new SQL.Database(savedBuffer);
    } else {
      this.db = new SQL.Database();
      // 首次创建时执行 Schema 迁移
      this.db.run(`CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT, 
        name TEXT NOT NULL, 
        age INTEGER
      )`);
      await this.saveToIndexedDB(); // 初始化后立即持久化
    }
  }

  // 2. 写入操作
  async run(sql: string, params?: any[]): Promise<void> {
    if (!this.db) throw new Error('Database not initialized');
    this.db.run(sql, params);
    await this.saveToIndexedDB(); // 写入后自动持久化
  }

  // 3. 查询操作(将二维数组转换为对象格式)
  async query(sql: string, params?: any[]): Promise<Record<string, any>[]> {
    if (!this.db) throw new Error('Database not initialized');
    const stmt = this.db.prepare(sql);
    if (params) stmt.bind(params);
    
    const results: Record<string, any>[] = [];
    while (stmt.step()) {
      results.push(stmt.getAsObject());
    }
    stmt.free();
    return results;
  }

  async close(): Promise<void> {
    this.db?.close();
    this.db = null;
  }

  // 4. 持久化核心:将内存中的数据库导出并写入 IndexedDB
  private async saveToIndexedDB(): Promise<void> {
    if (!this.db) return;
    const data = this.db.export();
    const buffer = data.buffer as ArrayBuffer;
    
    const dbReq = indexedDB.open(DB_NAME, 1);
    dbReq.onupgradeneeded = (event) => {
      const idb = (event.target as IDBOpenDBRequest).result;
      if (!idb.objectStoreNames.contains(DB_STORE)) {
        idb.createObjectStore(DB_STORE);
      }
    };
    dbReq.onsuccess = (event) => {
      const idb = (event.target as IDBOpenDBRequest).result;
      const tx = idb.transaction(DB_STORE, 'readwrite');
      tx.objectStore(DB_STORE).put(buffer, 'db_blob');
    };
  }

  // 5. 恢复核心:从 IndexedDB 读取二进制数据
  private loadFromIndexedDB(): Promise<Uint8Array | undefined> {
    return new Promise((resolve) => {
      const dbReq = indexedDB.open(DB_NAME, 1);
      dbReq.onsuccess = (event) => {
        const idb = (event.target as IDBOpenDBRequest).result;
        if (!idb.objectStoreNames.contains(DB_STORE)) {
          resolve(undefined);
          return;
        }
        const tx = idb.transaction(DB_STORE, 'readonly');
        const req = tx.objectStore(DB_STORE).get('db_blob');
        req.onsuccess = () => resolve(req.result ? new Uint8Array(req.result) : undefined);
        req.onerror = () => resolve(undefined);
      };
      dbReq.onerror = () => resolve(undefined);
    });
  }
}
2. 业务层调用示例
// main.ts
async function main() {
  const db = await StorageFactory.createEngine();
  
  // 插入数据
  await db.run('INSERT INTO users (name, age) VALUES (?, ?)', ['Alice', 25]);
  
  // 查询数据
  const users = await db.query('SELECT * FROM users WHERE age > ?', [20]);
  console.log('查询结果:', users); 
  // 输出: [{ id: 1, name: 'Alice', age: 25 }]
}

三、 Native 端方案:原生桥接与 ORM 封装

在移动端或桌面端,通常直接调用系统原生的 SQLite 引擎,并通过桥接机制与业务层通信:

  • 原生桥接通信:在 React Native 或鸿蒙 ArkTS 中,通过 JS Bridge 或 NAPI 机制将 JS 层的 SQL 指令异步传递给原生层(如 Android 的 SQLiteDatabase 或 iOS 的 FMDB)执行,并将结果回调给前端。
  • 跨平台 ORM:为避免手动编写大量 SQL 语句,推荐使用 SQLite-NET 或 WatermelonDB 等 ORM 框架。它们通过反射或装饰器自动映射数据模型,生成建表与增删改查语句,并支持 Linq 查询,极大提升了开发效率。
// storage/NativeSqliteEngine.ts
import SQLite from 'react-native-sqlite-storage';

export class NativeSqliteEngine implements IStorageEngine {
  private db: SQLite.SQLiteDatabase | null = null;

  async initialize(): Promise<void> {
    this.db = await SQLite.openDatabase({ name: 'cross_platform.db', location: 'default' });
    await this.executeSql(`CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT, 
      name TEXT NOT NULL, 
      age INTEGER
    )`);
  }

  // 核心优化:批量事务提交,将 N 次 I/O 降为 1 次
  async batchInsert(users: { name: string; age: number }[]): Promise<void> {
    if (!this.db) throw new Error('Database not initialized');
    
    return new Promise((resolve, reject) => {
      this.db!.transaction((tx) => {
        for (const user of users) {
          tx.executeSql(
            'INSERT INTO users (name, age) VALUES (?, ?)',
            [user.name, user.age]
          );
        }
      }, reject, () => resolve());
    });
  }

  async run(sql: string, params?: any[]): Promise<void> {
    await this.executeSql(sql, params);
  }

  async query(sql: string, params?: any[]): Promise<Record<string, any>[]> {
    const results = await this.executeSql(sql, params);
    return results.rows.raw(); // 直接返回对象数组
  }

  private executeSql(sql: string, params?: any[]): Promise<any> {
    return new Promise((resolve, reject) => {
      this.db!.executeSql(sql, params, (_, res) => resolve(res), (_, err) => reject(err));
    });
  }

  async close(): Promise<void> {
    if (this.db) await SQLite.closeDatabase(this.db);
  }
}

四、 性能优化

  • 事务与并发控制:SQLite 支持完整的 ACID 事务,而 IndexedDB 仅支持有限事务。在跨端设计时需避免多事务并发修改,防止 Native 端出现单连接锁或 iOS 端严格的串行化冲突。
  • 查询与缓存策略:针对高频读取场景,应建立多级缓存体系与智能缓存失效策略。对于长列表数据,采用懒加载与数据预取机制,并结合复合索引加速查询。
  • 打包与路径隔离:在 Electron 等桌面应用中,WASM 文件的加载路径需根据打包环境动态配置;在移动端,需注意 Android 与 iOS 默认的数据库存储路径差异,并建议关闭 iCloud 备份以防止数据库文件同步警告。

五、 跨端统一存储架构实战与引擎自动切换

为了在不同平台间提供一致的开发体验,必须构建一套自动探测运行环境并无缝切换底层引擎的适配器机制。

  • 环境探测与引擎路由:在统一 API 层,通过判断全局对象(如 window.indexedDB)或运行平台(如 Platform.OS)来动态实例化对应的存储引擎。例如,在 H5 环境下自动路由至 IndexedDB 引擎,在 React Native 或鸿蒙原生环境中路由至 SQLite 引擎。
  • 标准化数据模型约束:跨端存储的基石是统一的数据格式。建议在工程底层定义严格的实体接口(如包含 idcreatedAtupdatedAt 的 BaseEntity),确保不同存储引擎在序列化与反序列化时保持数据结构的高度一致。
// storage/StorageFactory.ts
import { IStorageEngine } from './IStorageEngine';
import { WebSqliteEngine } from './WebSqliteEngine';
import { NativeSqliteEngine } from './NativeSqliteEngine';

// 标准化数据模型约束
export interface BaseEntity {
  id: number;
  createdAt?: string;
  updatedAt?: string;
}

export class StorageFactory {
  static async createEngine(): Promise<IStorageEngine> {
    let engine: IStorageEngine;
    
    // 环境探测与路由
    if (typeof window !== 'undefined' && window.indexedDB) {
      console.log('[Storage] H5 环境 -> 加载 Web SQLite (WASM)');
      engine = new WebSqliteEngine();
    } else {
      console.log('[Storage] Native 环境 -> 加载原生 SQLite');
      engine = new NativeSqliteEngine();
    }

    await engine.initialize();
    return engine;
  }
}

六、 高性能读写与批量事务优化

在跨端数据交互中,频繁的 I/O 操作是性能杀手,必须从底层引擎和架构层面进行深度优化。

  • 批量事务提交(Batch Operations):单条 INSERT 或 UPDATE 效率极低。在 Native 端,应利用 SQLite 的事务机制将多条写操作合并为一个事务提交;在 Web 端,IndexedDB 同样支持事务批处理。这能大幅减少跨端通信的桥接延迟与磁盘 I/O 次数。
  • 多级缓存与智能预取:构建“内存缓存 → 本地数据库 → 远程服务器”的多级缓存体系。对于高频读取的列表数据,采用懒加载与数据预取机制;同时配合复合索引(如为聊天消息的 sent_at 和 sender_id 建立联合索引)加速复杂查询。
// repository/UserRepository.ts
import { StorageFactory, BaseEntity } from '../storage/StorageFactory';

interface User extends BaseEntity {
  name: string;
  age: number;
}

export class UserRepository {
  private cache: Map<number, User> = new Map(); // 内存级 LRU 缓存
  private db = StorageFactory.createEngine();

  // 智能预取与缓存拦截
  async getUserById(id: number): Promise<User | null> {
    // 1. 命中内存缓存
    if (this.cache.has(id)) return this.cache.get(id)!;

    // 2. 穿透至本地数据库
    const db = await this.db;
    const users = await db.query('SELECT * FROM users WHERE id = ?', [id]);
    
    if (users.length > 0) {
      const user = users[0] as User;
      this.cache.set(id, user); // 回填缓存
      return user;
    }
    return null;
  }

  // 写入时自动失效缓存
  async createUser(name: string, age: number): Promise<void> {
    const db = await this.db;
    await db.run('INSERT INTO users (name, age) VALUES (?, ?)', [name, age]);
    this.cache.clear(); // 简单策略:写操作后清空缓存
  }
}

七、 平台底层差异处理与异常恢复

尽管上层 API 进行了统一,但各平台底层存储引擎的行为差异仍需在适配层进行针对性抹平。

  • 并发写入与锁机制适配:SQLite 在 Android 端支持有限的单连接锁,而在 iOS 端则实行更严格的串行化。在架构设计时,必须避免多事务并发修改同一数据表,防止底层抛出数据库锁定异常。
  • 配额溢出与自动清理:在 Web 端(IndexedDB)或移动端,当存储空间达到浏览器或系统限制时,会抛出 QuotaExceededError。必须在存储错误处理层捕获该异常,并触发智能缓存失效策略(如自动清理过期的旧数据),随后重试当前操作,保障应用不崩溃。

八、 离线优先(Offline-First)与多端数据同步

在弱网或断网环境下,本地数据库应作为应用唯一的“数据真相来源(Truth Source)”。

  • 本地优先与变更队列:应用 UI 直接与本地 SQLite/IndexedDB 交互,实现毫秒级响应。所有的增删改操作在写入本地的同时,推入后台的“同步队列”。
  • 网络恢复与冲突解决:当设备重新联网时,后台同步引擎自动将队列中的变更推送到云端。针对多设备并发修改导致的数据冲突,需在架构层引入 Last-Write-Wins(最后写入优先)或 CRDTs(无冲突复制数据类型)算法,确保跨端数据的最终一致性。
Logo

一站式 AI 云服务平台

更多推荐