黑马程序员苍穹外卖项目【地图收货范围(使用sn校验方式)】

 此文章适用于创建应用时选择sn校验方式的小伙伴,视频项目里老师给的代码用的应该是ip白名单的方式,所以向百度地图接口发请求的代码得自己写了。下面有我的代码:

1. 环境准备

注册账号:注册百度账号

登录百度地图开放平台:百度地图-百万开发者首选的地图服务商,提供专属的行业解决方案

进入控制台,创建应用,获取AK:

相关接口:

地理编码 | 百度地图API SDK

轻量级路线规划 | 百度地图API SDK

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


                
  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值