本篇文章主要介绍在最近维护的项目中,由于百度地图JS地点检索服务,配额超限引出的问题;以及最后采用的优化方法。
1、问题表现
上周的任务比较多,当我正常按工作计划开展的时候,突然群里用户反馈大屏地图的轮廓无法正常显示了?当时我还蒙了一下,啥大屏地图? 当用户截图过来的时候,我才明白他要表达的意思,大屏地图部分地区的轮廓无法正常显示出来了?这到底是咋回事儿呢?不应该啊?
2、思索、排查
我立马本地启动项目开展了排查,该项目我是第二个接手的,因此一时无法确定该问题是由何引起的。
当项目启动后,进入该页面发现确实出现了报错
咦~,好像是百度地图的ak出问题了,要不换一下我个人的ak测一测?说做就做,我将项目中的ak换成自己的,然后刷新一看;
嘿!好了,这个问题如此轻易的就被我解决了,看来老板又要给我升职加薪了(陷入了美好幻想中~)。
当我在激动中,多刷了几次页面,突然我收到了百度发来的邮件
给我发警告了,但是我这人就爱“
无视风险安装”,我又多刷了几遍,得,客户反馈的问题现象以及相同的报错又出现了。
好吧,到现在为止我才终于找到了导致该问题出现的根源:使用的百度地图JS地点检索服务超限
3、探寻解决问题的办法
这问题应该怎么解决呢,使用的百度服务超限了。
(1)官方购买配额
官方那不给出了解决方法嘛 —— “及时访问购买,提升服务配额”。
这~,对于我这小公司来说有点儿压力山大啊!这我要是给领导按官方的费用进行反馈,搞不好得把我给优化喽!不行,这条路走不通,不能给领导这个机会!
(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 的方式,解决当前由于百度限额引出的问题;
最终成果收获:
- 减少公司支出,避免在当前项目体量不足够大的时候增添成本投入
- 通过客户反馈的问题,采用目前已知的方式合理的解决;以问题为出发点,运用自己的技能去解决,使自己得到进一步的提升
- 在周会上又有了一笔谈资~~
那么本次问题以及相关解决方式的记录就到此结束了,在前端的技术路上,希望我们能越走越远,在此,与君共勉!!!