数据存储
在uni-app开发中,数据存储是应用程序保存和读取本地数据的重要功能。本文将详细介绍uni-app中的数据存储方法、存储限制、加密存储以及最佳实践。
基础存储API
uni-app提供了一系列API用于数据存储,这些API在各平台表现一致,简化了跨平台开发。
uni.setStorage 与 uni.setStorageSync
用于将数据存储在本地缓存中指定的key中,会覆盖掉原来该key对应的内容。
异步方法
uni.setStorage({
key: 'userInfo',
data: {
name: '张三',
age: 25,
gender: '男'
},
success: function() {
console.log('存储成功')
},
fail: function(err) {
console.error('存储失败:', err)
},
complete: function() {
console.log('操作完成')
}
})
同步方法
try {
uni.setStorageSync('userInfo', {
name: '张三',
age: 25,
gender: '男'
})
console.log('存储成功')
} catch (e) {
console.error('存储失败:', e)
}
uni.getStorage 与 uni.getStorageSync
用于从本地缓存中获取指定key对应的内容。
异步方法
uni.getStorage({
key: 'userInfo',
success: function(res) {
console.log('获取成功:', res.data)
},
fail: function(err) {
console.error('获取失败:', err)
}
})
同步方法
try {
const value = uni.getStorageSync('userInfo')
if (value) {
console.log('获取成功:', value)
} else {
console.log('数据不存在')
}
} catch (e) {
console.error('获取失败:', e)
}
uni.removeStorage 与 uni.removeStorageSync
用于从本地缓存中移除指定key。
异步方法
uni.removeStorage({
key: 'userInfo',
success: function() {
console.log('删除成功')
},
fail: function(err) {
console.error('删除失败:', err)
}
})
同步方法
try {
uni.removeStorageSync('userInfo')
console.log('删除成功')
} catch (e) {
console.error('删除失败:', e)
}
uni.clearStorage 与 uni.clearStorageSync
用于清理本地数据缓存。
异步方法
uni.clearStorage()
同步方法
try {
uni.clearStorageSync()
console.log('清除成功')
} catch (e) {
console.error('清除失败:', e)
}
uni.getStorageInfo 与 uni.getStorageInfoSync
用于获取当前storage的相关信息。
异步方法
uni.getStorageInfo({
success: function(res) {
console.log('keys:', res.keys) // 当前storage中所有的key
console.log('currentSize:', res.currentSize) // 当前占用的空间大小, 单位:kb
console.log('limitSize:', res.limitSize) // 限制的空间大小, 单位:kb
},
fail: function(err) {
console.error('获取失败:', err)
}
})
同步方法
try {
const res = uni.getStorageInfoSync()
console.log('keys:', res.keys)
console.log('currentSize:', res.currentSize)
console.log('limitSize:', res.limitSize)
} catch (e) {
console.error('获取失败:', e)
}
存储限制
不同平台对存储空间的限制不同,开发者需要注意:
平台 | 存储限制 |
---|---|
微信小程序 | 10MB |
支付宝小程序 | 10MB |
百度小程序 | 10MB |
字节跳动小程序 | 10MB |
H5 | 根据浏览器不同而不同,一般为5MB左右 |
App | 无明确限制,但建议控制在50MB以内 |
为了避免超出存储限制,建议:
- 定期清理不必要的缓存数据
- 对大型数据进行分片存储
- 使用压缩算法减小数据体积
- 将不常用的大型数据存储在服务器端
数据类型支持
uni-app的存储API支持以下数据类型:
- String(字符串)
- Number(数字)
- Boolean(布尔值)
- Object(对象)
- Array(数组)
- Date(日期)
- null
注意:存储复杂数据类型(如对象、数组)时,会自动进行JSON序列化和反序列化。但是,某些特殊对象(如函数、正则表达式、Error对象等)在序列化过程中会丢失信息。
存储封装与管理
为了更好地管理本地存储,建议封装一个存储管理模块:
// storage.js
/**
* 存储管理类
*/
class Storage {
/**
* 设置存储
* @param {string} key 键名
* @param {any} data 数据
* @param {number} expires 过期时间(毫秒),不设置则永不过期
*/
set(key, data, expires) {
const item = {
data,
expires: expires ? Date.now() + expires : null
}
try {
uni.setStorageSync(key, JSON.stringify(item))
return true
} catch (e) {
console.error('存储失败:', e)
return false
}
}
/**
* 获取存储
* @param {string} key 键名
* @param {any} defaultValue 默认值,当获取失败或已过期时返回
* @returns {any} 存储的数据或默认值
*/
get(key, defaultValue = null) {
try {
const itemStr = uni.getStorageSync(key)
if (!itemStr) {
return defaultValue
}
const item = JSON.parse(itemStr)
// 检查是否过期
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 res = uni.getStorageInfoSync()
return res.keys.includes(key)
} catch (e) {
console.error('检查键失败:', e)
return false
}
}
/**
* 获取存储大小(KB)
* @returns {number} 存储大小
*/
size() {
try {
const res = uni.getStorageInfoSync()
return res.currentSize || 0
} catch (e) {
console.error('获取存储大小失败:', e)
return 0
}
}
/**
* 获取存储限制大小(KB)
* @returns {number} 限制大小
*/
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) {
if (!data || typeof data !== 'object') {
return false
}
let success = true
Object.keys(data).forEach(key => {
const result = this.set(key, data[key], expires)
if (!result) {
success = false
}
})
return success
}
/**
* 批量获取存储
* @param {Array<string>} keys 键名数组
* @returns {Object} 键值对对象
*/
getAll(keys) {
if (!Array.isArray(keys)) {
return {}
}
const result = {}
keys.forEach(key => {
result[key] = this.get(key)
})
return result
}
/**
* 批量移除存储
* @param {Array<string>} keys 键名数组
* @returns {boolean} 是否全部成功
*/
removeAll(keys) {
if (!Array.isArray(keys)) {
return false
}
let success = true
keys.forEach(key => {
const result = this.remove(key)
if (!result) {
success = false
}
})
return success
}
/**
* 清理过期数据
* @returns {number} 清理的数量
*/
clearExpired() {
let count = 0
try {
const keys = this.keys()
keys.forEach(key => {
try {
const itemStr = uni.getStorageSync(key)
if (itemStr) {
const item = JSON.parse(itemStr)
if (item.expires && item.expires < Date.now()) {
this.remove(key)
count++
}
}
} catch (e) {
// 忽略解析错误
}
})
} catch (e) {
console.error('清理过期数据失败:', e)
}
return count
}
}
// 创建存储实例
const storage = new Storage()
export default storage
使用存储管理模块:
import storage from '@/utils/storage'
// 设置数据(1小时过期)
storage.set('token', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 3600 * 1000)
// 设置永久数据
storage.set('settings', { theme: 'dark', fontSize: 16 })
// 获取数据
const token = storage.get('token')
const settings = storage.get('settings', { theme: 'light', fontSize: 14 }) // 提供默认值
// 检查数据是否存在
if (storage.has('userInfo')) {
console.log('用户信息已存在')
}
// 移除数据
storage.remove('token')
// 批量操作
storage.setAll({
name: '张三',
age: 25,
gender: '男'
})
const userInfo = storage.getAll(['name', 'age', 'gender'])
console.log(userInfo) // { name: '张三', age: 25, gender: '男' }
// 清理过期数据
const count = storage.clearExpired()
console.log(`清理了${count}条过期数据`)
// 获取存储信息
const info = storage.info()
console.log(`当前使用:${info.currentSize}KB,限制:${info.limitSize}KB`)
加密存储
对于敏感数据(如用户密码、令牌等),建议进行加密存储。以下是一个简单的加密存储示例:
// crypto-storage.js
import CryptoJS from 'crypto-js' // 需要安装crypto-js库:npm install crypto-js
/**
* 加密存储类
*/
class CryptoStorage {
constructor(secretKey = 'default-secret-key') {
this.secretKey = secretKey
}
/**
* 加密数据
* @param {any} data 要加密的数据
* @returns {string} 加密后的字符串
*/
encrypt(data) {
const dataStr = JSON.stringify(data)
return CryptoJS.AES.encrypt(dataStr, this.secretKey).toString()
}
/**
* 解密数据
* @param {string} ciphertext 加密的字符串
* @returns {any} 解密后的数据
*/
decrypt(ciphertext) {
try {
const bytes = CryptoJS.AES.decrypt(ciphertext, this.secretKey)
const dataStr = bytes.toString(CryptoJS.enc.Utf8)
return JSON.parse(dataStr)
} catch (e) {
console.error('解密失败:', e)
return null
}
}
/**
* 设置加密存储
* @param {string} key 键名
* @param {any} data 数据
* @param {number} expires 过期时间(毫秒),不设置则永不过期
*/
set(key, data, expires) {
const item = {
data,
expires: expires ? Date.now() + expires : null
}
const encryptedData = this.encrypt(item)
try {
uni.setStorageSync(`encrypted_${key}`, encryptedData)
return true
} 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
使用加密存储:
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进行存储:
保存文件
// 保存文件到本地
uni.saveFile({
tempFilePath: tempFilePath, // 需要保存的文件的临时路径
success: function(res) {
const savedFilePath = res.savedFilePath
console.log('文件保存成功:', savedFilePath)
// 可以将文件路径存储起来,以便后续使用
uni.setStorageSync('savedFilePath', savedFilePath)
},
fail: function(err) {
console.error('文件保存失败:', err)
}
})
获取保存的文件列表
uni.getSavedFileList({
success: function(res) {
console.log('文件列表:', res.fileList)
// res.fileList是一个数组,包含filePath和createTime等信息
},
fail: function(err) {
console.error('获取文件列表失败:', err)
}
})
获取文件信息
uni.getSavedFileInfo({
filePath: savedFilePath,
success: function(res) {
console.log('文件大小:', res.size)
console.log('创建时间:', res.createTime)
},
fail: function(err) {
console.error('获取文件信息失败:', err)
}
})
删除文件
uni.removeSavedFile({
filePath: savedFilePath,
success: function() {
console.log('文件删除成功')
},
fail: function(err) {
console.error('文件删除失败:', err)
}
})
文件管理器
对于App平台,可以使用更强大的文件管理器API:
// 仅在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进行大容量数据存储:
// 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存储:
// #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
数据同步与备份
为了确保用户数据安全,可以实现数据同步与备份功能:
云端同步
// 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
使用数据同步:
import dataSync from '@/utils/sync'
// 手动同步
dataSync.sync()
.then(() => {
uni.showToast({
title: '同步成功',
icon: 'success'
})
})
.catch(() => {
uni.showToast({
title: '同步失败',
icon: 'none'
})
})
// 启动自动同步(每5分钟)
dataSync.startAutoSync()
本地备份与恢复
// 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
使用数据备份与恢复:
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
}
最佳实践
性能优化
- 批量操作:尽量减少存储操作次数,使用批量读写
- 延迟写入:对于频繁变化的数据,可以使用防抖或节流技术延迟写入
- 压缩数据:对于大型数据,可以使用压缩算法减小体积
- 分片存储:将大型数据分割成多个小块存储,避免超出单个键的大小限制
// 延迟写入示例
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)
}
安全建议
- 敏感数据加密:使用加密存储敏感信息
- 定期清理:定期清理过期或不必要的数据
- 数据验证:读取数据时进行验证,防止使用损坏的数据
- 错误处理:妥善处理存储操作中的异常
// 数据验证示例
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
}
}
存储策略
分级存储:根据数据重要性和使用频率选择不同的存储方式
- 关键数据:加密存储
- 常用数据:本地存储
- 大型数据:文件存储或IndexedDB
- 临时数据:内存缓存
存储生命周期:为不同类型的数据设置合适的过期时间
- 会话数据:应用关闭时清除
- 缓存数据:设置合理的过期时间
- 用户数据:长期保存,但提供清除选项
// 分级存储示例
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中的数据存储方法和最佳实践,包括:
- 基础存储API:uni.setStorage、uni.getStorage等方法的使用
- 存储限制:不同平台的存储空间限制及应对策略
- 存储封装与管理:封装存储管理模块,支持过期时间、批量操作等
- 加密存储:敏感数据的加密存储方案
- 文件存储:大型数据的文件系统存储方法
- IndexedDB存储:H5平台的大容量数据存储方案
- 数据同步与备份:实现云端同步和本地备份恢复功能
- 最佳实践:性能优化、安全建议和存储策略
通过合理使用这些存储方法和技巧,可以有效管理uni-app应用中的数据,提高应用性能和用户体验。在实际开发中,应根据数据特性和业务需求选择合适的存储方案,并注意数据安全和性能优化。