service worker 本身不提供异步资源完整性修复功能,但可通过组合 fetch/install 事件、indexeddb 存储预期哈希、cache api 缓存管理及 crypto.subtle 哈希校验,在响应流中按需验证并后台静默修复损坏资源。

Service Worker 本身不直接提供“异步资源完整性修复”这一标准功能,它也没有内置的哈希校验、自动重试下载或动态替换损坏缓存的机制。但你可以基于它的事件驱动模型和缓存控制能力,组合实现一个具备完整性感知与自愈能力的本地分发中心——关键在于把完整性验证逻辑嵌入到 fetch 和 install 流程中,并借助 IndexedDB 或 Cache API 配合 Promise 异步协作完成修复。
明确“完整性修复”的实际含义
所谓“资源完整性修复”,通常指:当缓存中的某个资源(如 JS、CSS、字体)因传输中断、磁盘错误或篡改导致内容损坏(例如文件长度正确但内容错乱),应用在加载时能识别异常并自动重新获取、校验、替换。Service Worker 不会主动扫描缓存做健康检查,因此需按需触发:
- 使用 Subresource Integrity(SRI)哈希仅适用于 HTML 中通过 <script>/<link> 加载的资源,无法约束 SW 自己 fetch 的响应;</script>
- 真正可控的方式是:为每个需保护的资源维护一份预期 SHA-256(或类似)摘要,在 fetch 响应后读取 body 并计算哈希,比对失败则标记为“待修复”;
- 修复动作本身必须异步执行(避免阻塞主线程或 fetch 响应),且不能影响当前请求的返回(例如先返回旧缓存,后台静默更新)。
构建本地分发中心的四层结构
这个“中心”不是独立服务,而是由 Service Worker 主导的缓存协调层,包含以下协同模块:
- 资源注册表:用 IndexedDB 存储资源 URL → 预期哈希 → 缓存时间戳 → 修复状态("ok" / "corrupted" / "repairing");
- 受控缓存池:使用 caches.open('dist-v1') 管理,所有资源均通过 cache.put(key, response) 写入,确保可读取 body;
- 异步校验器:fetch 事件中对关键资源(如 /app.js、/runtime.json)调用 stream.clone().getReader() 逐块读取并累加哈希,完成后比对;
- 后台修复引擎:校验失败时,启动一个 self.skipWaiting() 后仍存活的 fetch 请求重拉资源,成功后写入新缓存 + 更新注册表,再通知客户端刷新可用性。
关键代码逻辑示意
以下为 fetch 阶段的完整性感知处理片段(简化版,依赖 crypto.subtle):
self.addEventListener('fetch', event => {
const url = new URL(event.request.url);
// 仅对白名单资源启用完整性检查
if (!['/app.js', '/config.json'].includes(url.pathname)) return;
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) {
return cached.text().then(text => {
// 计算当前缓存内容哈希
return crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
.then(hash => btoa(String.fromCharCode(...new Uint8Array(hash))))
.then(calculatedHash => {
const expected = getResourceHash(url.pathname); // 从 IndexedDB 读
if (calculatedHash === expected) return cached;
// 校验失败:返回旧缓存保可用,同时触发后台修复
repairResource(url.pathname);
return cached;
});
});
}
// 无缓存则走网络,后续 install 阶段再校验并入库
return fetch(event.request);
})
);
});
function repairResource(path) {
fetch(path)
.then(res => res.arrayBuffer())
.then(buf => crypto.subtle.digest('SHA-256', buf))
.then(hash => {
const hex = Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0')).join('');
if (hex === getResourceHash(path)) {
return caches.open('dist-v1').then(cache =>
cache.put(path, new Response(buf, { headers: { 'Content-Type': 'application/javascript' } }))
);
}
})
.then(() => updateRegistryStatus(path, 'ok'));
}
注意事项与边界条件
这类设计在真实场景中需谨慎权衡:
- 哈希计算开销较大,不宜对大文件(如视频、图片)或高频资源(如 /metrics)启用;
- IndexedDB 读写需 await,不可在 install/activate 中同步阻塞,应封装为异步初始化流程;
- 浏览器可能终止空闲 SW,修复任务需设计为幂等(重复执行无副作用),并利用 sync 事件兜底;
- 首次安装时,ASSETS_TO_CACHE 列表中的资源应在 install 中完成哈希预存,否则冷启动无依据校验。










