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

企业H5站点升级PWA (七)

如何在 Service Worker 中实现缓存的版本控制和更新策略,以确保用户始终获取到最新的资源?

下面直接结合你当前的 sw.js,给一套可落地的“版本控制 + 更新策略”,目标是:

  • 有明确的缓存版本号,发版时可控地失效旧缓存;
  • index.html 等壳尽量拿最新;
  • JS/CSS 等静态资源不会长期卡在老版本;
  • SW 更新后,用户可以自动/半自动切到新版本。

1. 版本号控制:用 CACHE_VERSION 驱动缓存失效

你现在已经有:

const CACHE_VERSION = 'v0.0.5';
const CACHE_PREFIX = 'densenapp-static-';
const CACHE_NAME = `${CACHE_PREFIX}${CACHE_VERSION}`;

这是第一层“版本控制”。建议约定:

  • 每次前端发版,手动提升 CACHE_VERSION(如 v0.0.6);
  • activate 中按前缀删除旧版本缓存。

你已经写了按前缀清理旧缓存的逻辑,这部分是正确的:

self.addEventListener('activate', (event) => {event.waitUntil((async () => {const keys = await caches.keys();await Promise.all(keys.filter((key) => key.startsWith(CACHE_PREFIX) && key !== CACHE_NAME).map((key) => caches.delete(key)));// 通知所有客户端:SW 已激活新版本const clientsList = await self.clients.matchAll({ type: 'window' });for (const client of clientsList) {client.postMessage({type: 'SW_ACTIVATED',version: CACHE_VERSION});}await self.clients.claim();})());
});

这样只要版本号变了:

  • 安装新 SW 时会建新缓存 densenapp-static-v0.0.6
  • 激活时会删掉所有旧的 densenapp-static-*

2. 更新策略:不同资源用不同缓存策略

2.1 建议策略组合

  1. HTML(尤其是 index.html
    使用 network-first:优先从网络拉,失败再用缓存。这样壳页面几乎总是最新。

  2. JS/CSS/图片等静态资源
    使用 stale-while-revalidate

    • 有缓存时立即返回缓存(响应快);
    • 同时后台去拉网络,如果成功则更新缓存;
    • 下次访问时就是新资源。
      即便你忘了改 CACHE_VERSION,它也会逐步更新。
  3. API

    • 你已经对部分 API 用了 networkFirst(request, 'api-pos-cache')
    • 其它 /api/ 直接放行,不缓存(避免数据过期问题)。

2.2 在现有 fetch 上的改造示例

可以把当前 fetch 改成类似这样(保持你现有 API 处理逻辑):

self.addEventListener('fetch', (event) => {const { request } = event;if (request.method !== 'GET') {return;}const url = new URL(request.url);// 1. HTML 文档:network-first,保证壳尽量最新if (request.destination === 'document') {event.respondWith(networkFirst(request, CACHE_NAME));return;}// 2. 静态资源:stale-while-revalidate,兼顾速度和更新if (request.destination === 'script'|| request.destination === 'style'|| request.destination === 'image'|| request.destination === 'font') {event.respondWith(staleWhileRevalidate(request, CACHE_NAME));return;}// 3. 部分需要离线兜底的 API:network-first + 单独缓存区if (url.pathname.startsWith('/api/getProduct')|| url.pathname.startsWith('/api/help')|| url.pathname.startsWith('/api/pro')|| url.pathname.startsWith('/pro/customer')|| url.pathname.startsWith('/files')|| url.pathname.startsWith('/view')) {event.respondWith(networkFirst(request, 'api-pos-cache'));return;}// 4. 其它 API 直接放行,避免被 SW 干扰if (url.pathname.startsWith('/api/')|| url.pathname.startsWith('/pro/')|| url.pathname.startsWith('/swagger')|| url.pathname.includes('/signalr')) {return;}
});

然后在底部增加一个 staleWhileRevalidate,并保留你原来的 networkFirst(可以复用):

function networkFirst(request, cacheName) {return fetch(request).then((response) => {if (response && response.status === 200) {const clone = response.clone();caches.open(cacheName).then((cache) => {cache.put(request, clone);});}return response;}).catch(() => {return caches.match(request).then((cached) => {if (cached) {return cached;}// 对 HTML 做兜底if (request.headers.get('accept')?.includes('text/html')) {return caches.match('./index.html');}return new Response('网络不可用,且没有可用的缓存数据。', {status: 503,headers: { 'Content-Type': 'text/plain; charset=utf-8' }});});});
}function staleWhileRevalidate(request, cacheName) {return caches.match(request).then((cached) => {const fetchPromise = fetch(request).then((response) => {if (response && response.status === 200) {const clone = response.clone();caches.open(cacheName).then((cache) => {cache.put(request, clone);});}return response;}).catch(() => {// 网络失败时不抛错,留给上层兜底return undefined;});if (cached) {// 有缓存:先用缓存,后台刷新return cached;}// 没缓存:只能等网络return fetchPromise.then((response) => {if (response) {return response;}// 最后兜底:如果是 HTML,再退回 index.htmlif (request.headers.get('accept')?.includes('text/html')) {return caches.match('./index.html');}return new Response('网络不可用,且本资源未被缓存。', {status: 503,headers: { 'Content-Type': 'text/plain; charset=utf-8' }});});});
}

这样配合上面的版本号策略,即使你偶尔忘记改 CACHE_VERSION,静态资源也会被后台拉新。


3. 前端配合:SW 更新后自动或提示刷新

你的 activate 中已经通过 postMessage 通知页面:

client.postMessage({type: 'SW_ACTIVATED',version: CACHE_VERSION
});

在前端公共 JS(例如 js/index.js 或某个引导脚本)里加一个监听,可以选择:

  • 简单粗暴:直接自动刷新;
  • 或者弹个“发现新版本,点击刷新”的提示。

一个自动刷新的最简版:

if ('serviceWorker' in navigator) {navigator.serviceWorker.addEventListener('message', (event) => {if (event.data?.type === 'SW_ACTIVATED') {console.log('Service Worker 已更新到版本:', event.data.version);// 直接刷新页面,让用户切到新资源window.location.reload();}});
}

这样就形成了完整链路:

  1. 你改了前端代码 → 提升 CACHE_VERSION → 部署;
  2. 客户端加载新 SW:
    • 安装新缓存(PRECACHE_URLS);
    • 激活时清旧缓存,postMessage 给页面;
  3. 页面收到 SW_ACTIVATEDreload 一次;
  4. 用户立刻跑在最新资源 + 最新缓存上。

4. 实际发版时你需要做的事

  1. 修改前端代码后

    • 更新 CACHE_VERSION(如从 v0.0.5 改到 v0.0.6);
    • 如果你对静态文件做版本号(index.js?v=0.0.6)或 hash,也一并更新。
  2. 发布(Kestrel / WinForms 宿主的 wwwroot 同步上去)。

  3. 客户端行为

    • 下次打开时:
      • 新 SW 安装并预缓存;
      • 删除旧缓存;
      • 通知页面(SW_ACTIVATED);
      • 页面刷新,所有资源切新。

如果你希望,我可以按你刚才这份 sw.js 直接给一个“已经嵌好 networkFirst + staleWhileRevalidate”的完整版本,你只要整体替换就可以。

http://www.jsqmd.com/news/392393/

相关文章:

  • 企业H5站点升级PWA (四)
  • 题解:洛谷 P3916 图的遍历
  • 【硬盘】个人数据备份的各种方式##37
  • 题解:洛谷 P5318 【深基18.例3】查找文献
  • 题解:洛谷 P4017 最大食物链计数
  • 题解:洛谷 P1113 杂务
  • 别只会用 getData!Watcher 注册源码流程全拆解
  • Java线程解析:5种线程创建方法及应用场景 - 指南
  • 题解:洛谷 P2814 家谱
  • 题解:洛谷 P3879 [TJOI2010] 阅读理解
  • 2024 年 09 月 二级真题(1)--数位之和
  • 2026年龙岩连城长汀红白喜事鼓吹铜管乐队演出推荐:客家非遗与市场化服务的平衡之选 - 小白条111
  • 题解:洛谷 P4305 [JLOI2011] 不重复数字
  • 12:内核ROP与提权技术
  • 13:现代内核保护机制与绕过技术
  • 14:跨架构内核漏洞利用差异
  • 超市在线销售与分析|基于Python + Django超市在线销售与分析系统(源码+数据库+文档)
  • AI知识图谱构建:企业智能搜索的底层架构
  • 大数据领域数据中台的教育培训机构数据分析
  • 一天一个开源项目(第26篇):ZeroClaw - 零开销、全 Rust 的自主 AI 助手基础设施,与 OpenClaw 的关系与对比
  • OpenClaw(Clawdbot)部署指南:2026年天翼云部署快速上手
  • 彼得林奇的“家庭作业“投资法
  • 实用指南:Elasticsearch:监控 LLM 推理和 Agent Builder 使用 OpenRouter
  • AI提示系统反馈机制设计:如何解决“反馈噪音”问题?
  • 企业H5站点升级PWA (一)
  • 456348568
  • 75757
  • MongoDB备份策略:大数据场景下全量+增量备份的实现与恢复测试
  • AI训练算力利用率低?架构师的4个算力优化+调度方案
  • OpenClaw(Clawdbot):2026阿里云部署教程,掌握技巧超容易