Skip to content

數據存儲

uni-app提供了豐富的數據存儲方案,支持本地存儲、文件存儲、數據庫存儲等多種方式。本文將詳細介紹各種存儲方法的使用和最佳實踐。

基礎存儲API

uni-app提供了一套統一的存儲API,在不同平台上會自動適配到對應的存儲機制:

異步存儲

javascript
// 存儲數據
uni.setStorage({
  key: 'userInfo',
  data: {
    name: '張三',
    age: 25,
    email: 'zhangsan@example.com'
  },
  success: function() {
    console.log('存儲成功')
  },
  fail: function(err) {
    console.error('存儲失敗:', err)
  }
})

// 獲取數據
uni.getStorage({
  key: 'userInfo',
  success: function(res) {
    console.log('用戶信息:', res.data)
  },
  fail: function(err) {
    console.error('獲取失敗:', err)
  }
})

// 移除數據
uni.removeStorage({
  key: 'userInfo',
  success: function() {
    console.log('移除成功')
  },
  fail: function(err) {
    console.error('移除失敗:', err)
  }
})

// 清空所有存儲
uni.clearStorage({
  success: function() {
    console.log('清空成功')
  },
  fail: function(err) {
    console.error('清空失敗:', err)
  }
})

// 獲取存儲信息
uni.getStorageInfo({
  success: function(res) {
    console.log('存儲信息:', res)
    console.log('已使用空間:', res.currentSize)
    console.log('總空間:', res.limitSize)
    console.log('所有鍵名:', res.keys)
  }
})

同步存儲

javascript
// 同步存儲數據
try {
  uni.setStorageSync('userInfo', {
    name: '李四',
    age: 30,
    email: 'lisi@example.com'
  })
  console.log('存儲成功')
} catch (e) {
  console.error('存儲失敗:', e)
}

// 同步獲取數據
try {
  const userInfo = uni.getStorageSync('userInfo')
  if (userInfo) {
    console.log('用戶信息:', userInfo)
  } else {
    console.log('未找到用戶信息')
  }
} catch (e) {
  console.error('獲取失敗:', e)
}

// 同步移除數據
try {
  uni.removeStorageSync('userInfo')
  console.log('移除成功')
} catch (e) {
  console.error('移除失敗:', e)
}

// 同步清空所有存儲
try {
  uni.clearStorageSync()
  console.log('清空成功')
} catch (e) {
  console.error('清空失敗:', e)
}

// 同步獲取存儲信息
try {
  const res = uni.getStorageInfoSync()
  console.log('存儲信息:', res)
} catch (e) {
  console.error('獲取存儲信息失敗:', e)
}

存儲限制

不同平台對存儲空間有不同的限制:

各平台存儲限制

平台存儲限制說明
微信小程序10MB單個key最大1MB
支付寶小程序10MB單個key最大200KB
百度小程序10MB單個key最大1MB
字節跳動小程序10MB單個key最大1MB
H55-10MB取決於瀏覽器localStorage限制
App無限制受設備存儲空間限制

支持的數據類型

uni-app存儲支持以下數據類型:

javascript
// 字符串
uni.setStorageSync('string', 'Hello World')

// 數字
uni.setStorageSync('number', 123)

// 布爾值
uni.setStorageSync('boolean', true)

// 對象
uni.setStorageSync('object', {
  name: '張三',
  age: 25,
  hobbies: ['讀書', '運動', '旅行']
})

// 數組
uni.setStorageSync('array', [1, 2, 3, 4, 5])

// 日期對象
uni.setStorageSync('date', new Date())

// null值
uni.setStorageSync('null', null)

注意:存儲的數據會自動進行JSON序列化和反序列化,因此不支持函數、undefined等特殊類型。

存儲封裝與管理

為了更好地管理存儲數據,建議封裝一個存儲管理模塊:

javascript
// storage.js

/**
 * 存儲管理類
 */
class Storage {
  /**
   * 設置存儲
   * @param {string} key 鍵名
   * @param {any} data 數據
   * @param {number} expires 過期時間(毫秒),不設置則永不過期
   * @returns {boolean} 是否成功
   */
  set(key, data, expires) {
    try {
      const item = {
        data,
        expires: expires ? Date.now() + expires : null,
        timestamp: Date.now()
      }
      
      uni.setStorageSync(key, item)
      return true
    } catch (e) {
      console.error('存儲失敗:', e)
      return false
    }
  }
  
  /**
   * 獲取存儲
   * @param {string} key 鍵名
   * @param {any} defaultValue 默認值,當獲取失敗或已過期時返回
   * @returns {any} 存儲的數據
   */
  get(key, defaultValue = null) {
    try {
      const item = uni.getStorageSync(key)
      
      if (!item) {
        return defaultValue
      }
      
      // 檢查是否過期
      if (item.expires && item.expires < Date.now()) {
        this.remove(key)
        return defaultValue
      }
      
      return item.data
    } catch (e) {
      console.error('獲取存儲失敗:', e)
      return defaultValue
    }
  }
  
  /**
   * 移除存儲
   * @param {string} key 鍵名
   * @returns {boolean} 是否成功
   */
  remove(key) {
    try {
      uni.removeStorageSync(key)
      return true
    } catch (e) {
      console.error('移除存儲失敗:', e)
      return false
    }
  }
  
  /**
   * 清空所有存儲
   * @returns {boolean} 是否成功
   */
  clear() {
    try {
      uni.clearStorageSync()
      return true
    } catch (e) {
      console.error('清空存儲失敗:', e)
      return false
    }
  }
  
  /**
   * 獲取所有鍵名
   * @returns {Array<string>} 鍵名數組
   */
  keys() {
    try {
      const res = uni.getStorageInfoSync()
      return res.keys || []
    } catch (e) {
      console.error('獲取鍵名失敗:', e)
      return []
    }
  }
  
  /**
   * 獲取存儲信息
   * @returns {Object} 存儲信息
   */
  info() {
    try {
      return uni.getStorageInfoSync()
    } catch (e) {
      console.error('獲取存儲信息失敗:', e)
      return {
        keys: [],
        currentSize: 0,
        limitSize: 0
      }
    }
  }
  
  /**
   * 檢查鍵是否存在
   * @param {string} key 鍵名
   * @returns {boolean} 是否存在
   */
  has(key) {
    try {
      const item = uni.getStorageSync(key)
      
      if (!item) {
        return false
      }
      
      // 檢查是否過期
      if (item.expires && item.expires < Date.now()) {
        this.remove(key)
        return false
      }
      
      return true
    } catch (e) {
      console.error('檢查鍵存在失敗:', e)
      return false
    }
  }
  
  /**
   * 獲取當前存儲大小
   * @returns {number} 存儲大小(KB)
   */
  size() {
    try {
      const res = uni.getStorageInfoSync()
      return res.currentSize || 0
    } catch (e) {
      console.error('獲取存儲大小失敗:', e)
      return 0
    }
  }
  
  /**
   * 獲取存儲限制大小
   * @returns {number} 限制大小(KB)
   */
  limitSize() {
    try {
      const res = uni.getStorageInfoSync()
      return res.limitSize || 0
    } catch (e) {
      console.error('獲取存儲限制失敗:', e)
      return 0
    }
  }
  
  /**
   * 批量設置存儲
   * @param {Object} data 鍵值對對象
   * @param {number} expires 過期時間(毫秒)
   * @returns {boolean} 是否全部成功
   */
  setAll(data, expires) {
    let success = true
    
    Object.keys(data).forEach(key => {
      if (!this.set(key, data[key], expires)) {
        success = false
      }
    })
    
    return success
  }
  
  /**
   * 批量獲取存儲
   * @param {Array<string>} keys 鍵名數組
   * @param {any} defaultValue 默認值
   * @returns {Object} 鍵值對對象
   */
  getAll(keys, defaultValue = null) {
    const result = {}
    
    keys.forEach(key => {
      result[key] = this.get(key, defaultValue)
    })
    
    return result
  }
  
  /**
   * 批量移除存儲
   * @param {Array<string>} keys 鍵名數組
   * @returns {boolean} 是否全部成功
   */
  removeAll(keys) {
    let success = true
    
    keys.forEach(key => {
      if (!this.remove(key)) {
        success = false
      }
    })
    
    return success
  }
  
  /**
   * 清理過期數據
   * @returns {number} 清理的數量
   */
  clearExpired() {
    let count = 0
    
    try {
      const keys = this.keys()
      
      keys.forEach(key => {
        const item = uni.getStorageSync(key)
        
        if (item && item.expires && item.expires < Date.now()) {
          this.remove(key)
          count++
        }
      })
    } catch (e) {
      console.error('清理過期數據失敗:', e)
    }
    
    return count
  }
}

// 創建存儲實例
const storage = new Storage()

export default storage

使用封裝的存儲管理:

javascript
import storage from '@/utils/storage'

// 設置數據(30分鐘過期)
storage.set('userToken', 'abc123', 30 * 60 * 1000)

// 設置永不過期的數據
storage.set('userSettings', {
  theme: 'dark',
  language: 'zh-CN'
})

// 獲取數據
const token = storage.get('userToken')
const settings = storage.get('userSettings', {})

// 檢查數據是否存在
if (storage.has('userToken')) {
  console.log('用戶已登錄')
}

// 批量操作
storage.setAll({
  'key1': 'value1',
  'key2': 'value2',
  'key3': 'value3'
}, 60 * 1000) // 1分鐘過期

const data = storage.getAll(['key1', 'key2', 'key3'])

// 清理過期數據
const expiredCount = storage.clearExpired()
console.log(`清理了${expiredCount}個過期數據`)

// 獲取存儲信息
const info = storage.info()
console.log(`已使用:${info.currentSize}KB,總容量:${info.limitSize}KB`)

加密存儲

對於敏感數據,建議使用加密存儲:

javascript
// crypto-storage.js

import CryptoJS from 'crypto-js'

/**
 * 加密存儲類
 */
class CryptoStorage {
  constructor(secretKey) {
    this.secretKey = secretKey || 'default-secret-key'
  }
  
  /**
   * 加密數據
   * @param {any} data 要加密的數據
   * @returns {string} 加密後的字符串
   */
  encrypt(data) {
    try {
      const jsonStr = JSON.stringify(data)
      const encrypted = CryptoJS.AES.encrypt(jsonStr, this.secretKey).toString()
      return encrypted
    } catch (e) {
      console.error('加密失敗:', e)
      return null
    }
  }
  
  /**
   * 解密數據
   * @param {string} encryptedData 加密的數據
   * @returns {any} 解密後的數據
   */
  decrypt(encryptedData) {
    try {
      const bytes = CryptoJS.AES.decrypt(encryptedData, this.secretKey)
      const decryptedStr = bytes.toString(CryptoJS.enc.Utf8)
      return JSON.parse(decryptedStr)
    } catch (e) {
      console.error('解密失敗:', e)
      return null
    }
  }
  
  /**
   * 設置加密存儲
   * @param {string} key 鍵名
   * @param {any} data 數據
   * @param {number} expires 過期時間(毫秒)
   * @returns {boolean} 是否成功
   */
  set(key, data, expires) {
    try {
      const item = {
        data,
        expires: expires ? Date.now() + expires : null,
        timestamp: Date.now()
      }
      
      const encryptedData = this.encrypt(item)
      
      if (encryptedData) {
        uni.setStorageSync(`encrypted_${key}`, encryptedData)
        return true
      }
      
      return false
    } catch (e) {
      console.error('設置加密存儲失敗:', e)
      return false
    }
  }
  
  /**
   * 獲取加密存儲
   * @param {string} key 鍵名
   * @param {any} defaultValue 默認值
   * @returns {any} 存儲的數據
   */
  get(key, defaultValue = null) {
    try {
      const encryptedData = uni.getStorageSync(`encrypted_${key}`)
      
      if (!encryptedData) {
        return defaultValue
      }
      
      const item = this.decrypt(encryptedData)
      
      if (!item) {
        return defaultValue
      }
      
      // 檢查是否過期
      if (item.expires && item.expires < Date.now()) {
        this.remove(key)
        return defaultValue
      }
      
      return item.data
    } catch (e) {
      console.error('獲取加密存儲失敗:', e)
      return defaultValue
    }
  }
  
  /**
   * 移除加密存儲
   * @param {string} key 鍵名
   * @returns {boolean} 是否成功
   */
  remove(key) {
    try {
      uni.removeStorageSync(`encrypted_${key}`)
      return true
    } catch (e) {
      console.error('刪除加密存儲失敗:', e)
      return false
    }
  }
  
  /**
   * 檢查加密鍵是否存在
   * @param {string} key 鍵名
   * @returns {boolean} 是否存在
   */
  has(key) {
    try {
      const res = uni.getStorageInfoSync()
      return res.keys.includes(`encrypted_${key}`)
    } catch (e) {
      console.error('檢查加密鍵失敗:', e)
      return false
    }
  }
  
  /**
   * 更改密鑰(會重新加密所有數據)
   * @param {string} newSecretKey 新密鑰
   * @returns {boolean} 是否成功
   */
  changeSecretKey(newSecretKey) {
    if (!newSecretKey) {
      return false
    }
    
    try {
      const res = uni.getStorageInfoSync()
      const encryptedKeys = res.keys.filter(key => key.startsWith('encrypted_'))
      
      // 獲取所有加密數據
      const allData = {}
      encryptedKeys.forEach(key => {
        const originalKey = key.replace('encrypted_', '')
        const encryptedData = uni.getStorageSync(key)
        const item = this.decrypt(encryptedData)
        
        if (item) {
          allData[originalKey] = {
            data: item.data,
            expires: item.expires
          }
        }
      })
      
      // 更改密鑰
      const oldSecretKey = this.secretKey
      this.secretKey = newSecretKey
      
      // 重新加密所有數據
      Object.keys(allData).forEach(key => {
        const item = allData[key]
        const encryptedData = this.encrypt({
          data: item.data,
          expires: item.expires
        })
        
        uni.setStorageSync(`encrypted_${key}`, encryptedData)
      })
      
      return true
    } catch (e) {
      // 恢復舊密鑰
      this.secretKey = oldSecretKey
      console.error('更改密鑰失敗:', e)
      return false
    }
  }
}

// 創建加密存儲實例
const cryptoStorage = new CryptoStorage('your-secret-key')

export default cryptoStorage

使用加密存儲:

javascript
import cryptoStorage from '@/utils/crypto-storage'

// 存儲敏感數據(7天過期)
cryptoStorage.set('password', '123456', 7 * 24 * 3600 * 1000)
cryptoStorage.set('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...')

// 獲取敏感數據
const password = cryptoStorage.get('password')
const token = cryptoStorage.get('token')

// 更改密鑰
cryptoStorage.changeSecretKey('new-secret-key')

文件存儲

對於大型數據或二進制數據,可以使用uni-app提供的文件系統API進行存儲:

保存文件

javascript
// 保存文件到本地
uni.saveFile({
  tempFilePath: tempFilePath, // 需要保存的文件的臨時路徑
  success: function(res) {
    const savedFilePath = res.savedFilePath
    console.log('文件保存成功:', savedFilePath)
    
    // 可以將文件路徑存儲起來,以便後續使用
    uni.setStorageSync('savedFilePath', savedFilePath)
  },
  fail: function(err) {
    console.error('文件保存失敗:', err)
  }
})

獲取保存的文件列表

javascript
uni.getSavedFileList({
  success: function(res) {
    console.log('文件列表:', res.fileList)
    // res.fileList是一個數組,包含filePath和createTime等信息
  },
  fail: function(err) {
    console.error('獲取文件列表失敗:', err)
  }
})

獲取文件信息

javascript
uni.getSavedFileInfo({
  filePath: savedFilePath,
  success: function(res) {
    console.log('文件大小:', res.size)
    console.log('創建時間:', res.createTime)
  },
  fail: function(err) {
    console.error('獲取文件信息失敗:', err)
  }
})

刪除文件

javascript
uni.removeSavedFile({
  filePath: savedFilePath,
  success: function() {
    console.log('文件刪除成功')
  },
  fail: function(err) {
    console.error('文件刪除失敗:', err)
  }
})

文件管理器

對於App平台,可以使用更強大的文件管理器API:

javascript
// 僅在App平台可用
// #ifdef APP-PLUS
const fs = uni.getFileSystemManager()

// 寫入文件
fs.writeFile({
  filePath: `${uni.env.USER_DATA_PATH}/test.txt`,
  data: 'Hello, uni-app!',
  encoding: 'utf8',
  success: function() {
    console.log('寫入成功')
  },
  fail: function(err) {
    console.error('寫入失敗:', err)
  }
})

// 讀取文件
fs.readFile({
  filePath: `${uni.env.USER_DATA_PATH}/test.txt`,
  encoding: 'utf8',
  success: function(res) {
    console.log('文件內容:', res.data)
  },
  fail: function(err) {
    console.error('讀取失敗:', err)
  }
})

// 刪除文件
fs.unlink({
  filePath: `${uni.env.USER_DATA_PATH}/test.txt`,
  success: function() {
    console.log('刪除成功')
  },
  fail: function(err) {
    console.error('刪除失敗:', err)
  }
})
// #endif

IndexedDB存儲

在H5平台,可以使用IndexedDB進行大容量數據存儲:

javascript
// indexed-db.js

/**
 * IndexedDB存儲類(僅H5平台可用)
 */
class IndexedDBStorage {
  constructor(dbName = 'uniapp-db', version = 1) {
    this.dbName = dbName
    this.version = version
    this.db = null
    
    // 僅在H5平台初始化
    // #ifdef H5
    this.init()
    // #endif
  }
  
  /**
   * 初始化數據庫
   * @returns {Promise} 初始化Promise
   */
  init() {
    return new Promise((resolve, reject) => {
      // 檢查瀏覽器是否支持IndexedDB
      if (!window.indexedDB) {
        reject(new Error('瀏覽器不支持IndexedDB'))
        return
      }
      
      const request = window.indexedDB.open(this.dbName, this.version)
      
      request.onerror = (event) => {
        console.error('IndexedDB打開失敗:', event)
        reject(event)
      }
      
      request.onsuccess = (event) => {
        this.db = event.target.result
        console.log('IndexedDB打開成功')
        resolve(this.db)
      }
      
      request.onupgradeneeded = (event) => {
        const db = event.target.result
        
        // 創建存儲對象
        if (!db.objectStoreNames.contains('keyvaluepairs')) {
          db.createObjectStore('keyvaluepairs', { keyPath: 'key' })
        }
      }
    })
  }
  
  /**
   * 確保數據庫已連接
   * @returns {Promise} 數據庫連接Promise
   */
  ensureDB() {
    if (this.db) {
      return Promise.resolve(this.db)
    }
    return this.init()
  }
  
  /**
   * 設置數據
   * @param {string} key 鍵名
   * @param {any} data 數據
   * @param {number} expires 過期時間(毫秒),不設置則永不過期
   * @returns {Promise} 操作Promise
   */
  set(key, data, expires) {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readwrite')
        const store = transaction.objectStore('keyvaluepairs')
        
        const item = {
          key,
          data,
          expires: expires ? Date.now() + expires : null,
          timestamp: Date.now()
        }
        
        const request = store.put(item)
        
        request.onsuccess = () => {
          resolve(true)
        }
        
        request.onerror = (event) => {
          console.error('存儲數據失敗:', event)
          reject(event)
        }
      })
    })
  }
  
  /**
   * 獲取數據
   * @param {string} key 鍵名
   * @param {any} defaultValue 默認值,當獲取失敗或已過期時返回
   * @returns {Promise} 包含數據的Promise
   */
  get(key, defaultValue = null) {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readonly')
        const store = transaction.objectStore('keyvaluepairs')
        const request = store.get(key)
        
        request.onsuccess = (event) => {
          const item = event.target.result
          
          if (!item) {
            resolve(defaultValue)
            return
          }
          
          // 檢查是否過期
          if (item.expires && item.expires < Date.now()) {
            this.remove(key).then(() => {
              resolve(defaultValue)
            }).catch(() => {
              resolve(defaultValue)
            })
            return
          }
          
          resolve(item.data)
        }
        
        request.onerror = (event) => {
          console.error('獲取數據失敗:', event)
          reject(event)
        }
      })
    })
  }
  
  /**
   * 移除數據
   * @param {string} key 鍵名
   * @returns {Promise} 操作Promise
   */
  remove(key) {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readwrite')
        const store = transaction.objectStore('keyvaluepairs')
        const request = store.delete(key)
        
        request.onsuccess = () => {
          resolve(true)
        }
        
        request.onerror = (event) => {
          console.error('刪除數據失敗:', event)
          reject(event)
        }
      })
    })
  }
  
  /**
   * 清空所有數據
   * @returns {Promise} 操作Promise
   */
  clear() {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readwrite')
        const store = transaction.objectStore('keyvaluepairs')
        const request = store.clear()
        
        request.onsuccess = () => {
          resolve(true)
        }
        
        request.onerror = (event) => {
          console.error('清空數據失敗:', event)
          reject(event)
        }
      })
    })
  }
  
  /**
   * 獲取所有鍵
   * @returns {Promise} 包含鍵數組的Promise
   */
  keys() {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readonly')
        const store = transaction.objectStore('keyvaluepairs')
        const request = store.getAllKeys()
        
        request.onsuccess = (event) => {
          resolve(event.target.result || [])
        }
        
        request.onerror = (event) => {
          console.error('獲取鍵失敗:', event)
          reject(event)
        }
      })
    })
  }
  
  /**
   * 獲取所有數據
   * @returns {Promise} 包含所有數據的Promise
   */
  getAll() {
    return this.ensureDB().then(db => {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction(['keyvaluepairs'], 'readonly')
        const store = transaction.objectStore('keyvaluepairs')
        const request = store.getAll()
        
        request.onsuccess = (event) => {
          const items = event.target.result || []
          const result = {}
          
          items.forEach(item => {
            // 檢查是否過期
            if (item.expires && item.expires < Date.now()) {
              this.remove(item.key)
            } else {
              result[item.key] = item.data
            }
          })
          
          resolve(result)
        }
        
        request.onerror = (event) => {
          console.error('獲取所有數據失敗:', event)
          reject(event)
        }
      })
    })
  }
}

// 創建IndexedDB存儲實例
// #ifdef H5
const indexedDBStorage = new IndexedDBStorage()
export default indexedDBStorage
// #endif

使用IndexedDB存儲:

javascript
// #ifdef H5
import indexedDBStorage from '@/utils/indexed-db'

// 存儲數據
indexedDBStorage.set('largeData', largeDataObject)
  .then(() => {
    console.log('大型數據存儲成功')
  })
  .catch(err => {
    console.error('存儲失敗:', err)
  })

// 獲取數據
indexedDBStorage.get('largeData')
  .then(data => {
    console.log('獲取大型數據成功:', data)
  })
  .catch(err => {
    console.error('獲取失敗:', err)
  })
// #endif

數據同步與備份

為了確保用戶數據安全,可以實現數據同步與備份功能:

雲端同步

javascript
// sync.js

import request from '@/utils/request'
import storage from '@/utils/storage'

/**
 * 數據同步類
 */
class DataSync {
  constructor() {
    this.syncKeys = ['userSettings', 'favorites', 'history']
    this.lastSyncTime = storage.get('lastSyncTime', 0)
  }
  
  /**
   * 上傳數據到雲端
   * @returns {Promise} 同步Promise
   */
  uploadToCloud() {
    // 獲取需要同步的數據
    const syncData = {}
    
    this.syncKeys.forEach(key => {
      syncData[key] = storage.get(key)
    })
    
    // 添加同步時間戳
    syncData.timestamp = Date.now()
    
    // 上傳到服務器
    return request.post('/user/sync', syncData)
      .then(() => {
        // 更新最後同步時間
        this.lastSyncTime = syncData.timestamp
        storage.set('lastSyncTime', this.lastSyncTime)
        
        console.log('數據上傳成功')
        return true
      })
      .catch(err => {
        console.error('數據上傳失敗:', err)
        throw err
      })
  }
  
  /**
   * 從雲端下載數據
   * @returns {Promise} 同步Promise
   */
  downloadFromCloud() {
    // 獲取服務器數據
    return request.get('/user/sync', { lastSyncTime: this.lastSyncTime })
      .then(data => {
        if (!data || !data.timestamp) {
          console.log('沒有新數據需要同步')
          return false
        }
        
        // 檢查服務器數據是否比本地新
        if (data.timestamp <= this.lastSyncTime) {
          console.log('本地數據已是最新')
          return false
        }
        
        // 更新本地數據
        Object.keys(data).forEach(key => {
          if (key !== 'timestamp' && this.syncKeys.includes(key)) {
            storage.set(key, data[key])
          }
        })
        
        // 更新最後同步時間
        this.lastSyncTime = data.timestamp
        storage.set('lastSyncTime', this.lastSyncTime)
        
        console.log('數據下載成功')
        return true
      })
      .catch(err => {
        console.error('數據下載失敗:', err)
        throw err
      })
  }
  
  /**
   * 雙向同步數據
   * @returns {Promise} 同步Promise
   */
  sync() {
    return this.downloadFromCloud()
      .then(() => {
        return this.uploadToCloud()
      })
      .then(() => {
        console.log('數據同步完成')
        return true
      })
      .catch(err => {
        console.error('數據同步失敗:', err)
        throw err
      })
  }
  
  /**
   * 自動同步(定時執行)
   * @param {number} interval 同步間隔(毫秒)
   */
  startAutoSync(interval = 5 * 60 * 1000) {
    // 先執行一次同步
    this.sync()
      .catch(() => {
        // 忽略錯誤
      })
    
    // 設置定時同步
    setInterval(() => {
      this.sync()
        .catch(() => {
          // 忽略錯誤
        })
    }, interval)
  }
}

// 創建數據同步實例
const dataSync = new DataSync()

export default dataSync

使用數據同步:

javascript
import dataSync from '@/utils/sync'

// 手動同步
dataSync.sync()
  .then(() => {
    uni.showToast({
      title: '同步成功',
      icon: 'success'
    })
  })
  .catch(() => {
    uni.showToast({
      title: '同步失敗',
      icon: 'none'
    })
  })

// 啟動自動同步(每5分鐘)
dataSync.startAutoSync()

本地備份與恢復

javascript
// backup.js

import storage from '@/utils/storage'

/**
 * 數據備份類
 */
class DataBackup {
  /**
   * 創建備份
   * @param {Array<string>} keys 要備份的鍵名數組,不提供則備份所有
   * @returns {Object} 備份數據
   */
  createBackup(keys) {
    try {
      // 獲取所有鍵或指定鍵
      const backupKeys = keys || storage.keys()
      
      // 獲取數據
      const backupData = {}
      
      backupKeys.forEach(key => {
        const value = storage.get(key)
        if (value !== null) {
          backupData[key] = value
        }
      })
      
      // 添加備份信息
      const backupInfo = {
        timestamp: Date.now(),
        version: '1.0.0',
        keys: Object.keys(backupData),
        count: Object.keys(backupData).length
      }
      
      const backup = {
        info: backupInfo,
        data: backupData
      }
      
      return backup
    } catch (e) {
      console.error('創建備份失敗:', e)
      return null
    }
  }
  
  /**
   * 保存備份到文件
   * @param {Object} backup 備份數據
   * @returns {Promise} 操作Promise
   */
  saveBackupToFile(backup) {
    return new Promise((resolve, reject) => {
      try {
        if (!backup) {
          reject(new Error('備份數據為空'))
          return
        }
        
        // 轉換為JSON字符串
        const backupStr = JSON.stringify(backup)
        
        // 僅在App平台可用
        // #ifdef APP-PLUS
        const fs = uni.getFileSystemManager()
        const backupPath = `${uni.env.USER_DATA_PATH}/backup_${backup.info.timestamp}.json`
        
        fs.writeFile({
          filePath: backupPath,
          data: backupStr,
          encoding: 'utf8',
          success: () => {
            console.log('備份文件保存成功:', backupPath)
            resolve(backupPath)
          },
          fail: (err) => {
            console.error('備份文件保存失敗:', err)
            reject(err)
          }
        })
        // #endif
        
        // 在H5平台使用下載文件
        // #ifdef H5
        const blob = new Blob([backupStr], { type: 'application/json' })
        const url = URL.createObjectURL(blob)
        const a = document.createElement('a')
        a.href = url
        a.download = `backup_${backup.info.timestamp}.json`
        document.body.appendChild(a)
        a.click()
        document.body.removeChild(a)
        URL.revokeObjectURL(url)
        resolve(a.download)
        // #endif
        
        // 在小程序平台,可以將備份保存到本地存儲
        // #ifdef MP
        const backupKey = `backup_${backup.info.timestamp}`
        storage.set(backupKey, backup)
        resolve(backupKey)
        // #endif
      } catch (e) {
        console.error('保存備份失敗:', e)
        reject(e)
      }
    })
  }
  
  /**
   * 從文件加載備份
   * @param {string} filePath 文件路徑
   * @returns {Promise} 包含備份數據的Promise
   */
  loadBackupFromFile(filePath) {
    return new Promise((resolve, reject) => {
      try {
        // 僅在App平台可用
        // #ifdef APP-PLUS
        const fs = uni.getFileSystemManager()
        
        fs.readFile({
          filePath,
          encoding: 'utf8',
          success: (res) => {
            try {
              const backup = JSON.parse(res.data)
              console.log('備份文件加載成功')
              resolve(backup)
            } catch (e) {
              console.error('解析備份文件失敗:', e)
              reject(e)
            }
          },
          fail: (err) => {
            console.error('讀取備份文件失敗:', err)
            reject(err)
          }
        })
        // #endif
        
        // 在小程序平台,從本地存儲加載備份
        // #ifdef MP
        const backup = storage.get(filePath)
        if (backup) {
          resolve(backup)
        } else {
          reject(new Error('未找到備份'))
        }
        // #endif
        
        // H5平台需要用戶手動選擇文件
        // #ifdef H5
        reject(new Error('H5平台請使用恢復備份方法'))
        // #endif
      } catch (e) {
        console.error('加載備份失敗:', e)
        reject(e)
      }
    })
  }
  
  /**
   * 恢復備份
   * @param {Object} backup 備份數據
   * @param {boolean} override 是否覆蓋現有數據
   * @returns {boolean} 是否成功
   */
  restoreBackup(backup, override = false) {
    try {
      if (!backup || !backup.data || !backup.info) {
        console.error('備份數據無效')
        return false
      }
      
      const { data } = backup
      
      // 恢復數據
      Object.keys(data).forEach(key => {
        // 如果不覆蓋且已存在,則跳過
        if (!override && storage.has(key)) {
          return
        }
        
        storage.set(key, data[key])
      })
      
      console.log('備份恢復成功')
      return true
    } catch (e) {
      console.error('恢復備份失敗:', e)
      return false
    }
  }
  
  /**
   * H5平台從文件選擇器恢復備份
   * @returns {Promise} 操作Promise
   */
  restoreBackupFromFileSelector() {
    return new Promise((resolve, reject) => {
      // 僅在H5平台可用
      // #ifdef H5
      try {
        // 創建文件輸入元素
        const input = document.createElement('input')
        input.type = 'file'
        input.accept = 'application/json'
        
        input.onchange = (event) => {
          const file = event.target.files[0]
          
          if (!file) {
            reject(new Error('未選擇文件'))
            return
          }
          
          const reader = new FileReader()
          
          reader.onload = (e) => {
            try {
              const backup = JSON.parse(e.target.result)
              
              // 恢復備份
              const result = this.restoreBackup(backup, true)
              
              if (result) {
                resolve(backup)
              } else {
                reject(new Error('恢復備份失敗'))
              }
            } catch (err) {
              console.error('解析備份文件失敗:', err)
              reject(err)
            }
          }
          
          reader.onerror = (err) => {
            console.error('讀取備份文件失敗:', err)
            reject(err)
          }
          
          reader.readAsText(file)
        }
        
        // 觸發文件選擇
        document.body.appendChild(input)
        input.click()
        document.body.removeChild(input)
      } catch (e) {
        console.error('恢復備份失敗:', e)
        reject(e)
      }
      // #endif
      
      // 非H5平台
      // #ifndef H5
      reject(new Error('此方法僅在H5平台可用'))
      // #endif
    })
  }
  
  /**
   * 獲取所有備份列表(小程序平台)
   * @returns {Array} 備份列表
   */
  getBackupList() {
    try {
      // 僅在小程序平台可用
      // #ifdef MP
      const keys = storage.keys()
      const backupKeys = keys.filter(key => key.startsWith('backup_'))
      
      const backupList = backupKeys.map(key => {
        const backup = storage.get(key)
        return {
          key,
          info: backup ? backup.info : null
        }
      }).filter(item => item.info)
      
      return backupList
      // #endif
      
      // 非小程序平台
      // #ifndef MP
      return []
      // #endif
    } catch (e) {
      console.error('獲取備份列表失敗:', e)
      return []
    }
  }
}

// 創建數據備份實例
const dataBackup = new DataBackup()

export default dataBackup

使用數據備份與恢復:

javascript
import dataBackup from '@/utils/backup'

// 創建並保存備份
function createBackup() {
  const backup = dataBackup.createBackup()
  
  if (backup) {
    dataBackup.saveBackupToFile(backup)
      .then(path => {
        uni.showToast({
          title: '備份成功',
          icon: 'success'
        })
        console.log('備份路徑:', path)
      })
      .catch(err => {
        uni.showToast({
          title: '備份失敗',
          icon: 'none'
        })
        console.error('備份失敗:', err)
      })
  } else {
    uni.showToast({
      title: '創建備份失敗',
      icon: 'none'
    })
  }
}

// 恢復備份(H5平台)
function restoreBackup() {
  // #ifdef H5
  dataBackup.restoreBackupFromFileSelector()
    .then(() => {
      uni.showToast({
        title: '恢復成功',
        icon: 'success'
      })
    })
    .catch(err => {
      uni.showToast({
        title: '恢復失敗',
        icon: 'none'
      })
      console.error('恢復失敗:', err)
    })
  // #endif
  
  // 小程序平台
  // #ifdef MP
  const backupList = dataBackup.getBackupList()
  
  if (backupList.length === 0) {
    uni.showToast({
      title: '沒有可用的備份',
      icon: 'none'
    })
    return
  }
  
  // 顯示備份列表
  uni.showActionSheet({
    itemList: backupList.map(item => {
      const date = new Date(item.info.timestamp)
      return `${date.toLocaleString()} (${item.info.count}項)`
    }),
    success: (res) => {
      const index = res.tapIndex
      const backup = storage.get(backupList[index].key)
      
      if (backup) {
        const result = dataBackup.restoreBackup(backup, true)
        
        if (result) {
          uni.showToast({
            title: '恢復成功',
            icon: 'success'
          })
        } else {
          uni.showToast({
            title: '恢復失敗',
            icon: 'none'
          })
        }
      }
    }
  })
  // #endif
}

最佳實踐

性能優化

  1. 批量操作:盡量減少存儲操作次數,使用批量讀寫
  2. 延遲寫入:對於頻繁變化的數據,可以使用防抖或節流技術延遲寫入
  3. 壓縮數據:對於大型數據,可以使用壓縮算法減小體積
  4. 分片存儲:將大型數據分割成多個小塊存儲,避免超出單個鍵的大小限制
javascript
// 延遲寫入示例
function debounce(func, wait) {
  let timeout
  return function(...args) {
    clearTimeout(timeout)
    timeout = setTimeout(() => {
      func.apply(this, args)
    }, wait)
  }
}

// 使用延遲寫入保存用戶設置
const saveSettings = debounce((settings) => {
  storage.set('settings', settings)
}, 500)

// 用戶更改設置時調用
function updateSettings(key, value) {
  const settings = storage.get('settings', {})
  settings[key] = value
  saveSettings(settings)
}

安全建議

  1. 敏感數據加密:使用加密存儲敏感信息
  2. 定期清理:定期清理過期或不必要的數據
  3. 數據驗證:讀取數據時進行驗證,防止使用損壞的數據
  4. 錯誤處理:妥善處理存儲操作中的異常
javascript
// 數據驗證示例
function getUserInfo() {
  try {
    const userInfo = storage.get('userInfo')
    
    // 驗證數據完整性
    if (!userInfo || !userInfo.id || !userInfo.name) {
      console.warn('用戶信息不完整')
      return null
    }
    
    return userInfo
  } catch (e) {
    console.error('獲取用戶信息失敗:', e)
    return null
  }
}

存儲策略

  1. 分級存儲:根據數據重要性和使用頻率選擇不同的存儲方式

    • 關鍵數據:加密存儲
    • 常用數據:本地存儲
    • 大型數據:文件存儲或IndexedDB
    • 臨時數據:內存緩存
  2. 存儲生命週期:為不同類型的數據設置合適的過期時間

    • 會話數據:應用關閉時清除
    • 緩存數據:設置合理的過期時間
    • 用戶數據:長期保存,但提供清除選項
javascript
// 分級存儲示例
class DataManager {
  constructor() {
    // 內存緩存
    this.cache = new Map()
    
    // 本地存儲
    this.storage = storage
    
    // 加密存儲
    this.secureStorage = cryptoStorage
    
    // 清理過期緩存
    setInterval(() => {
      this.cleanExpiredCache()
    }, 60000) // 每分鐘清理一次
  }
  
  // 設置緩存(內存)
  setCache(key, data, expires = 60000) {
    this.cache.set(key, {
      data,
      expires: expires ? Date.now() + expires : null
    })
  }
  
  // 獲取緩存
  getCache(key) {
    const item = this.cache.get(key)
    
    if (!item) {
      return null
    }
    
    if (item.expires && item.expires < Date.now()) {
      this.cache.delete(key)
      return null
    }
    
    return item.data
  }
  
  // 清理過期緩存
  cleanExpiredCache() {
    for (const [key, item] of this.cache.entries()) {
      if (item.expires && item.expires < Date.now()) {
        this.cache.delete(key)
      }
    }
  }
  
  // 設置數據(根據類型選擇存儲方式)
  setData(key, data, options = {}) {
    const { type = 'normal', expires } = options
    
    switch (type) {
      case 'cache':
        // 內存緩存
        this.setCache(key, data, expires)
        break
      case 'secure':
        // 加密存儲
        this.secureStorage.set(key, data, expires)
        break
      case 'normal':
      default:
        // 普通存儲
        this.storage.set(key, data, expires)
        break
    }
  }
  
  // 獲取數據(按優先級查找)
  getData(key, defaultValue = null, type = 'normal') {
    // 先查找內存緩存
    const cachedData = this.getCache(key)
    if (cachedData !== null) {
      return cachedData
    }
    
    // 根據類型查找存儲
    let data = null
    
    switch (type) {
      case 'secure':
        data = this.secureStorage.get(key, defaultValue)
        break
      case 'normal':
      default:
        data = this.storage.get(key, defaultValue)
        break
    }
    
    // 如果找到數據,更新緩存
    if (data !== null && data !== defaultValue) {
      this.setCache(key, data)
    }
    
    return data
  }
}

// 創建數據管理實例
const dataManager = new DataManager()

// 使用示例
dataManager.setData('tempData', { value: 123 }, { type: 'cache', expires: 30000 }) // 30秒過期
dataManager.setData('userData', { name: '張三' }, { type: 'normal' }) // 永不過期
dataManager.setData('password', '123456', { type: 'secure', expires: 7 * 24 * 3600 * 1000 }) // 7天過期

const tempData = dataManager.getData('tempData')
const userData = dataManager.getData('userData')
const password = dataManager.getData('password', null, 'secure')

總結

本文詳細介紹了uni-app中的數據存儲方法和最佳實踐,包括:

  1. 基礎存儲API:uni.setStorage、uni.getStorage等方法的使用
  2. 存儲限制:不同平台的存儲空間限制及應對策略
  3. 存儲封裝與管理:封裝存儲管理模塊,支持過期時間、批量操作等
  4. 加密存儲:敏感數據的加密存儲方案
  5. 文件存儲:大型數據的文件系統存儲方法
  6. IndexedDB存儲:H5平台的大容量數據存儲方案
  7. 數據同步與備份:實現雲端同步和本地備份恢復功能
  8. 最佳實踐:性能優化、安全建議和存儲策略

通過合理使用這些存儲方法和技巧,可以有效管理uni-app應用中的數據,提高應用性能和用戶體驗。在實際開發中,應根據數據特性和業務需求選擇合適的存儲方案,並注意數據安全和性能優化。

一次開發,多端部署 - 讓跨平台開發更簡單