flat: 消息
This commit is contained in:
256
common/UniStorageHelper.js
Normal file
256
common/UniStorageHelper.js
Normal file
@@ -0,0 +1,256 @@
|
||||
// uni-storage-helper.js
|
||||
class UniStorageHelper {
|
||||
constructor(dbName, options = {}) {
|
||||
this.dbName = dbName;
|
||||
this.storesMeta = {};
|
||||
this.options = {
|
||||
maxEntries: 500, // 单个存储空间最大条目数
|
||||
maxSizeMB: 1, // 单条数据最大限制(微信小程序限制)
|
||||
autoPurge: true, // 是否自动清理旧数据
|
||||
purgeBatch: 10, // 自动清理批次数量
|
||||
debug: false, // 调试模式
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
/*==================
|
||||
核心方法
|
||||
==================*/
|
||||
|
||||
/**
|
||||
* 初始化存储空间
|
||||
* @param {Array} stores - 存储空间配置
|
||||
*/
|
||||
async openDB(stores = []) {
|
||||
stores.forEach(store => {
|
||||
const storeKey = this._getStoreKey(store.name);
|
||||
if (!this._storageHas(storeKey)) {
|
||||
this._storageSet(storeKey, []);
|
||||
}
|
||||
|
||||
this.storesMeta[store.name] = {
|
||||
keyPath: store.keyPath,
|
||||
autoIncrement: !!store.autoIncrement,
|
||||
indexes: store.indexes || []
|
||||
};
|
||||
|
||||
if (store.autoIncrement) {
|
||||
const counterKey = this._getCounterKey(store.name);
|
||||
if (!this._storageHas(counterKey)) {
|
||||
this._storageSet(counterKey, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this._log('数据库初始化完成');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加数据(自动处理容量限制)
|
||||
*/
|
||||
async add(storeName, data) {
|
||||
try {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
let storeData = this._storageGet(storeKey) || [];
|
||||
const meta = this.storesMeta[storeName];
|
||||
const items = Array.isArray(data) ? data : [data];
|
||||
|
||||
// 容量预检
|
||||
await this._checkCapacity(storeName, items);
|
||||
|
||||
// 处理自增ID
|
||||
if (meta?.autoIncrement) {
|
||||
const counterKey = this._getCounterKey(storeName);
|
||||
let nextId = this._storageGet(counterKey) || 1;
|
||||
items.forEach(item => {
|
||||
item[meta.keyPath] = nextId++;
|
||||
this._createIndexes(meta.indexes, item);
|
||||
});
|
||||
this._storageSet(counterKey, nextId);
|
||||
}
|
||||
|
||||
// 保存数据
|
||||
storeData = [...storeData, ...items];
|
||||
this._storageSet(storeKey, storeData);
|
||||
|
||||
this._log(`成功添加${items.length}条数据到${storeName}`);
|
||||
|
||||
return meta?.autoIncrement ?
|
||||
Array.isArray(data) ?
|
||||
items.map(i => i[meta.keyPath]) :
|
||||
items[0][meta.keyPath] :
|
||||
undefined;
|
||||
|
||||
} catch (error) {
|
||||
if (error.message.includes('exceed')) {
|
||||
this._log('触发自动清理...');
|
||||
await this._purgeData(storeName, this.options.purgeBatch);
|
||||
return this.add(storeName, data);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/*==================
|
||||
查询方法
|
||||
==================*/
|
||||
|
||||
async get(storeName, key) {
|
||||
const storeData = this._storageGet(this._getStoreKey(storeName)) || [];
|
||||
const keyPath = this.storesMeta[storeName]?.keyPath;
|
||||
return storeData.find(item => item[keyPath] === key);
|
||||
}
|
||||
|
||||
async getAll(storeName) {
|
||||
return this._storageGet(this._getStoreKey(storeName)) || [];
|
||||
}
|
||||
|
||||
async queryByField(storeName, fieldName, value) {
|
||||
const storeData = this._storageGet(this._getStoreKey(storeName)) || [];
|
||||
return storeData.filter(item => item[fieldName] === value);
|
||||
}
|
||||
|
||||
/*==================
|
||||
更新/删除方法
|
||||
==================*/
|
||||
|
||||
async update(storeName, data, key) {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
const storeData = this._storageGet(storeKey) || [];
|
||||
const meta = this.storesMeta[storeName];
|
||||
const keyPath = meta?.keyPath;
|
||||
const targetKey = key ?? data[keyPath];
|
||||
|
||||
const index = storeData.findIndex(item => item[keyPath] === targetKey);
|
||||
if (index === -1) throw new Error('未找到对应记录');
|
||||
|
||||
// 合并数据并重建索引
|
||||
const newItem = {
|
||||
...storeData[index],
|
||||
...data
|
||||
};
|
||||
this._createIndexes(meta.indexes, newItem);
|
||||
|
||||
storeData[index] = newItem;
|
||||
this._storageSet(storeKey, storeData);
|
||||
|
||||
return "更新成功";
|
||||
}
|
||||
|
||||
async delete(storeName, key) {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
const storeData = this._storageGet(storeKey) || [];
|
||||
const keyPath = this.storesMeta[storeName]?.keyPath;
|
||||
const newData = storeData.filter(item => item[keyPath] !== key);
|
||||
this._storageSet(storeKey, newData);
|
||||
return `删除${storeData.length - newData.length}条记录`;
|
||||
}
|
||||
|
||||
/*==================
|
||||
存储管理
|
||||
==================*/
|
||||
|
||||
async clearStore(storeName) {
|
||||
this._storageSet(this._getStoreKey(storeName), []);
|
||||
return "存储空间已清空";
|
||||
}
|
||||
|
||||
async deleteDB() {
|
||||
Object.keys(this.storesMeta).forEach(storeName => {
|
||||
uni.removeStorageSync(this._getStoreKey(storeName));
|
||||
uni.removeStorageSync(this._getCounterKey(storeName));
|
||||
});
|
||||
return "数据库已删除";
|
||||
}
|
||||
|
||||
/*==================
|
||||
私有方法
|
||||
==================*/
|
||||
|
||||
_getStoreKey(storeName) {
|
||||
return `${this.dbName}_${storeName}`;
|
||||
}
|
||||
|
||||
_getCounterKey(storeName) {
|
||||
return `${this.dbName}_${storeName}_counter`;
|
||||
}
|
||||
|
||||
_createIndexes(indexes, item) {
|
||||
indexes.forEach(index => {
|
||||
item[index.name] = item[index.key];
|
||||
});
|
||||
}
|
||||
|
||||
async _checkCapacity(storeName, newItems) {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
const currentData = this._storageGet(storeKey) || [];
|
||||
|
||||
// 检查条目数限制
|
||||
if (currentData.length + newItems.length > this.options.maxEntries) {
|
||||
await this._purgeData(storeName, newItems.length);
|
||||
}
|
||||
|
||||
// 检查单条数据大小
|
||||
newItems.forEach(item => {
|
||||
const sizeMB = this._getItemSizeMB(item);
|
||||
if (sizeMB > this.options.maxSizeMB) {
|
||||
throw new Error(`单条数据大小超出${this.options.maxSizeMB}MB限制`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getItemSizeMB(item) {
|
||||
try {
|
||||
// 精确计算(支持Blob的环境)
|
||||
return new Blob([JSON.stringify(item)]).size / 1024 / 1024;
|
||||
} catch {
|
||||
// 兼容方案
|
||||
return encodeURIComponent(JSON.stringify(item)).length * 2 / 1024 / 1024;
|
||||
}
|
||||
}
|
||||
|
||||
async _purgeData(storeName, count) {
|
||||
const storeKey = this._getStoreKey(storeName);
|
||||
const currentData = this._storageGet(storeKey) || [];
|
||||
const newData = currentData.slice(count);
|
||||
this._storageSet(storeKey, newData);
|
||||
this._log(`自动清理${count}条旧数据`);
|
||||
}
|
||||
|
||||
/*==================
|
||||
存储适配器
|
||||
==================*/
|
||||
|
||||
_storageHas(key) {
|
||||
return !!uni.getStorageSync(key);
|
||||
}
|
||||
|
||||
_storageGet(key) {
|
||||
try {
|
||||
return uni.getStorageSync(key);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_storageSet(key, value) {
|
||||
try {
|
||||
uni.setStorageSync(key, value);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.errMsg?.includes('exceed')) {
|
||||
throw new Error('STORAGE_QUOTA_EXCEEDED');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
_log(...args) {
|
||||
if (this.options.debug) {
|
||||
console.log(`[StorageHelper]`, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default UniStorageHelper;
|
Reference in New Issue
Block a user