Service Worker缓存策略与离线应用架构设计概述Service Worker作为PWA的核心技术,提供了强大的网络代理和缓存管理能力。本文将深入探讨如何设计高效的Service Worker缓存架构,实现智能缓存策略,构建可靠的离线应用。核心架构设计1. 智能缓存策略管理器interface CacheStrategy { name: string; priority: number; match(request: Request): boolean; handle(request: Request): Promise<Response>; } class IntelligentCacheStrategy implements CacheStrategy { constructor( public name: string, public priority: number, private options: CacheOptions ) {} match(request: Request): boolean { const { patterns, methods, contentTypes } = this.options; return ( this.matchesPattern(request.url, patterns) && this.matchesMethod(request.method, methods) && this.matchesContentType(request.headers.get('accept'), contentTypes) ); } private matchesPattern(url: string, patterns: RegExp[]): boolean { return patterns.some(pattern => pattern.test(url)); } private matchesMethod(method: string, allowedMethods: string[]): boolean { return allowedMethods.includes(method.toUpperCase()); } private matchesContentType(accept: string | null, contentTypes: string[]): boolean { if (!accept) return contentTypes.length === 0; return contentTypes.some(type => accept.includes(type)); } async handle(request: Request): Promise<Response> { throw new Error('Must be implemented by subclass'); } } 2. 预缓存策略实现class PrecacheStrategy extends IntelligentCacheStrategy { private cacheName: string; private maxAge: number; constructor(options: PrecacheOptions) { super('precache', 100, options); this.cacheName = options.cacheName || 'precache-v1'; this.maxAge = options.maxAge || 30 * 24 * 60 * 60 * 1000; // 30 days } async handle(request: Request): Promise<Response> { const cache = await caches.open(this.cacheName); const cachedResponse = await cache.match(request); if (cachedResponse) { const isExpired = this.isExpired(cachedResponse); if (!isExpired) { return cachedResponse; } await cache.delete(request); } try { const networkResponse = await fetch(request); if (networkResponse.ok) { const responseToCache = networkResponse.clone(); const enhancedResponse = this.enhanceResponse(responseToCache); await cache.put(request, enhancedResponse); } return networkResponse; } catch (error) { return this.createOfflineResponse(); } } private isExpired(response: Response): boolean { const cachedTime = response.headers.get('x-cached-time'); if (!cachedTime) return true; return Date.now() - parseInt(cachedTime) > this.maxAge; } private enhanceResponse(response: Response): Response { const headers = new Headers(response.headers); headers.set('x-cached-time', Date.now().toString()); headers.set('x-cache-strategy', this.name); return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); } private createOfflineResponse(): Response { return new Response('Offline - Content not available', { status: 503, statusText: 'Service Unavailable', headers: { 'Content-Type': 'text/plain' } }); } } 3. 运行时缓存策略class RuntimeCacheStrategy extends IntelligentCacheStrategy { private cacheName: string; private maxEntries: number; private maxAge: number; constructor(options: RuntimeCacheOptions) { super('runtime', 80, options); this.cacheName = options.cacheName || 'runtime-v1'; this.maxEntries = options.maxEntries || 100; this.maxAge = options.maxAge || 7 * 24 * 60 * 60 * 1000; // 7 days } async handle(request: Request): Promise<Response> { const cache = await caches.open(this.cacheName); try { const networkResponse = await fetch(request); if (networkResponse.ok) { const responseToCache = networkResponse.clone(); await this.addToCache(cache, request, responseToCache); } return networkResponse; } catch (error) { const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } throw error; } } private async addToCache(cache: Cache, request: Request, response: Response): Promise<void> { const enhancedResponse = this.enhanceResponse(response); await cache.put(request, enhancedResponse); await this.evictIfNeeded(cache); } private async evictIfNeeded(cache: Cache): Promise<void> { const requests = await cache.keys(); if (requests.length > this.maxEntries) { const entriesToDelete = requests.slice(0, requests.length - this.maxEntries); await Promise.all(entriesToDelete.map(request => cache.delete(request))); } } private enhanceResponse(response: Response): Response { const headers = new Headers(response.headers); headers.set('x-cached-time', Date.now().toString()); headers.set('x-cache-strategy', this.name); return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); } } 4. 网络优先策略class NetworkFirstStrategy extends IntelligentCacheStrategy { private cacheName: string; private timeout: number; constructor(options: NetworkFirstOptions) { super('network-first', 90, options); this.cacheName = options.cacheName || 'network-first-v1'; this.timeout = options.timeout || 3000; // 3 seconds } async handle(request: Request): Promise<Response> { const cache = await caches.open(this.cacheName); try { const networkResponse = await Promise.race([ fetch(request), new Promise<Response>((_, reject) => setTimeout(() => reject(new Error('Network timeout')), this.timeout) ) ]); if (networkResponse.ok) { const responseToCache = networkResponse.clone(); const enhancedResponse = this.enhanceResponse(responseToCache); await cache.put(request, enhancedResponse); } return networkResponse; } catch (error) { const cachedResponse = await cache.match(request); if (cachedResponse) { return cachedResponse; } throw new Error('Network unavailable and no cached response'); } } private enhanceResponse(response: Response): Response { const headers = new Headers(response.headers); headers.set('x-cached-time', Date.now().toString()); headers.set('x-cache-strategy', this.name); return new Response(response.body, { status: response.status, statusText: response.statusText, headers }); } } 缓存失效与更新机制1. 智能缓存失效管理器class CacheInvalidationManager { private invalidationRules: InvalidationRule[]; constructor() { this.invalidationRules = []; } addRule(rule: InvalidationRule): void { this.invalidationRules.push(rule); } async shouldInvalidate(request: Request, response: Response): Promise<boolean> { for (const rule of this.invalidationRules) { if (await rule.shouldInvalidate(request, response)) { return true; } } return false; } async invalidate(request: Request): Promise<void> { const cacheNames = await caches.keys(); await Promise.all(cacheNames.map(async cacheName => { const cache = await caches.open(cacheName); await cache.delete(request); })); } } interface InvalidationRule { shouldInvalidate(request: Request, response: Response): Promise<boolean>; } class HeaderBasedInvalidation implements InvalidationRule { constructor(private headerName: string, private expectedValue: string) {} async shouldInvalidate(request: Request, response: Response): Promise<boolean> { const headerValue = response.headers.get(this.headerName); return headerValue !== this.expectedValue; } } class TimeBasedInvalidation implements InvalidationRule { constructor(private maxAge: number) {} async shouldInvalidate(request: Request, response: Response): Promise<boolean> { const cachedTime = response.headers.get('x-cached-time'); if (!cachedTime) return true; const age = Date.now() - parseInt(cachedTime); return age > this.maxAge; } } 2. 后台同步机制class BackgroundSyncManager { private syncQueue: SyncTask[]; private dbName: string; private storeName: string; constructor(dbName: string = 'background-sync', storeName: string = 'sync-queue') { this.dbName = dbName; this.storeName = storeName; this.syncQueue = []; } async init(): Promise<void> { await this.initializeDB(); await this.loadQueue(); this.registerSyncEvent(); } private async initializeDB(): Promise<void> { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName, 1); request.onerror = () => reject(request.error); request.onsuccess = () => resolve(); request.onupgradeneeded = (event) => { const db = (event.target as IDBOpenDBRequest).result; if (!db.objectStoreNames.contains(this.storeName)) { db.createObjectStore(this.storeName, { keyPath: 'id', autoIncrement: true }); } }; }); } private async loadQueue(): Promise<void> { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName); request.onsuccess = (event) => { const db = (event.target as IDBOpenDBRequest).result; const transaction = db.transaction([this.storeName], 'readonly'); const store = transaction.objectStore(this.storeName); const getAllRequest = store.getAll(); getAllRequest.onsuccess = () => { this.syncQueue = getAllRequest.result; resolve(); }; getAllRequest.onerror = () => reject(getAllRequest.error); }; request.onerror = () => reject(request.error); }); } async addToQueue(task: SyncTask): Promise<void> { this.syncQueue.push(task); await this.saveQueue(); if ('serviceWorker' in navigator && 'sync' in ServiceWorkerRegistration.prototype) { await navigator.serviceWorker.ready.then(registration => { return registration.sync.register(`sync-${task.id}`); }); } } private async saveQueue(): Promise<void> { return new Promise((resolve, reject) => { const request = indexedDB.open(this.dbName); request.onsuccess = (event) => { const db = (event.target as IDBOpenDBRequest).result; const transaction = db.transaction([this.storeName], 'readwrite'); const store = transaction.objectStore(this.storeName); const clearRequest = store.clear(); clearRequest.onsuccess = () => { const addPromises = this.syncQueue.map(task => store.add(task)); Promise.all(addPromises).then(() => resolve()).catch(reject); }; clearRequest.onerror = () => reject(clearRequest.error); }; request.onerror = () => reject(request.error); }); } private registerSyncEvent(): void { self.addEventListener('sync', (event: SyncEvent) => { if (event.tag.startsWith('sync-')) { event.waitUntil(this.processSyncQueue()); } }); } private async processSyncQueue(): Promise<void> { const failedTasks: SyncTask[] = []; for (const task of this.syncQueue) { try { await this.executeTask(task); } catch (error) { console.error('Sync task failed:', error); failedTasks.push(task); } } this.syncQueue = failedTasks; await this.saveQueue(); } private async executeTask(task: SyncTask): Promise<void> { const response = await fetch(task.url, { method: task.method, headers: task.headers, body: task.body }); if (!response.ok) { throw new Error(`Sync request failed: ${response.status}`); } } } 离线应用架构1. 离线页面管理器class OfflinePageManager { private offlinePages: Map<string, OfflinePage>; private cacheName: string; constructor(cacheName: string = 'offline-pages') { this.cacheName = cacheName; this.offlinePages = new Map(); } async registerPage(url: string, content: string, metadata: PageMetadata): Promise<void> { const offlinePage: OfflinePage = { url, content, metadata, timestamp: Date.now() }; this.offlinePages.set(url, offlinePage); await this.saveToCache(offlinePage); } private async saveToCache(offlinePage: OfflinePage): Promise<void> { const cache = await caches.open(this.cacheName); const response = new Response(offlinePage.content, { headers: { 'Content-Type': 'text/html', 'X-Offline-Page': 'true', 'X-Timestamp': offlinePage.timestamp.toString() } }); await cache.put(offlinePage.url, response); } async getOfflinePage(url: string): Promise<OfflinePage | null> { return this.offlinePages.get(url) || null; } async generateOfflineFallback(): Promise<string> { const pages = Array.from(this.offlinePages.values()); const navigationLinks = pages.map(page => `<li><a href="${page.url}">${page.metadata.title}</a></li>` ).join(''); return ` <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>离线模式 - 应用不可用</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; } .offline-container { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; max-width: 500px; } .offline-icon { font-size: 64px; margin-bottom: 20px; } h1 { color: #333; margin-bottom: 10px; } p { color: #666; margin-bottom: 30px; } .offline-pages { text-align: left; margin-top: 30px; } .offline-pages h3 { color: #333; margin-bottom: 15px; } .offline-pages ul { list-style: none; padding: 0; } .offline-pages li { margin-bottom: 10px; } .offline-pages a { color: #007bff; text-decoration: none; padding: 8px 12px; border: 1px solid #007bff; border-radius: 4px; display: inline-block; transition: all 0.3s ease; } .offline-pages a:hover { background: #007bff; color: white; } .retry-btn { background: #28a745; color: white; border: none; padding: 12px 24px; border-radius: 4px; font-size: 16px; cursor: pointer; transition: background 0.3s ease; } .retry-btn:hover { background: #218838; } </style> </head> <body> <div class="offline-container"> <div class="offline-icon">📡</div> <h1>您当前处于离线状态</h1> <p>请检查网络连接,或访问以下已缓存的页面:</p> <button class="retry-btn" onclick="window.location.reload()">重新连接</button> <div class="offline-pages"> <h3>可用的离线页面:</h3> <ul> ${navigationLinks} </ul> </div> </div> <script> // 监听网络状态变化 window.addEventListener('online', () => { window.location.reload(); }); // 定期检查网络状态 setInterval(() => { if (navigator.onLine) { window.location.reload(); } }, 5000); </script> </body> </html>`; } } 2. 综合Service Worker实现class ProgressiveWebAppServiceWorker { private cacheManager: CacheManager; private syncManager: BackgroundSyncManager; private offlineManager: OfflinePageManager; private invalidationManager: CacheInvalidationManager; constructor() { this.cacheManager = new CacheManager(); this.syncManager = new BackgroundSyncManager(); this.offlineManager = new OfflinePageManager(); this.invalidationManager = new CacheInvalidationManager(); } async initialize(): Promise<void> { await this.setupCacheStrategies(); await this.syncManager.init(); await this.registerEventListeners(); } private async setupCacheStrategies(): Promise<void> { // 预缓存策略 - 用于关键资源 this.cacheManager.addStrategy(new PrecacheStrategy({ patterns: [ /\/static\/.*\.(js|css|woff2?)$/, /\/assets\/.*\.(png|jpg|jpeg|gif|svg)$/, /\/manifest\.json$/, /\/(index\.html)?$/ ], methods: ['GET'], cacheName: 'precache-v1', maxAge: 30 * 24 * 60 * 60 * 1000 // 30 days })); // 网络优先策略 - 用于API请求 this.cacheManager.addStrategy(new NetworkFirstStrategy({ patterns: [ /\/api\/.*$/, /\/graphql$/ ], methods: ['GET', 'POST'], cacheName: 'api-cache-v1', timeout: 5000 })); // 运行时缓存策略 - 用于图片和媒体资源 this.cacheManager.addStrategy(new RuntimeCacheStrategy({ patterns: [ /\.(png|jpg|jpeg|gif|svg|webp)$/, /\.(mp4|webm|ogg)$/, /\.(mp3|wav|flac)$/ ], methods: ['GET'], cacheName: 'media-cache-v1', maxEntries: 50, maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days })); // 添加缓存失效规则 this.invalidationManager.addRule(new HeaderBasedInvalidation('x-cache-version', 'v1')); this.invalidationManager.addRule(new TimeBasedInvalidation(24 * 60 * 60 * 1000)); // 1 day } private registerEventListeners(): void { self.addEventListener('install', (event: ExtendableEvent) => { event.waitUntil(this.handleInstall()); }); self.addEventListener('activate', (event: ExtendableEvent) => { event.waitUntil(this.handleActivate()); }); self.addEventListener('fetch', (event: FetchEvent) => { event.respondWith(this.handleFetch(event.request)); }); self.addEventListener('message', (event: MessageEvent) => { this.handleMessage(event); }); } private async handleInstall(): Promise<void> { console.log('Service Worker installing...'); await this.cacheManager.precacheCriticalResources(); await self.skipWaiting(); } private async handleActivate(): Promise<void> { console.log('Service Worker activating...'); await this.cacheManager.cleanupOldCaches(); await self.clients.claim(); } private async handleFetch(request: Request): Promise<Response> { try { const response = await this.cacheManager.handleRequest(request); if (await this.invalidationManager.shouldInvalidate(request, response)) { await this.invalidationManager.invalidate(request); return fetch(request); } return response; } catch (error) { console.error('Fetch handling error:', error); return this.handleOfflineRequest(request); } } private async handleOfflineRequest(request: Request): Promise<Response> { if (request.destination === 'document') { const offlinePage = await this.offlineManager.generateOfflineFallback(); return new Response(offlinePage, { headers: { 'Content-Type': 'text/html' } }); } return new Response('Offline - Resource not available', { status: 503, statusText: 'Service Unavailable' }); } private handleMessage(event: MessageEvent): void { const { type, data } = event.data; switch (type) { case 'SKIP_WAITING': self.skipWaiting(); break; case 'PRECACHE_URLS': this.cacheManager.precacheUrls(data.urls); break; case 'REGISTER_OFFLINE_PAGE': this.offlineManager.registerPage(data.url, data.content, data.metadata); break; default: console.log('Unknown message type:', type); } } } // 初始化Service Worker const sw = new ProgressiveWebAppServiceWorker(); sw.initialize().catch(console.error); 性能优化与最佳实践1. 缓存性能监控class CachePerformanceMonitor { private metrics: CacheMetrics; private startTime: number; constructor() { this.metrics = { hitRate: 0, missRate: 0, averageResponseTime: 0, cacheSize: 0, evictionCount: 0 }; this.startTime = performance.now(); } recordCacheHit(): void { this.metrics.hitRate++; } recordCacheMiss(): void { this.metrics.missRate++; } recordResponseTime(duration: number): void { const totalRequests = this.metrics.hitRate + this.metrics.missRate; this.metrics.averageResponseTime = (this.metrics.averageResponseTime * (totalRequests - 1) + duration) / totalRequests; } recordCacheEviction(): void { this.metrics.evictionCount++; } getMetrics(): CacheMetrics { const totalRequests = this.metrics.hitRate + this.metrics.missRate; const hitRate = totalRequests > 0 ? this.metrics.hitRate / totalRequests : 0; return { ...this.metrics, hitRatePercentage: hitRate * 100, missRatePercentage: (1 - hitRate) * 100, uptime: performance.now() - this.startTime }; } reset(): void { this.metrics = { hitRate: 0, missRate: 0, averageResponseTime: 0, cacheSize: 0, evictionCount: 0 }; this.startTime = performance.now(); } } 2. 内存管理优化class MemoryManager { private maxMemoryUsage: number; private currentUsage: number; constructor(maxMemoryUsage: number = 50 * 1024 * 1024) { // 50MB default this.maxMemoryUsage = maxMemoryUsage; this.currentUsage = 0; } async estimateMemoryUsage(response: Response): Promise<number> { const blob = await response.blob(); return blob.size; } canAddToCache(size: number): boolean { return this.currentUsage + size <= this.maxMemoryUsage; } addToCache(size: number): void { this.currentUsage += size; } removeFromCache(size: number): void { this.currentUsage = Math.max(0, this.currentUsage - size); } getUsageRatio(): number { return this.currentUsage / this.maxMemoryUsage; } async forceCleanup(): Promise<void> { if (this.getUsageRatio() > 0.9) { // 触发缓存清理 const cacheNames = await caches.keys(); const oldestCache = cacheNames.sort().shift(); if (oldestCache) { await caches.delete(oldestCache); } } } } 总结本文详细介绍了Service Worker缓存策略与离线应用架构设计的核心概念和实现方案。通过智能缓存策略、高效的缓存失效机制、后台同步和离线页面管理,我们可以构建出性能优异、用户体验良好的PWA应用。关键要点:多层缓存策略:结合预缓存、运行时缓存和网络优先策略智能失效机制:基于时间和头部的缓存失效管理后台同步:确保离线操作在网络恢复后同步性能监控:实时监控缓存命中率和响应时间内存优化:智能内存管理和强制清理机制这些技术的综合运用能够显著提升Web应用的离线能力和整体性能。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
1.883277s