浏览器端缓存地图请求:使用 IndexedDB + ajax-hook 提升地图加载速度

前言

在 Web 地图应用中(例如基于 CesiumJS、mars‑3d 或其它 3D 地球/地图库),一个常见性能瓶颈是"底图/地形/瓦片/影像请求过多、网络延迟高、重复请求资源多"。

为了解决这个问题,可以将重要的地图请求结果缓存到浏览器端(使用 IndexedDB),下一次再请求相同 URL 时,优先从缓存返回,从而减少网络请求、加快加载速度、改善用户体验。本文将演示如何使用 localforage(用于 IndexedDB 存储) + ajax‑hook(用于拦截 XHR/Fetch 请求)实现这一功能。


核心思路

  • 拦截所有发出的 AJAX 请求(通过 ajax-hook)

  • 对符合特定规则(例如天地图、ArcGIS 地形服务等)的请求,先检查浏览器缓存(IndexedDB)是否已有响应数据

  • 如果缓存命中,则直接返回缓存数据,避免真实网络请求

  • 如果缓存未命中,则让请求继续,响应回来后,再将结果写入缓存,以便下次使用

这样做的好处包括:

  • 减少重复请求,减轻服务器和网络负担

  • 缩短客户端加载时间,提升性能

  • 优化弱网络或移动设备上的用户体验

在地图应用中,这种缓存尤其有意义,因为很多瓦片/地形/影像都是"可复用"的、被多次请求的资源。早在 Cesium 社区就有人讨论过将瓦片存入 IndexedDB 用作离线或加速方案。 Cesium Community+1


代码详解及意义

下面是完整代码(你已提供)并附上详细说明:

javascript 复制代码
import localforage from 'localforage';
import { proxy } from 'ajax-hook';

const cache = localforage.createInstance({
    name: 'mapRequestCache',
    driver: localforage.INDEXEDDB,
});

const rules = [
    /tianditu\.gov\.cn\/DataServer/,
    /elevation3d\.arcgis\.com\/arcgis\/rest\/services\/WorldElevation3D\/Terrain3D\/ImageServer/
];

function isMatchRule(url) {
    return rules.some((rule) => rule.test(url));
}

function getKey(url, method, version = 'v1') {
    return `${version}-${url}-${method}`;
}

proxy({
    onRequest: async (config, handler) => {
        const url = config.url;
        if (isMatchRule(url)) {
            const key = getKey(url, config.method);
            const data = await cache.getItem(key);
            if (data) {
                handler.resolve({
                    config,
                    status: 200,
                    response: data,
                    header: config.headers
                });
                return;
            }
        }
        return handler.next(config);
    },
    onResponse: async (response, handler) => {
        const url = response.config.url;
        if (response.status === 200 && isMatchRule(url)) {
            const key = getKey(response.config.url, response.config.method);
            await cache.setItem(key, response.response);
        }
        return handler.next(response);
    }
});

export default cache;

下面按模块逐一解释其意义与作用:

1. 引入 localforage 与 ajax-hook

javascript 复制代码
import localforage from 'localforage';
import { proxy } from 'ajax-hook';
  • localforage:一个包装 IndexedDB、Web SQL 和 localStorage 的库,提供统一异步 API,用于在浏览器中存储较大数据(例如图片、瓦片、二进制 Blob)。

  • ajax-hook:一个用来拦截 XHR/Fetch 请求的库。它允许你在请求发出前(onRequest)和响应返回后(onResponse)插入逻辑,从而可实现缓存、修改请求/响应、阻止请求等。 GitHub

2. 创建缓存实例

javascript 复制代码
const cache = localforage.createInstance({
    name: 'mapRequestCache',
    driver: localforage.INDEXEDDB,
});
  • 这里创建了一个 localforage 实例,命名为 'mapRequestCache',并明确指定 driver: localforage.INDEXEDDB,即使用浏览器的 IndexedDB 存储。

  • 这意味着你的缓存数据将以键值对形式保存在 IndexedDB 中(比 localStorage 容量更大、性能更好)。

3. 定义要拦截/缓存的 URL 规则

javascript 复制代码
const rules = [
    /tianditu\.gov\.cn\/DataServer/,
    /elevation3d\.arcgis\.com\/arcgis\/rest\/services\/WorldElevation3D\/Terrain3D\/ImageServer/
];

function isMatchRule(url) {
    return rules.some((rule) => rule.test(url));
}
  • rules 数组中定义了若干正则表达式,匹配你希望缓存的地图服务请求 URL(如 天地图 "tianditu.gov.cn/DataServer"、ArcGIS 地形服务 "elevation3d.arcgis.com/...Terrain3D/ImageServer")。

  • isMatchRule(url) 函数用于判断一个 URL 是否匹配这些规则。如果匹配,则说明这是一个"值得缓存"的请求。

  • 你可以根据自己的项目,扩展或修改这些规则(例如增加瓦片服务、3DTiles、贴图 URL 等)。

4. 生成缓存 Key

javascript 复制代码
function getKey(url, method, version = 'v1') {
    return `${version}-${url}-${method}`;
}
  • 每个缓存条目需要一个唯一 key,以便存取。这里用 ${version}-${url}-${method} 来生成。

  • url:请求的 URL

  • method:请求方法(如 GET、POST)

  • version:可选版本号,用于在服务更新/格式变更时"失效"旧缓存。

  • 使用版本号是一个好习惯:当底图服务、瓦片格式、API 版本发生变动时,通过改变 version 值即可让 key 全部不同,从而避免使用过期缓存。

5. 拦截请求(onRequest)

javascript 复制代码
onRequest: async (config, handler) => {
    const url = config.url;
    if (isMatchRule(url)) {
        const key = getKey(url, config.method);
        const data = await cache.getItem(key);
        if (data) {
            handler.resolve({
                config,
                status: 200,
                response: data,
                header: config.headers
            });
            return;
        }
    }
    return handler.next(config);
}

意义说明:

  • 在请求发送之前,这里检查 config.url 是否匹配规则(即 isMatchRule(url))。

  • 如果匹配,则尝试从 cache.getItem(key) 获取缓存数据。

  • 如果缓存命中(即 data 不为空),就 直接返回缓存结果 ,通过 handler.resolve() 模拟一个成功响应,从而跳过真实网络请求

  • 如果缓存未命中,则调用 handler.next(config),让请求继续发送。

这样就实现了"优先使用缓存"的逻辑。

6. 拦截响应(onResponse)

javascript 复制代码
onResponse: async (response, handler) => {
    const url = response.config.url;
    if (response.status === 200 && isMatchRule(url)) {
        const key = getKey(response.config.url, response.config.method);
        await cache.setItem(key, response.response);
    }
    return handler.next(response);
}

意义说明:

  • 真实网络请求返回后 ,首先检查 response.status === 200url 匹配规则。

  • 如果满足,则生成缓存 key ,并将 response.response 存入缓存。

  • 然后调用 handler.next(response) 继续处理响应(例如传递给业务逻辑)。

这样就保证了"第一次请求网络 → 写入缓存;以后相同请求 → 直接读缓存"的流程。

7. 导出缓存实例

javascript 复制代码
export default cache;
  • 导出 cache 实例,方便在其它模块中对缓存进行管理/清理/查询。

  • 比如你可能想在特定场景下清理缓存、查看缓存条目数、打印日志等。


使用步骤与实践建议

下面是你在地图项目中(例如使用 mars-3d/Cesium)建议的使用流程与注意事项:

  1. 在项目入口处初始化

    在项目的最早期(如 main.js、入口 script 中)引入上述缓存模块,使其在页面加载初期即激活拦截器。

    javascript 复制代码
    import './mapRequestCache.js';
  2. 定义合适的规则(rules)

    根据你的项目中所用的地图服务类型(瓦片、地形、影像、3DTiles、模型等)调整 rules 正则。例如:

    javascript 复制代码
    /.*tianditu\.com\/DataServer.*/,
    /.*tileserver\.yourdomain\.com\/tiles\/.*/,
    /.*3dtiles\.yourservice\.com\/tileset.json/,
    /.*\.glb$/,

    这样保证你缓存了对性能影响最大的资源。

  3. 考虑资源格式与缓存大小

    • 场景中可能请求的是 图片瓦片 (PNG/JPG) 、**二进制 ArrayBuffer/Blob **、JSON/3DTiles json 。你需要确认 ajax-hook 拦截时 response.response 是否包含你想缓存的数据(可能是字符串、二进制)。

    • 如果资源体量很大(例如高分辨率瓦片、影像、大 3DTiles 包),缓存可能占用大量 IndexedDB 空间。你要考虑:缓存条目数控制缓存大小控制版本切换机制

    • 在某些场景,浏览器自身的 HTTP 缓存/服务工作者缓存也可能起作用。你要测试确保你的机制是真实起效。

  4. 测试缓存命中 + 加载速度提升

    • 使用浏览器 DevTools → Network 面板,观察某个瓦片或地形请求第一次时有网络请求,第二次是否被缓存拦截(无网络请求或网络请求被替换为本地返回)。

    • 检测对 mars-3d 场景加载速度是否有显著提升。

    • 测试弱网络或手机设备情况,缓存机制通常对它们提升更明显。

  5. 处理版本或服务变动

    • 当地图服务底图/瓦片格式/参数发生变更时,你要"让缓存失效",避免服务更新但客户端使用了旧缓存。可通过改变 version 参数(如 'v2')或清理旧 key 实现。

    • 你还可以在缓存模块里加入清理逻辑:例如每次版本变动就 cache.clear()

  6. 监控 IndexedDB 使用情况

    • 定期查看浏览器 Application 面板 → IndexedDB → mapRequestCache 库,观察条目数、存储大小。

    • 如果缓存数据过多,可以实现"最少使用淘汰(LRU)"逻辑、或定期清理旧条目。


注意事项与风险提示

  • 并非所有请求都能被 ajax-hook 成功拦截。例如使用 img 标签、Canvas、WebGL 纹理加载、或者某些 fetch / Resource.fetch 机制可能绕过你的代理。你需要验证在你的地图库(例如 mars-3d)中实际资源加载方式。

  • 如果地图服务严格依赖 CORS 、或返回 ArrayBuffer/Blob 数据,缓存读取与返回时可能需要做额外处理(如 Blob 转 ObjectURL、Base64 编码等)。

  • 缓存如果使用不当可能导致"旧数据展示"或"风格/内容与服务器不一致"的问题。必须做好版本控制。

  • 浏览器不同平台对于 IndexedDB 大小、性能支持不同。在容量较低或私有模式下可能受限。

  • 使用第三方地图服务(如 天地图、ArcGIS Online)时,需确认其服务协议允许客户端缓存;部分服务可能限制缓存或离线使用。


总结

通过这段代码,你可以在浏览器中为地图应用构建一个"请求拦截 + 本地缓存"机制,从而显著提升地图加载性能、减少重复网络请求。关键在于:

  • 明确哪些资源值得缓存(瓦片、地形、影像、3D 模型等)

  • 拦截请求 + 返回缓存 +写入缓存的流程

  • 版本控制与缓存管理。

如果你在项目中使用 mars‑3d,建议将这段缓存逻辑与底图/地形图层的加载流程结合起来使用,并在实际设备(尤其是移动设备/弱网络)上做性能测试。

相关推荐
没有故事、有酒4 小时前
Ajax介绍
前端·ajax·okhttp
朝新_4 小时前
【SpringMVC】详解用户登录前后端交互流程:AJAX 异步通信与 Session 机制实战
前端·笔记·spring·ajax·交互·javaee
咖啡の猫8 小时前
vue 项目中常用的 2 个 Ajax 库
vue.js·ajax·okhttp
星梦清河10 小时前
Redis(四):缓存击穿及其解决方案(SpringBoot+mybatis-plus)
spring boot·redis·缓存
塔能物联运维14 小时前
物联网边缘节点数据缓存优化与一致性保障技术
java·后端·物联网·spring·缓存
Maỿbe15 小时前
Redis的持久化
数据库·redis·缓存
白露与泡影21 小时前
Redis:我是如何与客户端进行通信的
数据库·redis·缓存
小生凡一1 天前
redis 大key、热key优化技巧|空间存储优化|调优技巧(一)
数据库·redis·缓存
小马哥编程1 天前
【软考架构】案例分析-对比MySQL查询缓存与Memcached
java·数据库·mysql·缓存·架构·memcached