如何处理第三方地图使用限额问题?

1,245 阅读7分钟

本篇文章主要介绍在最近维护的项目中,由于百度地图JS地点检索服务,配额超限引出的问题;以及最后采用的优化方法。

1、问题表现

上周的任务比较多,当我正常按工作计划开展的时候,突然群里用户反馈大屏地图的轮廓无法正常显示了?当时我还蒙了一下,啥大屏地图? 当用户截图过来的时候,我才明白他要表达的意思,大屏地图部分地区的轮廓无法正常显示出来了?这到底是咋回事儿呢?不应该啊?

2、思索、排查

我立马本地启动项目开展了排查,该项目我是第二个接手的,因此一时无法确定该问题是由何引起的。
当项目启动后,进入该页面发现确实出现了报错 image.png 咦~,好像是百度地图的ak出问题了,要不换一下我个人的ak测一测?说做就做,我将项目中的ak换成自己的,然后刷新一看;
嘿!好了,这个问题如此轻易的就被我解决了,看来老板又要给我升职加薪了(陷入了美好幻想中~)。
当我在激动中,多刷了几次页面,突然我收到了百度发来的邮件 image.png 给我发警告了,但是我这人就爱“无视风险安装”,我又多刷了几遍,得,客户反馈的问题现象以及相同的报错又出现了。
好吧,到现在为止我才终于找到了导致该问题出现的根源:使用的百度地图JS地点检索服务超限

3、探寻解决问题的办法

这问题应该怎么解决呢,使用的百度服务超限了。

(1)官方购买配额

官方那不给出了解决方法嘛 —— “及时访问购买,提升服务配额”。 image.png 这~,对于我这小公司来说有点儿压力山大啊!这我要是给领导按官方的费用进行反馈,搞不好得把我给优化喽!不行,这条路走不通,不能给领导这个机会!

(2)优化方法一:数据库存储百度地点数据,避开百度请求

我不想掏钱,我又想要服务!!!
那我就必须得牺牲点儿脑细胞了 —— 将百度地图返回的地区数据,首次存储到后台数据库,然后前端后续先从后台数据中请求不就好了吗?这样后续都不从百度获取数据,进而也就没有超限服务了,又能给公司省一笔钱,搞不好上头一激动,再给我发点儿(yy的毛病得改啊,动不动就爱做会儿美梦···)。
这部分的代码我就不上了,基础逻辑就是写了个循环,然后先访问后端接口判断有无该地点的数据,如果有正常绘制,如果没有的话,请求百度该地区的数据,然后绘制并且调用后端存储接口,存储到后端服务中。 当我确定方法后,先沟通客户确定该问题修复的具体时间,然后沟通后端工程师,并交流确定了解决方案。我们两个人开始咔咔写功能,最终写完后,满怀期待的进行测试!
基础逻辑没有问题(我的基本功还是可以的,嘻嘻~),但是发现一个严重的问题,请求真的好慢啊... 无法让人忍受。
并且其中部分 POST 请求出现了错误 413
原因: 部分省份的数据信息过大,造成存储失败 解决方法: 后端工程师,修改 nginx 配置文件,配置客户端请求大小和缓存大小(具体方法网上都有,在本文中不为重点就不细说了)
但是发出的网络请求真的太慢了,因为我们的后端数据库配置也不高(真是让人难过啊~),也不能为了这个小问题去升级数据库配置吧,那样不得把我打死...
循环发起多个请求,莫非是多个并发,导致的,要是按一个请求完毕,再开始下一个请求会不会好点儿呢?

(3)优化方法二:采用递归发送请求(效果有,但不大)

我将当前的逻辑进行调整,修改为:一次请求成功后进行地图绘制,然后再发起下一次的请求进行绘制,然后如此循环,直至各个地区的数据都获取并完成绘制。
经过调整后测试,整体请求所需的时间并没有多少变化,唯一的好处就是:大屏地图会逐步进行绘制,首次等待时间缩短,感觉上好了一些,但是有限。
这种方式肯定不是我们所需要的,离着理想差的太远了,必须再度进行调整。

(4)最终方案:本地数据库 —— indexedDB

目前最大的问题是由于后端请求响应过慢引起的,那么最直接的就是将数据本地进行缓存,在需要的时候直接从本地取;如果没有再通过后端接口获取,再没有的话再去百度请求。
采用本地数据缓存,但是当前的数据量有些大,localStorge 有些勉强,那么就只好采用 —— indexedDB。
indexedDB介绍: IndexedDB是一种在浏览器端存储数据的方式。既然称之为DB,是因为它丰富了客户端的查询方式,并且因为是本地存储,可以有效的减少网络对页面数据的影响。

/**
 * 本地 indexedDB 存储库,
 * 对当前本地的数据进行存储
 */
let myDB = {
  name: "boundaryDB",
  version: 1,
  db: null,
};
// 兼容不同的浏览器
let request = null;
let storeName = 'boundaryTable';

/**
 * 打开 indexDB 数据库
 * 没有则 进行创建
 */
function openIndexedDB() {
  if (!window.indexedDB) {
    console.log("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
  }
  return new Promise((resolve, reject) => {
  request = window.indexedDB.open(myDB.name, myDB.version);

  request.onerror = function (e) {
    // console.log("OPen Error!");
  };
  request.onsuccess = function (e) {
    // console.log("数据库db e", e);
    myDB.db = e.target.result;
    resolve({
      code: 200
    })
  };
  request.onupgradeneeded = function (e) {
    // console.log('onupgradeneeded');
    var db = e.target.result;
    if (!db.objectStoreNames.contains(storeName)) {
      var objectStore = db.createObjectStore(storeName, { keyPath: "name" });
      objectStore.createIndex("name", "name", { unique: false });
      objectStore.createIndex("boundaries", "boundaries", { unique: false });
    } else {
      console.log('数据库 存在');
    }
  };
})
}

/**
 * 关闭数据库
 */
function closeIndexedDB() {
  myDB.db.close();
}

/**
 * 查询当前的 indexedDB 中的数据
 */
function getDataFromIndexedDB(value) {
  return new Promise((resolve, reject) => {
    var transaction = myDB.db.transaction(storeName,'readwrite'); 
    var store = transaction.objectStore(storeName); 
    var request = store.get(value); 
    request.onsuccess = function(e){
      var boundaryDB = e.target.result;
      resolve(boundaryDB);
    };
    request.onerror = function(error){
      reject(error);
    };
  })
}

/**
 * 当前 indexDB 数据库中添加数据
 */
function addDataToIndexedDB(data) {
  if(!data.name) {
    return;
  }
  var transaction = myDB.db.transaction(storeName, 'readwrite'); 
  var store = transaction.objectStore(storeName);
  store.add(data);
}

/**
 * 清空当前的 本地数据
 */
function clearIndexedDBData(){
  var transaction = myDB.db.transaction(storeName,'readwrite'); 
  var store = transaction.objectStore(storeName); 
  store.clear();
}

export { 
  openIndexedDB, 
  closeIndexedDB, 
  addDataToIndexedDB,
  getDataFromIndexedDB, 
  clearIndexedDBData,
}

大屏页面

/**
   * 判断 当前数据库中大屏轮廓信息存储情况
   */
  checkBaiduBoundary = (actIndex = 0) => {
    let provinces = [
      "北京",
      "天津市",
      "上海市",
      "重庆市",
      "河北省",
      "山西省",
      "辽宁省",
      "吉林省",
      "黑龙江省",
      "江苏省",
      "浙江省",
      "安徽省",
      "福建省",
      "江西省",
      "山东省",
      "河南省",
      "湖北省",
      "湖南省",
      "广东省",
      "海南省",
      "四川省",
      "贵州省",
      "云南省",
      "陕西省",
      "甘肃省",
      "青海省",
      "台湾省",
      "内蒙古自治区",
      "广西壮族自治区",
      "西藏自治区",
      "宁夏回族自治区",
      "新疆维吾尔自治区",
      // "香港特别行政区",
      // "澳门特别行政区",
    ];

    getDataFromIndexedDB(provinces[actIndex]).then((value) => {
      if (!value) {
        // 当前 indexedDB 数据库中没有
        getDataBaseBaiduBoundary([provinces[actIndex]]).then((res) => {
          if (res.data.length !== 0) {
            // 数据库中存在该区域轮廓
            // 行政区域的点有多少个
            let count = res.data[0].boundaries.length;
            let pointArray = [];
            for (let i = 0; i < count; i++) {
              let ply = new BMap.Polygon(res.data[0].boundaries[i], {
                fillColor: "#4c4d86",
                strokeWeight: 1,
                strokeColor: "#9c9be1",
              }); //建立多边形覆盖物
              window.BaiduMap.addOverlay(ply); //添加覆盖物
              pointArray = pointArray.concat(ply.getPath());
            }

            addDataToIndexedDB({
              id: actIndex,
              name: provinces[actIndex],
              boundaries: res.data[0].boundaries
            })
            if (actIndex + 1 < provinces.length) {
              this.checkBaiduBoundary(actIndex + 1);
            }

          } else {
            // 云端数据库中也没有,请求百度
            this.getBoundary([provinces[actIndex]], actIndex);
          }
        });
      } else {
        // 当前indexedDB 数据库中存在数据
        // console.log('存在数据', value, actIndex);
        let count = value.boundaries.length;
        let pointArray = [];
        for (let i = 0; i < count; i++) {
          let ply = new BMap.Polygon(value.boundaries[i], {
            fillColor: "#4c4d86",
            strokeWeight: 1,
            strokeColor: "#9c9be1",
          }); //建立多边形覆盖物
          window.BaiduMap.addOverlay(ply); //添加覆盖物
          pointArray = pointArray.concat(ply.getPath());
        }
        if (actIndex + 1 < provinces.length) {
          this.checkBaiduBoundary(actIndex + 1);
        } else {
          closeIndexedDB();
        }
      }
    }).catch(err => {
      console.log('读取 indexedDB 失败', err);
    });
  };

  /**
   * 进行大屏轮廓的绘制
   */
  getBoundary = (provinces, id) => {
    let bdary = new BMap.Boundary();
    for (let index = 0; index < provinces.length; index++) {
      // 请求百度
      bdary.get(provinces[index], function (rs) {
        // 获取行政区域
        if (rs) {
          saveBaiduBoundaryToDataBase([
            {
              name: provinces[index],
              boundaries: rs.boundaries,
            },
          ]);
          addDataToIndexedDB({
            id,
            name: provinces[index],
            boundaries: rs.boundaries,
          })
          let count = rs.boundaries.length; // 行政区域的点有多少个
          let pointArray = [];
          for (let i = 0; i < count; i++) {
            let ply = new BMap.Polygon(rs.boundaries[i], {
              fillColor: "#4c4d86",
              strokeWeight: 1,
              strokeColor: "#9c9be1",
            }); //建立多边形覆盖物
            window.BaiduMap.addOverlay(ply); //添加覆盖物
            pointArray = pointArray.concat(ply.getPath());
          }
        }
      });
    }
  };

采用这种方式,当本地indexedDB数据库中存在数据的时候,地图绘制效果就跟直接请求百度地图的接口渲染速度几乎一致了!

总结

最终我们确定了采用后端数据存储 + indexedDB 的方式,解决当前由于百度限额引出的问题;
最终成果收获:

  1. 减少公司支出,避免在当前项目体量不足够大的时候增添成本投入
  2. 通过客户反馈的问题,采用目前已知的方式合理的解决;以问题为出发点,运用自己的技能去解决,使自己得到进一步的提升
  3. 在周会上又有了一笔谈资~~

那么本次问题以及相关解决方式的记录就到此结束了,在前端的技术路上,希望我们能越走越远,在此,与君共勉!!!