256 lines
7.9 KiB
JavaScript
256 lines
7.9 KiB
JavaScript
// 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; |