👨‍💻🔥前端用IDB缓存大文件, 减少服务端流量压力

524 阅读3分钟

🔍 场景分析 为什么要把大文件存在前端?在某些应用场景下,对于需要频繁加载文件的web应用。 如在线编辑器,文件预览, 文件解析。 这样做。能够给服务器剩下很大一笔带宽。这么做是很有必要的。

使用需求场景代码如下: 前端根据记录的id,获取加载详情需要的本地文件。如果没有我封装的DB模块就会报错,就会走到下面的加载文件的后端接口,并把文件给保存到indexDB里,这样下次就不用走后端了。

// 从本地读取文件
try {
  const dbReadFile = await Db.getValue(`pdfFile:${item.requestId}`)
  if (dbReadFile) {
    setCurPdf(URL.createObjectURL(dbReadFile));
  }
} catch (err) {
  const file = await getFile(item.requestId);
  setCurPdf(URL.createObjectURL(file));
  await Db.setValue(`pdfFile:${item.requestId}`, file)
  await saveFileTimeDb.setValue(item.requestId.toString(), new Date().getTime())
}

🔄 性能考量 前端存储可以减少服务器的负担,加快数据的读写速度,提升用户体验。

🛠️ 为什么选择IndexDB

  • LocalStorage:简单易用,但容量有限。
  • IndexedDB:功能强大,适合存储大量结构化数据。
  • Web SQL(已废弃)和FileSystem API(实验性)。

🔍 IndexedDB是什么? IndexedDB是一个运行在浏览器中的非关系型数据库,它允许网页应用存储大量数据。与传统的cookie和localStorage相比,IndexedDB提供了更复杂的数据存储能力。

🛠️ 主要特点

  • 大量数据存储:可以存储比localStorage更多的数据。
  • 异步操作:不阻塞主线程,提升网页性能。
  • 事务支持:保证数据操作的原子性。
  • 键值对存储:通过键来索引数据,提高数据检索效率。

🔄 使用场景

  • 存储大量数据,如用户数据、缓存的网页内容等。
  • 需要事务支持的场景,确保数据的一致性。

📚 基本操作

  • 打开数据库:使用open方法打开或创建数据库。
  • 创建对象存储:在数据库中创建存储数据的对象存储。
  • 读取和写入数据:通过事务进行数据的读取和写入。
  • 索引:创建索引以优化查询性能。

🌟 为什么选择IDB? 在前端开发中,本地存储是一个常见需求。IDB这个包,提供了比原生IndexDB更简单, 更方便的方法,你不用再担心数据库版本同步问题。它会帮你解决

🛠️ 如何封装IDB? 我将IDB封装成了一个易于使用的CrxIndexDB类,它提供了getValuesetValuedeleteValue三个公共方法,让本地数据操作变得简单快捷。

封装代码代码如下:

import { IDBPDatabase, openDB } from 'idb'

/**
 *  封装indexDB方便background进行本地缓存
 *  暴露三个公共方法(异步调用):
 *  getValue
 *  setValue
 *  deleteValue
 *
 *  同时注册这三个方法的Message消息,便于contentScript调用
 */
class CrxIndexDB {
  private database: string
  private tableName: string
  private db: any

  constructor(database: string, tableName: string) {
    this.database = database
    this.tableName = tableName
    this.createObjectStore()
  }
  public async getAllData() {
    // 打开数据库
    const dbName = this.database;
    const storeName = this.tableName;

    const db = await openDB(dbName, 1, {
      upgrade(db) {
        // 这里假设对象存储已经存在,如果不存在可以在这里创建
        if (!db.objectStoreNames.contains(storeName)) {
          db.createObjectStore(storeName);
        }
      }
    });

    // 获取对象存储中的所有数据
    const tx = db.transaction(storeName, 'readonly');
    const store = tx.objectStore(storeName);
    const allData = await store.getAll();

    await tx.done;
    return allData;
  }
  public async getValue(keyName: string): Promise<any> {
    await this.dbReady()
    const { tableName } = this
    const tx = this.db.transaction(tableName, 'readonly')
    const store = tx.objectStore(tableName)
    const result = await store.get(keyName)
    return result.value
  }

  public async setValue(keyName: string, value: any) {
    await this.dbReady()
    const { tableName } = this
    const tx = this.db.transaction(tableName, 'readwrite')
    const store = tx.objectStore(tableName)
    const result = await store.put({
      keyName,
      value
    })
    return result
  }

  public async deleteValue(keyName: string) {
    await this.dbReady()
    const { tableName } = this
    const tx = this.db.transaction(tableName, 'readwrite')
    const store = tx.objectStore(tableName)
    const result = await store.get(keyName)
    if (!result) {
      return result
    }
    await store.delete(keyName)
    return keyName
  }

  private sleep = (num): Promise<boolean> => {
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(true)
      }, num * 1000)
    })
  }

  private async dbReady() {
    if (!this.db) {
      await this.sleep(0.5)
      return await this.dbReady()
    }
    return true
  }

  private async createObjectStore() {
    const tableName = this.tableName
    try {
      this.db = await openDB(this.database, 1, {
        upgrade(db: IDBPDatabase) {
          if (db.objectStoreNames.contains(tableName)) {
            return
          }
          db.createObjectStore(tableName, {
            keyPath: 'keyName'
          })
        }
      })
    } catch (error) {
      return false
    }
  }
}