前言
现在很行业使用到了地图相关的服务,比如外卖行业、某滴、物流行业、还有一些导航软件。国内有很多做地图的厂商,地图厂商能从国家权威部门拿到或者买到的地图数据。在做路线规划、电子围栏、地址逆解析等等提供了很好地服务。例如:腾讯位置服务,高德、百度等等,这些厂商都有提供开发者服务,可以免费注册秘钥,当然我们也可以通过某些设计进行薅羊毛。
使用
使用腾讯位置服务,我们可以创建相关的秘钥,每个秘钥都有免费的额度。当然根据自己的业务需求量,可以针对的(免费)”白嫖“使用服务。
创建秘钥地址
最近看了几篇DDD敏捷开发的文章,正好想设计一下如何利用模型来驱动开发(本文只是对模型的一种抽象开发),所需的位置信息是我们的模型。 位置服务对于我们属于第三方服务,为了服务解耦我们需要抽象我们的第三方服务,当然我们今天说的是腾讯的位置服务,利用免费的来进行我们的业务服务,当然服务方为我们提供了很好地测试免费额度。为了更好地体验服务,我们可以免费申请相关的秘钥,每个秘钥key有相关的限制额度,比如我们的并发大于限额或者说免费的额度不能满足我们的业务需求,我们可以创建多个key,利用某种策略来进行“友好的体验”。当然有条件也可以申请相关的付费秘钥。从我们业务来考虑,利用lb负载相关服务的经验,我们可以把key的使用策略来进行控制,比如我们可以定义一个Strategy类
public interface Strategy {
/**
* 获取秘钥key
* @return
*/
SecretKey SecretKey();
}
我们可以根据需求实现不同的策略,比如轮询、权重等等
轮询策略
/**
* 轮询策略
*/
public class PollingStrategy implements Strategy {
/**
* 秘钥集合
*/
private List<SecretKey> secretKeys = new ArrayList<>();
/**
* 位置
*/
private int position = 0;
public PollingStrategy(List<SecretKey> secretKeys) {
if (null == secretKeys || secretKeys.size() < 1)
throw new RuntimeException("secret key is not empty!");
this.secretKeys = secretKeys;
}
/**
* 如果当前位置小于初始化key的长度-1 下次获取直接从position+1位置开始
* 反之从其实位置开始
*
* @return str
*/
@Override
public SecretKey SecretKey() {
SecretKey secretKey = secretKeys.get(position);
if (secretKeys.size() == 1) {
return secretKey;
}
if (position < (secretKeys.size() - 1)) {
position++;
} else {
position = 0;
}
return secretKey;
}
}
权重
/**
* 权重
*/
public class WeightStrategy implements Strategy {
private Map<SecretKey, Integer> secretKeyIntegerMap = new LinkedHashMap<>();
private int total;
private Random random = new Random();
public WeightStrategy(Map<SecretKey, Integer> secretKeyIntegerMap) {
secretKeyIntegerMap.forEach((k, v) -> {
if (Objects.nonNull(k)) {
if (Objects.nonNull(v) && v.intValue() < 0) {
v = 1;
secretKeyIntegerMap.put(k, v);
total += v;
}
}
});
}
@Override
public SecretKey SecretKey() {
int w = random.nextInt(total) + 1;
Iterator<Map.Entry<SecretKey, Integer>> entryIterator = secretKeyIntegerMap.entrySet().iterator();
while (entryIterator.hasNext()) {
Map.Entry<SecretKey, Integer> entry = entryIterator.next();
Integer v = entry.getValue();
if (w <= v) {
return entry.getKey();
} else {
w -= v;
}
}
return null;
}
}
写好策略我们可以搭建我们预想好的第三方接口,我们拿逆地理解析来做例子参考官方文档:
public interface SdkLbsService {
/**
* 逆地理解析
*
* @param params 参数
* @return t entity
*/
<T> T geoCoder(GeoCoderBaseParams params);
}
我们可能面向多个抽象策略类,将策略抽象一层,与具体的接口类分开:
public abstract class AbstractStrategy {
/**
* 策略
*
* @return str
*/
public abstract Strategy strategy();
/**
* 获取秘钥
*
* @return secretKey
*/
public SecretKey getSecretKey() {
return strategy().SecretKey();
}
}
我们将接口实现类与策略类进行组合一个新的抽象类:
public abstract class AbstractSdkLbsService extends AbstractStrategy implements SdkService{
}
当然这个实现还可以扩展其他的接口,我们暂且保留。
我们可以安心的实现腾讯相关的位置解析服务了:
public class TencentLbsServiceImpl extends AbstractSdkLbsService {
private Strategy strategy;
public TencentLbsServiceImpl(TencentLbsProperties tencentLbsProperties) {
TencentLbsProperties.SecretKey secretKey;
List<TencentLbsProperties.SecretKey> secretKeys = tencentLbsProperties.getPool();
if (Objects.isNull(secretKey = tencentLbsProperties.getSecretKey()) && (Objects.isNull(secretKeys) || secretKeys.size() < 1)) {
throw new RuntimeException("tencent lbs config is null");
}
if (Objects.nonNull(secretKeys) && secretKeys.size() > 0) {
List<SecretKey> skeys = new ArrayList<>();
for (TencentLbsProperties.SecretKey skey : secretKeys) {
skeys.add(new SecretKey(skey.getKey()));
}
this.strategy = new PollingStrategy(skeys);
} else {
strategy = new SingleStrategy(new SecretKey(secretKey.getKey()));
}
}
public TencentLbsServiceImpl(TencentLbsProperties tencentLbsProperties, @NotNull StrategyType strategyType) {
List<TencentLbsProperties.SecretKey> secretKeys;
switch (strategyType) {
case SINGLE:
TencentLbsProperties.SecretKey secretKey;
if (Objects.isNull(secretKey = tencentLbsProperties.getSecretKey())) {
throw new RuntimeException("tencent lbs config is null");
}
this.strategy = new SingleStrategy(new SecretKey(secretKey.getKey()));
break;
case POLLING:
secretKeys = tencentLbsProperties.getPool();
if (Objects.isNull(secretKeys) || secretKeys.size() < 1) {
throw new RuntimeException("tencent lbs poll config is null");
}
List<SecretKey> skeys = new ArrayList<>();
for (TencentLbsProperties.SecretKey skey : secretKeys) {
skeys.add(new SecretKey(skey.getKey()));
}
this.strategy = new PollingStrategy(skeys);
break;
case WEIGHT:
secretKeys = tencentLbsProperties.getPool();
if (Objects.isNull(secretKeys) || secretKeys.size() < 1) {
throw new RuntimeException("tencent lbs poll config is null");
}
Map<SecretKey, Integer> secretKeyIntegerMap = new HashMap<>();
for (TencentLbsProperties.SecretKey skey : secretKeys) {
secretKeyIntegerMap.put(new SecretKey(skey.getKey()), skey.getWeight());
}
this.strategy = new WeightStrategy(secretKeyIntegerMap);
break;
default:
throw new RuntimeException("strategy type is no realization!");
}
}
@Override
public Strategy strategy() {
return strategy;
}
@Override
public BaseResult geoCoder(GeoCoderBaseParams params) {
GeoCoderParams geoCoderParams = (GeoCoderParams) params;
StringBuilder urlSb =
new StringBuilder("https://apis.map.qq.com/ws/geocoder/v1/?location=").append(geoCoderParams.getLocation().toString());
urlSb.append("&get_poi=").append(geoCoderParams.getPoi());
if (Objects.nonNull(geoCoderParams.getOutput())) {
urlSb.append("&output=").append(geoCoderParams.getOutput());
}
urlSb.append("&key=").append(getSecretKey().getKey());
String response = HttpClientTool.doGet(urlSb.toString(), null);
if (null != response) {
return JSON.parseObject(response, new TypeReference<BaseResult<GeoCoderDto>>() {
});
}
return null;
}
}
具体实现仅供参考,可以根据主流的框架随意扩展,比如现有的腾讯服务不够用,还可以再抽象,比如我们实现高德的、百度的,可以继承相关的策略,再度将应用层抽象。当然本文只是针对腾讯的位置服务。相关代码已上传到GitHub
专属邀请链接: https://lbs.qq.com?lbs_invite=5JPRFL5