现代Web存储技术(三):配额监控与自动化清理机制
【摘要】 现代浏览器虽然提供了充足的存储空间,但在某些情况下仍可能遇到存储配额超限的问题。本文将介绍如何处理这些情况,以及如何设计数据清理策略。 1. 存储配额超限场景分析 1.1 配额超限的常见场景浏览器存储空间虽然很大,但在以下场景中仍可能遇到超限:高存储需求应用离线视频应用:缓存大量高清视频文件图片编辑器:处理高分辨率图片和项目文件游戏应用:存储游戏资源、存档和缓存数据开发工具:缓存代码库、依赖...
现代浏览器虽然提供了充足的存储空间,但在某些情况下仍可能遇到存储配额超限的问题。本文将介绍如何处理这些情况,以及如何设计数据清理策略。
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
}
}
// 打开IndexedDB
openIndexedDB(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();
}
// 安全存储数据到IndexedDB
async 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; // 100KB
if (!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) { // 大于100KB
checkbox.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:适合缓存类数据
- 大小优先:适合媒体文件较多的应用
- 用户选择:适合重要数据较多的场景
- 优先级分级:适合复杂业务应用
最佳实践:
- 监控存储使用情况
- 提前预警和清理
- 给用户选择权
- 保护关键数据
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)