flat: 消息

This commit is contained in:
史典卓
2025-05-15 14:17:51 +08:00
parent 5333254c58
commit a93907018c
40 changed files with 875 additions and 56 deletions

256
common/UniStorageHelper.js Normal file
View 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;