当前位置: 首页 > news >正文

现代Web存储技术(三):配额监控与自动化清理机制

现代浏览器虽然提供了充足的存储空间,但在某些情况下仍可能遇到存储配额超限的问题。本文将介绍如何处理这些情况,以及如何设计数据清理策略。

1. 存储配额超限场景分析

1.1 配额超限的常见场景

浏览器存储空间虽然很大,但在以下场景中仍可能遇到超限:

高存储需求应用

  • 离线视频应用:缓存大量高清视频文件
  • 图片编辑器:处理高分辨率图片和项目文件
  • 游戏应用:存储游戏资源、存档和缓存数据
  • 开发工具:缓存代码库、依赖包和构建产物

实际案例
某在线视频编辑器,用户导入了几个4K视频文件,每个文件2GB,很快就将浏览器存储空间占满。此时如果不进行处理,用户将无法继续使用应用的离线功能。

1.2 QuotaExceededError错误处理

当存储空间不足时,浏览器会抛出QuotaExceededError错误。需要优雅地处理这个错误:

// 存储配额管理器
class StorageQuotaManager {constructor() {this.warningThreshold = 80; // 使用率超过80%时警告this.criticalThreshold = 95; // 使用率超过95%时强制清理}// 安全存储数据async safeStore(storageOperation, fallbackOperation = null) {try {await storageOperation();console.log('数据存储成功');return { success: true };} catch (error) {if (error.name === 'QuotaExceededError') {console.warn('存储配额已满,尝试清理空间...');return await this.handleQuotaExceeded(storageOperation, fallbackOperation);} else {console.error('存储操作失败:', error);throw error;}}}// 处理配额超限async handleQuotaExceeded(storageOperation, fallbackOperation) {try {// 1. 检查当前存储使用情况const usage = await this.getStorageUsage();console.log(`当前存储使用率: ${usage.percentage}%`);// 2. 尝试自动清理const cleanedSpace = await this.autoCleanup();console.log(`已清理 ${cleanedSpace} MB 空间`);// 3. 重试存储操作if (cleanedSpace > 0) {try {await storageOperation();return { success: true, cleaned: cleanedSpace };} catch (retryError) {if (retryError.name === 'QuotaExceededError') {console.warn('清理后仍然空间不足');} else {throw retryError;}}}// 4. 尝试降级方案if (fallbackOperation) {console.log('使用降级存储方案');await fallbackOperation();return { success: true, fallback: true };}// 5. 通知用户处理return await this.notifyUserAndCleanup();} catch (error) {console.error('处理配额超限失败:', error);return { success: false, error: error.message };}}// 获取存储使用情况async getStorageUsage() {if (!navigator.storage?.estimate) {return { percentage: 0, used: 0, quota: 0 };}const estimate = await navigator.storage.estimate();const used = estimate.usage || 0;const quota = estimate.quota || 0;const percentage = quota ? (used / quota * 100) : 0;return {used,quota,percentage: Math.round(percentage * 100) / 100,usedMB: Math.round(used / 1024 / 1024 * 100) / 100,quotaMB: Math.round(quota / 1024 / 1024 * 100) / 100};}// 自动清理async autoCleanup() {let totalCleaned = 0;// 清理过期缓存totalCleaned += await this.cleanExpiredCache();// 清理旧的IndexedDB数据totalCleaned += await this.cleanOldIndexedDBData();// 清理临时文件totalCleaned += await this.cleanTempFiles();return totalCleaned;}// 清理过期缓存async cleanExpiredCache() {if (!('caches' in window)) return 0;let cleanedSize = 0;try {const cacheNames = await caches.keys();for (const cacheName of cacheNames) {const cache = await caches.open(cacheName);const requests = await cache.keys();for (const request of requests) {const response = await cache.match(request);if (response) {const cacheDate = response.headers.get('date');const maxAge = response.headers.get('cache-control')?.match(/max-age=(\d+)/);if (cacheDate && maxAge) {const cacheTime = new Date(cacheDate).getTime();const maxAgeSeconds = parseInt(maxAge[1]);const expiryTime = cacheTime + (maxAgeSeconds * 1000);if (Date.now() > expiryTime) {await cache.delete(request);cleanedSize += this.estimateResponseSize(response);console.log(`已删除过期缓存: ${request.url}`);}}}}}} catch (error) {console.error('清理过期缓存失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100; // 返回MB}// 清理旧的IndexedDB数据async cleanOldIndexedDBData() {// 根据具体应用的数据结构实现// 示例:清理30天前的数据let cleanedSize = 0;try {const db = await this.openIndexedDB('app-data', 1);const transaction = db.transaction(['articles'], 'readwrite');const store = transaction.objectStore('articles');const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);const index = store.index('timestamp');const range = IDBKeyRange.upperBound(thirtyDaysAgo);const cursor = await index.openCursor(range);while (cursor) {const data = cursor.value;cleanedSize += JSON.stringify(data).length;await cursor.delete();cursor.continue();}db.close();} catch (error) {console.error('清理IndexedDB数据失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}// 清理临时文件async cleanTempFiles() {if (!('showDirectoryPicker' in window)) return 0;// OPFS临时文件清理let cleanedSize = 0;try {const opfsRoot = await navigator.storage.getDirectory();const tempDir = await opfsRoot.getDirectoryHandle('temp', { create: false });for await (const [name, handle] of tempDir.entries()) {if (handle.kind === 'file') {const file = await handle.getFile();const lastModified = file.lastModified;const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);if (lastModified < oneDayAgo) {cleanedSize += file.size;await tempDir.removeEntry(name);console.log(`已删除临时文件: ${name}`);}}}} catch (error) {console.error('清理临时文件失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}// 通知用户并手动清理async notifyUserAndCleanup() {const userChoice = await this.showCleanupDialog();switch (userChoice) {case 'auto':const cleaned = await this.aggressiveCleanup();return { success: cleaned > 0, cleaned, userAction: 'auto' };case 'manual':this.showManualCleanupUI();return { success: false, userAction: 'manual' };case 'ignore':return { success: false, userAction: 'ignore' };default:return { success: false, userAction: 'cancel' };}}// 显示清理对话框async showCleanupDialog() {return new Promise((resolve) => {const dialog = document.createElement('div');dialog.className = 'storage-cleanup-dialog';dialog.innerHTML = `<div class="dialog-content"><h3>存储空间不足</h3><p>应用存储空间已满,需要清理一些数据才能继续使用。</p><div class="dialog-buttons"><button id="auto-cleanup">自动清理</button><button id="manual-cleanup">手动选择</button><button id="ignore-cleanup">暂时忽略</button></div></div>`;document.body.appendChild(dialog);dialog.querySelector('#auto-cleanup').onclick = () => {document.body.removeChild(dialog);resolve('auto');};dialog.querySelector('#manual-cleanup').onclick = () => {document.body.removeChild(dialog);resolve('manual');};dialog.querySelector('#ignore-cleanup').onclick = () => {document.body.removeChild(dialog);resolve('ignore');};});}// 激进清理async aggressiveCleanup() {let totalCleaned = 0;// 清理所有非关键缓存totalCleaned += await this.cleanNonCriticalCache();// 清理旧数据(保留最近7天)totalCleaned += await this.cleanOldData(7);// 压缩现有数据totalCleaned += await this.compressExistingData();return totalCleaned;}// 估算响应大小estimateResponseSize(response) {const contentLength = response.headers.get('content-length');if (contentLength) {return parseInt(contentLength);}// 如果没有content-length,根据类型估算const contentType = response.headers.get('content-type') || '';if (contentType.includes('image')) {return 50 * 1024; // 估算50KB} else if (contentType.includes('video')) {return 1024 * 1024; // 估算1MB} else {return 10 * 1024; // 估算10KB}}// 打开IndexedDBopenIndexedDB(name, version) {return new Promise((resolve, reject) => {const request = indexedDB.open(name, version);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}
}

1.3 IndexedDB存储管理

// IndexedDB存储管理器
class IndexedDBManager {constructor(dbName, version = 1) {this.dbName = dbName;this.version = version;this.quotaManager = new StorageQuotaManager();}// 安全存储数据到IndexedDBasync safeStoreData(storeName, data) {const storeOperation = async () => {const db = await this.openDB();const transaction = db.transaction([storeName], 'readwrite');const store = transaction.objectStore(storeName);if (Array.isArray(data)) {// 批量存储for (const item of data) {await store.add(item);}} else {await store.add(data);}db.close();};const fallbackOperation = async () => {// 降级方案:只存储关键数据const essentialData = this.extractEssentialData(data);const db = await this.openDB();const transaction = db.transaction([storeName], 'readwrite');const store = transaction.objectStore(storeName);if (Array.isArray(essentialData)) {for (const item of essentialData) {await store.add(item);}} else {await store.add(essentialData);}db.close();console.log('使用降级存储方案,只保存了关键数据');};return await this.quotaManager.safeStore(storeOperation, fallbackOperation);}// 提取关键数据extractEssentialData(data) {if (Array.isArray(data)) {return data.map(item => this.extractEssentialFields(item));} else {return this.extractEssentialFields(data);}}// 提取关键字段extractEssentialFields(item) {// 根据具体业务逻辑提取关键字段const essential = {id: item.id,title: item.title,timestamp: item.timestamp};// 移除大型字段if (item.content && item.content.length > 1000) {essential.content = item.content.substring(0, 1000) + '...';} else {essential.content = item.content;}return essential;}// 打开数据库openDB() {return new Promise((resolve, reject) => {const request = indexedDB.open(this.dbName, this.version);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);request.onupgradeneeded = (event) => {const db = event.target.result;// 创建对象存储if (!db.objectStoreNames.contains('articles')) {const store = db.createObjectStore('articles', { keyPath: 'id' });store.createIndex('timestamp', 'timestamp', { unique: false });store.createIndex('category', 'category', { unique: false });}if (!db.objectStoreNames.contains('media')) {const store = db.createObjectStore('media', { keyPath: 'id' });store.createIndex('type', 'type', { unique: false });store.createIndex('size', 'size', { unique: false });}};});}
}

1.4 Cache API管理

// Cache API管理器
class CacheManager {constructor() {this.quotaManager = new StorageQuotaManager();this.cacheNames = {static: 'static-resources-v1',dynamic: 'dynamic-content-v1',images: 'images-v1'};}// 安全缓存资源async safeCacheResource(cacheName, request, response) {const cacheOperation = async () => {const cache = await caches.open(cacheName);await cache.put(request, response.clone());};const fallbackOperation = async () => {// 降级方案:只缓存小文件const contentLength = response.headers.get('content-length');const maxSize = 100 * 1024; // 100KBif (!contentLength || parseInt(contentLength) <= maxSize) {const cache = await caches.open(cacheName);await cache.put(request, response.clone());console.log('使用降级缓存方案,只缓存小文件');} else {console.log('文件过大,跳过缓存');}};return await this.quotaManager.safeStore(cacheOperation, fallbackOperation);}// 智能缓存清理async smartCleanup() {let totalCleaned = 0;// 1. 清理最少使用的缓存totalCleaned += await this.cleanLeastUsedCache();// 2. 清理大文件缓存totalCleaned += await this.cleanLargeFileCache();// 3. 清理过期缓存totalCleaned += await this.cleanExpiredCache();return totalCleaned;}// 清理最少使用的缓存async cleanLeastUsedCache() {// 配合使用统计来实现// 简化示例let cleanedSize = 0;try {const cache = await caches.open(this.cacheNames.dynamic);const requests = await cache.keys();// 假设有使用统计数据const usageStats = await this.getUsageStats();// 删除使用次数最少的20%const sortedRequests = requests.sort((a, b) => {const usageA = usageStats[a.url] || 0;const usageB = usageStats[b.url] || 0;return usageA - usageB;});const toDelete = sortedRequests.slice(0, Math.floor(requests.length * 0.2));for (const request of toDelete) {const response = await cache.match(request);if (response) {cleanedSize += this.quotaManager.estimateResponseSize(response);}await cache.delete(request);}} catch (error) {console.error('清理最少使用缓存失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}// 获取使用统计(示例)async getUsageStats() {try {const stats = localStorage.getItem('cache-usage-stats');return stats ? JSON.parse(stats) : {};} catch (error) {return {};}}// 记录缓存使用async recordCacheUsage(url) {try {const stats = await this.getUsageStats();stats[url] = (stats[url] || 0) + 1;localStorage.setItem('cache-usage-stats', JSON.stringify(stats));} catch (error) {console.error('记录缓存使用失败:', error);}}
}

2. 数据清理策略

2.1 四种清理策略

LRU(最近最少使用)

  • 原理:删除最久没有访问的数据
  • 适用场景:新闻应用的文章缓存、图片缓存
  • 实现思路:记录每次访问时间,清理时删除最旧的

大小优先

  • 原理:优先删除占用空间最大的数据
  • 适用场景:视频应用、图片编辑器
  • 实现思路:按文件大小排序,优先删除大文件

用户选择

  • 原理:让用户自己决定删除什么
  • 适用场景:重要数据较多的应用
  • 实现思路:提供清理界面,让用户勾选要删除的内容

重要性分级

  • 原理:根据数据重要性分级清理
  • 适用场景:复杂的业务应用
  • 实现思路:给数据打标签,按重要性清理

2.2 清理策略实现

// 数据清理策略管理器
class DataCleanupStrategy {constructor() {this.strategies = {lru: new LRUCleanupStrategy(),size: new SizeBasedCleanupStrategy(),user: new UserChoiceCleanupStrategy(),priority: new PriorityBasedCleanupStrategy()};}// 执行清理async executeCleanup(strategyName, options = {}) {const strategy = this.strategies[strategyName];if (!strategy) {throw new Error(`未知的清理策略: ${strategyName}`);}return await strategy.cleanup(options);}// 智能选择清理策略async smartCleanup(targetSpaceMB = 100) {const usage = await this.getStorageUsage();if (usage.percentage < 70) {console.log('存储空间充足,无需清理');return { cleaned: 0, strategy: 'none' };}// 根据使用情况选择策略let strategy;if (usage.percentage > 95) {strategy = 'size'; // 紧急情况,优先删除大文件} else if (usage.percentage > 85) {strategy = 'lru'; // 删除最少使用的数据} else {strategy = 'priority'; // 按重要性清理}console.log(`选择清理策略: ${strategy}`);return await this.executeCleanup(strategy, { targetSpaceMB });}async getStorageUsage() {if (!navigator.storage?.estimate) {return { percentage: 0 };}const estimate = await navigator.storage.estimate();const percentage = estimate.quota ? (estimate.usage / estimate.quota * 100) : 0;return { percentage };}
}

2.3 LRU清理策略

// LRU清理策略
class LRUCleanupStrategy {async cleanup(options = {}) {const { targetSpaceMB = 100 } = options;let cleanedSize = 0;// 清理IndexedDB中的LRU数据cleanedSize += await this.cleanupIndexedDBLRU(targetSpaceMB);// 清理Cache API中的LRU数据cleanedSize += await this.cleanupCacheLRU(targetSpaceMB - cleanedSize);return { cleaned: cleanedSize, strategy: 'lru' };}async cleanupIndexedDBLRU(targetSpaceMB) {let cleanedSize = 0;try {const db = await this.openDB('app-data');const transaction = db.transaction(['articles'], 'readwrite');const store = transaction.objectStore('articles');const index = store.index('lastAccessed');// 按最后访问时间排序,删除最旧的数据const cursor = await index.openCursor();while (cursor && cleanedSize < targetSpaceMB * 1024 * 1024) {const data = cursor.value;const dataSize = JSON.stringify(data).length;await cursor.delete();cleanedSize += dataSize;console.log(`删除LRU数据: ${data.title}`);cursor.continue();}db.close();} catch (error) {console.error('LRU清理IndexedDB失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}async cleanupCacheLRU(targetSpaceMB) {if (!('caches' in window) || targetSpaceMB <= 0) return 0;let cleanedSize = 0;try {const cacheNames = await caches.keys();const accessStats = await this.getCacheAccessStats();// 收集所有缓存项并按访问时间排序const allCacheItems = [];for (const cacheName of cacheNames) {const cache = await caches.open(cacheName);const requests = await cache.keys();for (const request of requests) {const lastAccessed = accessStats[request.url] || 0;allCacheItems.push({cacheName,request,lastAccessed,url: request.url});}}// 按最后访问时间排序allCacheItems.sort((a, b) => a.lastAccessed - b.lastAccessed);// 删除最旧的缓存项for (const item of allCacheItems) {if (cleanedSize >= targetSpaceMB * 1024 * 1024) break;const cache = await caches.open(item.cacheName);const response = await cache.match(item.request);if (response) {const responseSize = this.estimateResponseSize(response);await cache.delete(item.request);cleanedSize += responseSize;console.log(`删除LRU缓存: ${item.url}`);}}} catch (error) {console.error('LRU清理缓存失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}async getCacheAccessStats() {try {const stats = localStorage.getItem('cache-access-stats');return stats ? JSON.parse(stats) : {};} catch (error) {return {};}}estimateResponseSize(response) {const contentLength = response.headers.get('content-length');return contentLength ? parseInt(contentLength) : 10240; // 默认10KB}openDB(name) {return new Promise((resolve, reject) => {const request = indexedDB.open(name);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}
}

2.4 基于大小的清理策略

// 基于大小的清理策略
class SizeBasedCleanupStrategy {async cleanup(options = {}) {const { targetSpaceMB = 100 } = options;let cleanedSize = 0;// 优先清理大文件cleanedSize += await this.cleanupLargeFiles(targetSpaceMB);return { cleaned: cleanedSize, strategy: 'size' };}async cleanupLargeFiles(targetSpaceMB) {let cleanedSize = 0;try {// 清理OPFS中的大文件const opfsRoot = await navigator.storage.getDirectory();const mediaDir = await opfsRoot.getDirectoryHandle('media', { create: false });const files = [];for await (const [name, handle] of mediaDir.entries()) {if (handle.kind === 'file') {const file = await handle.getFile();files.push({ name, handle, size: file.size });}}// 按大小排序,优先删除大文件files.sort((a, b) => b.size - a.size);for (const fileInfo of files) {if (cleanedSize >= targetSpaceMB * 1024 * 1024) break;await mediaDir.removeEntry(fileInfo.name);cleanedSize += fileInfo.size;console.log(`删除大文件: ${fileInfo.name} (${Math.round(fileInfo.size / 1024 / 1024)}MB)`);}} catch (error) {console.error('清理大文件失败:', error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}
}

2.5 用户选择清理策略

// 用户选择清理策略
class UserChoiceCleanupStrategy {async cleanup(options = {}) {const cleanupItems = await this.getCleanupItems();const userSelection = await this.showCleanupUI(cleanupItems);let cleanedSize = 0;for (const item of userSelection) {cleanedSize += await this.deleteItem(item);}return { cleaned: cleanedSize, strategy: 'user' };}async getCleanupItems() {const items = [];// 获取IndexedDB中的数据项try {const db = await this.openDB('app-data');const transaction = db.transaction(['articles'], 'readonly');const store = transaction.objectStore('articles');const cursor = await store.openCursor();while (cursor) {const data = cursor.value;items.push({type: 'indexeddb',id: data.id,title: data.title,size: JSON.stringify(data).length,lastAccessed: data.lastAccessed || 0,data: data});cursor.continue();}db.close();} catch (error) {console.error('获取IndexedDB项目失败:', error);}// 获取缓存项if ('caches' in window) {try {const cacheNames = await caches.keys();for (const cacheName of cacheNames) {const cache = await caches.open(cacheName);const requests = await cache.keys();for (const request of requests) {const response = await cache.match(request);if (response) {items.push({type: 'cache',id: request.url,title: this.extractTitleFromUrl(request.url),size: this.estimateResponseSize(response),cacheName: cacheName,request: request});}}}} catch (error) {console.error('获取缓存项目失败:', error);}}return items;}async showCleanupUI(items) {return new Promise((resolve) => {const dialog = document.createElement('div');dialog.className = 'cleanup-dialog';const itemsHtml = items.map(item => `<div class="cleanup-item"><input type="checkbox" id="item-${item.id}" data-item-id="${item.id}"><label for="item-${item.id}"><span class="item-title">${item.title}</span><span class="item-size">${this.formatSize(item.size)}</span><span class="item-type">${item.type}</span></label></div>`).join('');dialog.innerHTML = `<div class="dialog-content"><h3>选择要清理的项目</h3><div class="cleanup-items">${itemsHtml}</div><div class="dialog-buttons"><button id="select-all">全选</button><button id="select-large">选择大文件</button><button id="confirm-cleanup">确认清理</button><button id="cancel-cleanup">取消</button></div></div>`;document.body.appendChild(dialog);// 绑定事件dialog.querySelector('#select-all').onclick = () => {dialog.querySelectorAll('input[type="checkbox"]').forEach(cb => cb.checked = true);};dialog.querySelector('#select-large').onclick = () => {items.forEach(item => {const checkbox = dialog.querySelector(`#item-${item.id}`);if (checkbox && item.size > 100 * 1024) { // 大于100KBcheckbox.checked = true;}});};dialog.querySelector('#confirm-cleanup').onclick = () => {const selectedItems = [];dialog.querySelectorAll('input[type="checkbox"]:checked').forEach(cb => {const itemId = cb.dataset.itemId;const item = items.find(i => i.id === itemId);if (item) selectedItems.push(item);});document.body.removeChild(dialog);resolve(selectedItems);};dialog.querySelector('#cancel-cleanup').onclick = () => {document.body.removeChild(dialog);resolve([]);};});}async deleteItem(item) {try {if (item.type === 'indexeddb') {const db = await this.openDB('app-data');const transaction = db.transaction(['articles'], 'readwrite');const store = transaction.objectStore('articles');await store.delete(item.data.id);db.close();return item.size;} else if (item.type === 'cache') {const cache = await caches.open(item.cacheName);await cache.delete(item.request);return item.size;}} catch (error) {console.error('删除项目失败:', error);}return 0;}formatSize(bytes) {if (bytes < 1024) return bytes + ' B';if (bytes < 1024 * 1024) return Math.round(bytes / 1024) + ' KB';return Math.round(bytes / 1024 / 1024 * 100) / 100 + ' MB';}extractTitleFromUrl(url) {const parts = url.split('/');return parts[parts.length - 1] || url;}estimateResponseSize(response) {const contentLength = response.headers.get('content-length');return contentLength ? parseInt(contentLength) : 10240;}openDB(name) {return new Promise((resolve, reject) => {const request = indexedDB.open(name);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}
}

2.6 基于优先级的清理策略

// 基于优先级的清理策略
class PriorityBasedCleanupStrategy {constructor() {this.priorities = {critical: 1,    // 关键数据,不删除important: 2,   // 重要数据,最后删除normal: 3,      // 普通数据,可以删除cache: 4,       // 缓存数据,优先删除temp: 5         // 临时数据,最先删除};}async cleanup(options = {}) {const { targetSpaceMB = 100 } = options;let cleanedSize = 0;// 按优先级顺序清理const priorityOrder = ['temp', 'cache', 'normal', 'important'];for (const priority of priorityOrder) {if (cleanedSize >= targetSpaceMB) break;const remainingTarget = targetSpaceMB - cleanedSize;cleanedSize += await this.cleanupByPriority(priority, remainingTarget);}return { cleaned: cleanedSize, strategy: 'priority' };}async cleanupByPriority(priority, targetSpaceMB) {let cleanedSize = 0;try {const db = await this.openDB('app-data');const transaction = db.transaction(['articles'], 'readwrite');const store = transaction.objectStore('articles');const cursor = await store.openCursor();while (cursor && cleanedSize < targetSpaceMB * 1024 * 1024) {const data = cursor.value;const dataPriority = data.priority || 'normal';if (dataPriority === priority) {const dataSize = JSON.stringify(data).length;await cursor.delete();cleanedSize += dataSize;console.log(`删除${priority}优先级数据: ${data.title}`);}cursor.continue();}db.close();} catch (error) {console.error(`清理${priority}优先级数据失败:`, error);}return Math.round(cleanedSize / 1024 / 1024 * 100) / 100;}openDB(name) {return new Promise((resolve, reject) => {const request = indexedDB.open(name);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});}
}

3. 使用示例

// 使用示例
const cleanupStrategy = new DataCleanupStrategy();// 智能清理
cleanupStrategy.smartCleanup(200).then(result => {console.log(`清理完成: ${result.cleaned}MB, 策略: ${result.strategy}`);
});// 手动选择策略
document.getElementById('cleanup-btn')?.addEventListener('click', async () => {const strategy = document.getElementById('cleanup-strategy').value;const result = await cleanupStrategy.executeCleanup(strategy, { targetSpaceMB: 100 });alert(`清理了 ${result.cleaned}MB 空间`);
});

4. 总结

存储配额管理和数据清理是Web应用的重要组成部分。

配额超限处理要点:

  • 优雅处理QuotaExceededError错误
  • 提供降级存储方案
  • 自动清理和用户选择相结合

清理策略选择:

  • LRU:适合缓存类数据
  • 大小优先:适合媒体文件较多的应用
  • 用户选择:适合重要数据较多的场景
  • 优先级分级:适合复杂业务应用

最佳实践:

  • 监控存储使用情况
  • 提前预警和清理
  • 给用户选择权
  • 保护关键数据
http://www.dtcms.com/a/419169.html

相关文章:

  • 高并发系统的海量数据处理架构
  • 苹果群控系统游戏运营如何实现自动执行任务
  • NXP - 在MCUXpresso IDE中查看编译日志文件的方法
  • 荣耀官方网站郑州粒米seo外包
  • UI自动化框架之Selenium(一)
  • AI编程:自动化代码生成的实践
  • 网站免费建站ppa企业网站托管和网站建设服务商
  • LSTM自然语言处理情感分析项目(二)加载数据集
  • 自定义渲染管线 Custom Render Pipeline
  • 【循环神经网络3】门控循环单元GRU详解
  • 邯郸网站设计做网站的动态图片
  • 建网站要花钱吗网络建设推广
  • 【Java并发】揭秘Lock体系 -- 深入理解AbstractQueuedSynchronizer(AQS)
  • 3.8 数据链路层设备 (答案见原书 P122)
  • 轻松修复 WordPress 的“缺少临时文件夹”错误
  • PHP智能开发工具PhpStorm v2025.2全新上线——支持PHPUnit 12等
  • MySQL 事务和 Spring 事务
  • 怎样免费建立网站广州工商注册查询系统官网
  • 广州新建站wordpress 缩略图 oss
  • JVM 目录
  • Unity学习之常用的数据结构
  • 【C++实战(51)】C++11新特性实战:移动语义与右值引用,解锁性能密码
  • 做宠物的网站有哪些做任务 网站
  • python做网站缺点公司建设官方网站
  • 【笔记】1.1 化学电源的组成
  • 【面试题】HTTP与HTTPS的区别
  • 虚幻引擎|UE5制作DeepSeek插件并打包发布
  • 做链接的网站深圳门窗在哪里网站做推广
  • destoon 网站搬家做app找什么公司
  • UniApp键盘监听全攻略