Loading... # 引言 因为个人开发者API调用的次数限制原因,加载相同瓦片时仍然会耗费API次数,通过JS的拦截器重定向到web服务器,由服务器进行缓存,进而实现2次调用时(命中缓存)时,走缓存瓦片,进而节省API次数。 # JS拦截器 ```js class UniversalRequestInterceptor { constructor() { this.interceptors = []; this.init(); } use(callback) { if (typeof callback === 'function') { this.interceptors.push(callback); } } triggerInterceptors(requestInfo) { const info = { ...requestInfo }; for (const callback of this.interceptors) { try { const result = callback(info); if (result === false) { console.log(`[拦截阻止] 类型: ${info.type}, 地址: ${info.url}`); return false; } if (info.url !== requestInfo.url) { console.log(`[请求篡改] 原地址: ${requestInfo.url} → 新地址: ${info.url}`); } } catch (e) { console.error(`[拦截回调异常]`, e); } } console.log(`[拦截通过] 类型: ${info.type}, 地址: ${info.url}`); return true; } init() { this.interceptImage(); } interceptImage() { const originalCreateElement = document.createElement; const interceptor = this; const originalSrcDesc = Object.getOwnPropertyDescriptor(HTMLImageElement.prototype, 'src') || Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'src'); document.createElement = function (tagName) { const elem = originalCreateElement.call(this, tagName); if (tagName.toLowerCase() === 'img') { // 给动态 img 绑定相同的拦截逻辑 Object.defineProperty(elem, 'src', { set: function (url) { const urlStr = url.toString().trim(); if (!urlStr) { originalSrcDesc.set?.call(elem, urlStr); return; } const requestInfo = { type: 'image', url: urlStr, method: 'GET', params: this.parseParams(urlStr) }; requestInfo.url = "/getGeoImage?url=" + encodeURIComponent(requestInfo.url); const allow = interceptor.triggerInterceptors(requestInfo); if (allow) { originalSrcDesc.set?.call(elem, requestInfo.url); } else { elem.onerror?.call(elem, new Error(`动态 Image 请求被拦截: ${urlStr}`)); } }.bind(elem), get: function () { return originalSrcDesc.get?.call(elem) || ''; }, configurable: true, enumerable: true }); // 动态 img 的 setAttribute 处理 const originalSetAttr = elem.setAttribute; elem.setAttribute = function (name, value) { if (name.toLowerCase() === 'src') { elem.src = value; } else { originalSetAttr.call(this, name, value); } }; // 解析参数辅助方法 elem.parseParams = function (url) { const params = {}; const query = url.split('?')[1]; if (query) { query.split('&').forEach(item => { const [key, value] = item.split('='); if (key) params[decodeURIComponent(key)] = decodeURIComponent(value || ''); }); } return params; }; } return elem; }; } // 通用:解析 URL 参数 parseParams(url) { const params = {}; const query = url.split('?')[1]; if (query) { query.split('&').forEach(item => { const [key, value] = item.split('='); if (key) params[decodeURIComponent(key)] = decodeURIComponent(value || ''); }); } return params; } } const requestInterceptor = new UniversalRequestInterceptor(); requestInterceptor.use((requestInfo) => { console.log('[拦截回调]', requestInfo); if(requestInfo.type === 'image'){ return true; }}); console.log('请求拦截器初始化完成'); ``` 其中代码由AI生成,部分代码进行改造,实际上他的调用并非XHR,因此这里走了些弯路。 其中`requestInfo.url = "/getGeoImage?url=" + encodeURIComponent(requestInfo.url);`是把请求地址改成Host,并且加上真正调用的地址,如`http://t7.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&tk=xxxxxxxxxx&TILECOL=aaaa&TILEROW=bbbb&TILEMATRIX=13` # 服务端实现 这里以FastAPI实现 ``` @router.get("/getGeoImage", summary="getGeoImage") async def getGeoImage(request: Request): try: trust_url = request.query_params['url'] params_dict = parse_qs(trust_url) layer = params_dict['LAYER'][0] tilecol = params_dict['TILECOL'][0] tilerow = params_dict['TILEROW'][0] tilematrix = params_dict['TILEMATRIX'][0] cache_file = f"""{layer}_{tilecol}_{tilerow}_{tilematrix}""" cache_path = os.path.join(sysconfig.get_root_directory(),'static','geo_data',cache_file) headers = dict(request.headers) del headers['host'] del headers['referer'] if os.path.exists(cache_path): print("cache") return FileResponse(cache_path, media_type='image/png') else: image = requests.get(trust_url, headers=headers) with open(cache_path,'wb') as f: f.write(image.content) return FileResponse(cache_path, media_type='image/png') except Exception as e: import traceback traceback.print_exc() return re.Response(code=500, msg='error', data=None) ``` 其中图层、经纬度、级别作为关键数据,进行提取当作文件名,当命中缓存时,走缓存,否则下载到本地并返回。  # 结语 这里仅提供缓存思路,如果地图更新,需要手动更新缓存,因此不会保证地图数据是最新的,使用者应遵循用户协议。 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏