此文章适用于创建应用时选择sn校验方式的小伙伴,视频项目里老师给的代码用的应该是ip白名单的方式,所以向百度地图接口发请求的代码得自己写了。下面有我的代码:
1. 环境准备
注册账号:注册百度账号
登录百度地图开放平台:百度地图-百万开发者首选的地图服务商,提供专属的行业解决方案
进入控制台,创建应用,获取AK:
相关接口:
2. 代码开发
2.1 application.yml
配置外卖商家店铺地址和百度地图的AK以及SK:
2.2 OrderServiceImpl
改造OrderServiceImpl,注入上面的配置项:
@Value("${sky.shop.address}")
private String shopAddress;
在OrderServiceImpl中提供校验方法:因为我们使用的是sn校验方式,所以每次发起请求之前,都需要先进行sn校验,再发起请求。这种请求传回来的json字符串用老师代码中处理json字符串的方式不行,下面是我的解决方法。店铺和用户收货地址两次获取坐标的代码都是一样的,但只用了两次,我也就懒得专门提取方法了。
/**
* 检查客户的收货地址是否超出配送范围
* @param address
*/
private void checkOutOfRange(String address) throws Exception {
String status = "";
String lng = "";
String lat = "";
//获取店铺的经纬度坐标
String shopCoordinate = SearchHttpSN.getCoordinate(shopAddress);
// 提取有效JSON字符串部分
int startIndex = shopCoordinate.indexOf('{');
int endIndex = shopCoordinate.lastIndexOf('}');
//下面这一段是用来整理shopCoordinate 字符串,将我们所需要的属性值提取出来
if (startIndex != -1 && endIndex != -1) {
String validJsonString = shopCoordinate.substring(startIndex, endIndex + 1);
// 使用Jackson解析有效JSON
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode jsonNode = mapper.readTree(validJsonString);
// 示例:获取并打印部分属性值
status = jsonNode.get("status") + "";
JsonNode locationNode = jsonNode.get("result").get("location");
lng = locationNode.get("lng")+ "";
lat = locationNode.get("lat")+ "";
} catch (JsonProcessingException e) {
e.printStackTrace();
}
} else {
System.err.println("Invalid JSON format.");
}
if(!status.equals("0")){
log.info("服务状态码:{}",status);
throw new OrderBusinessException("店铺地址解析失败");
}
//店铺经纬度坐标
String shopLngLat = lat + "," + lng;
log.info("店铺经纬度坐标",shopLngLat);
//获取用户收货地址的经纬度坐标
String userCoordinate = SearchHttpSN.getCoordinate(address);
startIndex = userCoordinate.indexOf('{');
endIndex = userCoordinate.lastIndexOf('}');
//下面这一段是用来整理shopCoordinate 字符串,将我们所需要的属性值提取出来
if (startIndex != -1 && endIndex != -1) {
String validJsonString = userCoordinate.substring(startIndex, endIndex + 1);
// 使用Jackson解析有效JSON
ObjectMapper mapper = new ObjectMapper();
try {
JsonNode jsonNode = mapper.readTree(validJsonString);
// 示例:获取并打印部分属性值
status = jsonNode.get("status") + "";
JsonNode locationNode = jsonNode.get("result").get("location");
lng = locationNode.get("lng")+ "";
lat = locationNode.get("lat")+ "";
} catch (JsonProcessingException e) {
e.printStackTrace();
}
} else {
System.err.println("Invalid JSON format.");
}
if(!status.equals("0")){
log.info("服务状态码:{}",status);
throw new OrderBusinessException("收货地址解析失败");
}
//用户收货地址经纬度坐标
String userLngLat = lat + "," + lng;
log.info("收货地址经纬度坐标",userLngLat);
String json = SearchHttpSnDistance.getDistance(shopLngLat, userLngLat);
JSONObject jsonObject = JSON.parseObject(json);
if(!jsonObject.getString("status").equals("0")){
throw new OrderBusinessException("配送路线规划失败");
}
//数据解析
JSONObject result = jsonObject.getJSONObject("result");
JSONArray jsonArray = (JSONArray) result.get("routes");
Integer distance = (Integer) ((JSONObject) jsonArray.get(0)).get("distance");
if(distance > 10000){
//配送距离超过10000米
throw new OrderBusinessException("超出配送范围");
}
}
下面两段代码是通过百度地图接口在线生成实例代码所改的:
第一个类是获取店铺和用户收货地址坐标的:
//地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能。
public class SearchHttpSN {
public static String AK = "填写你自己的ak";
public static String SK = "填写你自己的ak";
public static String URL = "https://api.map.baidu.com/geocoding/v3?";
public static String getCoordinate(String address) throws Exception {
SearchHttpSN snCal = new SearchHttpSN();
Map params = new LinkedHashMap<String, String>();
params.put("address", address);
params.put("output", "json");
params.put("ak", AK);
params.put("callback", "showLocation");
params.put("sn", snCal.caculateSn(address));
return snCal.requestGetSN(URL, params);
}
/**
* 选择了ak,使用SN校验:
* 根据您选择的AK已为您生成调用代码
* 检测您当前的AK设置了sn检验,本示例中已为您生成sn计算代码
* @param strUrl
* @param param
* @throws Exception
*/
public String requestGetSN(String strUrl, Map<String, String> param) throws Exception {
if (strUrl == null || strUrl.length() <= 0 || param == null || param.size() <= 0) {
return null;
}
StringBuffer queryString = new StringBuffer();
queryString.append(strUrl);
for (Map.Entry<?, ?> pair : param.entrySet()) {
queryString.append(pair.getKey() + "=");
// 第一种方式使用的 jdk 自带的转码方式 第二种方式使用的 spring 的转码方法 两种均可
// queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8") + "&");
}
if (queryString.length() > 0) {
queryString.deleteCharAt(queryString.length() - 1);
}
java.net.URL url = new URL(queryString.toString());
URLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.connect();
InputStreamReader isr = new InputStreamReader(httpConnection.getInputStream());
BufferedReader reader = new BufferedReader(isr);
StringBuffer buffer = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
reader.close();
isr.close();
return buffer.toString();
}
public String caculateSn(String address) throws UnsupportedEncodingException,
NoSuchAlgorithmException {
SearchHttpSN snCal = new SearchHttpSN();
// 计算sn跟参数对出现顺序有关,get请求请使用LinkedHashMap保存<key,value>,该方法根据key的插入顺序排序;post请使用TreeMap保存<key,value>,该方法会自动将key按照字母a-z顺序排序。
// 所以get请求可自定义参数顺序(sn参数必须在最后)发送请求,但是post请求必须按照字母a-z顺序填充body(sn参数必须在最后)。
// 以get请求为例:http://api.map.baidu.com/geocoder/v2/?address=百度大厦&output=json&ak=yourak,paramsMap中先放入address,再放output,然后放ak,放入顺序必须跟get请求中对应参数的出现顺序保持一致。
Map paramsMap = new LinkedHashMap<String, String>();
paramsMap.put("address", address);
paramsMap.put("output", "json");
paramsMap.put("ak", AK);
paramsMap.put("callback", "showLocation");
// 调用下面的toQueryString方法,对LinkedHashMap内所有value作utf8编码,拼接返回结果address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourak
String paramsStr = snCal.toQueryString(paramsMap);
// 对paramsStr前面拼接上/geocoder/v2/?,后面直接拼接yoursk得到/geocoder/v2/?address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourakyoursk
String wholeStr = new String("/geocoding/v3?" + paramsStr + SK);
// 对上面wholeStr再作utf8编码
String tempStr = URLEncoder.encode(wholeStr, "UTF-8");
// 调用下面的MD5方法得到最后的sn签名
String sn = snCal.MD5(tempStr);
return sn;
}
// 对Map内所有value作utf8编码,拼接返回结果
public String toQueryString(Map<?, ?> data)
throws UnsupportedEncodingException {
StringBuffer queryString = new StringBuffer();
for (Map.Entry<?, ?> pair : data.entrySet()) {
queryString.append(pair.getKey() + "=");
// 第一种方式使用的 jdk 自带的转码方式 第二种方式使用的 spring 的转码方法 两种均可
// queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8") + "&");
}
if (queryString.length() > 0) {
queryString.deleteCharAt(queryString.length() - 1);
}
return queryString.toString();
}
// 来自stackoverflow的MD5计算方法,调用了MessageDigest库函数,并把byte数组结果转换成16进制
public String MD5(String md5) {
try {
java.security.MessageDigest md = java.security.MessageDigest
.getInstance("MD5");
byte[] array = md.digest(md5.getBytes());
StringBuffer sb = new StringBuffer();
for (int i = 0; i < array.length; ++i) {
sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100)
.substring(1, 3));
}
return sb.toString();
} catch (java.security.NoSuchAlgorithmException e) {
}
return null;
}
}
第二个类是对比两个坐标距离的(其实也就是轻量级路线规划这个接口),代码如下:
//轻量级路线规划服务 V1.0
public class SearchHttpSnDistance {
public static String AK = "填写你自己的ak";
public static String SK = "填写你自己的sk";
public static String URL = "https://api.map.baidu.com/directionlite/v1/driving?";
public static String getDistance(String origin,String destination) throws Exception {
SearchHttpSnDistance snCal = new SearchHttpSnDistance();
Map params = new LinkedHashMap<String, String>();
params.put("origin", origin);
params.put("destination", destination);
params.put("ak", AK);
String currentTimestamp = String.valueOf(System.currentTimeMillis());
params.put("timestamp", currentTimestamp);
params.put("sn", snCal.caculateSn(origin,destination));
return snCal.requestGetSN(URL, params);
}
/**
* 选择了ak,使用SN校验:
* 根据您选择的AK已为您生成调用代码
* 检测您当前的AK设置了sn检验,本示例中已为您生成sn计算代码
* @param strUrl
* @param param
* @throws Exception
*/
public String requestGetSN(String strUrl, Map<String, String> param) throws Exception {
if (strUrl == null || strUrl.length() <= 0 || param == null || param.size() <= 0) {
return null;
}
StringBuffer queryString = new StringBuffer();
queryString.append(strUrl);
for (Map.Entry<?, ?> pair : param.entrySet()) {
queryString.append(pair.getKey() + "=");
// 第一种方式使用的 jdk 自带的转码方式 第二种方式使用的 spring 的转码方法 两种均可
// queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8") + "&");
}
if (queryString.length() > 0) {
queryString.deleteCharAt(queryString.length() - 1);
}
java.net.URL url = new URL(queryString.toString());
URLConnection httpConnection = (HttpURLConnection) url.openConnection();
httpConnection.connect();
InputStreamReader isr = new InputStreamReader(httpConnection.getInputStream());
BufferedReader reader = new BufferedReader(isr);
StringBuffer buffer = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
reader.close();
isr.close();
return buffer.toString();
}
public String caculateSn(String origin,String destination) throws UnsupportedEncodingException,
NoSuchAlgorithmException {
SearchHttpSnDistance snCal = new SearchHttpSnDistance();
// 计算sn跟参数对出现顺序有关,get请求请使用LinkedHashMap保存<key,value>,该方法根据key的插入顺序排序;post请使用TreeMap保存<key,value>,该方法会自动将key按照字母a-z顺序排序。
// 所以get请求可自定义参数顺序(sn参数必须在最后)发送请求,但是post请求必须按照字母a-z顺序填充body(sn参数必须在最后)。
// 以get请求为例:http://api.map.baidu.com/geocoder/v2/?address=百度大厦&output=json&ak=yourak,paramsMap中先放入address,再放output,然后放ak,放入顺序必须跟get请求中对应参数的出现顺序保持一致。
Map paramsMap = new LinkedHashMap<String, String>();
paramsMap.put("origin", origin);
paramsMap.put("destination", destination);
paramsMap.put("ak", AK);
String currentTimestamp = String.valueOf(System.currentTimeMillis());
paramsMap.put("timestamp", currentTimestamp);
// 调用下面的toQueryString方法,对LinkedHashMap内所有value作utf8编码,拼接返回结果address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourak
String paramsStr = snCal.toQueryString(paramsMap);
// 对paramsStr前面拼接上/geocoder/v2/?,后面直接拼接yoursk得到/geocoder/v2/?address=%E7%99%BE%E5%BA%A6%E5%A4%A7%E5%8E%A6&output=json&ak=yourakyoursk
String wholeStr = new String("/directionlite/v1/driving?" + paramsStr + SK);
// 对上面wholeStr再作utf8编码
String tempStr = URLEncoder.encode(wholeStr, "UTF-8");
// 调用下面的MD5方法得到最后的sn签名
String sn = snCal.MD5(tempStr);
return sn;
}
// 对Map内所有value作utf8编码,拼接返回结果
public String toQueryString(Map<?, ?> data)
throws UnsupportedEncodingException {
StringBuffer queryString = new StringBuffer();
for (Map.Entry<?, ?> pair : data.entrySet()) {
queryString.append(pair.getKey() + "=");
// 第一种方式使用的 jdk 自带的转码方式 第二种方式使用的 spring 的转码方法 两种均可
// queryString.append(URLEncoder.encode((String) pair.getValue(), "UTF-8").replace("+", "%20") + "&");
queryString.append(UriUtils.encode((String) pair.getValue(), "UTF-8") + "&");
}
if (queryString.length() > 0) {
queryString.deleteCharAt(queryString.length() - 1);
}
return queryString.toString();
}
// 来自stackoverflow的MD5计算方法,调用了MessageDigest库函数,并把byte数组结果转换成16进制
public String MD5(String md5) {
try {
java.security.MessageDigest md = java.security.MessageDigest
.getInstance("MD5");
byte[] array = md.digest(md5.getBytes());
StringBuffer sb = new StringBuffer();
for (int i = 0; i < array.length; ++i) {
sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100)
.substring(1, 3));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
}
return null;
}
}
有没有发现这两个类大部分内容是一样的?没错,因为使用sn校验方式,这两个类里面,都包含了sn校验,最下面两个方法其实是一样的,读者有时间可以自己整合到一个类里面。
在OrderServiceImpl的submitOrder方法中调用上面的校验方法: 老师给的代码里少了省份名字,如果填的地址是省份还好,如果测试地址是直辖市,那调用百度地图的接口,应该会报错,例如天津市市辖区和平区,如果没有将省份加上,那发送过去的地址就变成了市辖区和平区
其实到最后,配置在yml文件里的ak和sk我并没有用到,上面两个接口类里面的ak和sk我是直接填进去的,orderServiceImpl里面注入了address,有时间的读者也可以改一下
最后我们来看一下运行结果吧!!
轻量级路线规划接口请求成功,最后获取出 distance