springboot 腾讯地图接口验签 java

1. 原因

  • 需求需要通过小程序定位拿到用户所在行政区信息,但是小程序定位只能拿到经纬度信息,所以需要调用腾讯地图的逆地址解析(我认为:微信是腾讯的,那么使用腾讯地图的逆地址解析经度应该不会损失太多)
  • 如果WebServiceAPI Key配置中签名校验,那么调用接口就需要进行验签

image.png

2. WebServiceAPI(GET方法)签名计算

官方文档地址:以下内容是从官方文档摘写下来的验签规则
(1)通用概念:
a. 请求路径:调用接口时的路径,如:/ws/geocoder/v1,末尾是否带 / 均可,不做要求,但需要保持一致,比如调用路径用了/ws/geocoder/v1,签名计算的时候也要用/ws/geocoder/v1
b. SecretKey (SK):在腾讯位置服务控制台 > Key配置中,勾选WebServiceAPI的 SN校验时自动生成的随机字串,用于计算签名(sig)
c. sig:签名计算结果
通过以下示例讲解(本例为调用逆地址解析请求的url):
https://apis.map.qq.com/ws/geocoder/v1?location=28.7033487,115.8660847&key=5Q5BZ-5EVWJ-SN5F3-K6QBZ-B3FAO-*****

(2)GET请求分为:域名,请求路径和参数三个部分,用于签名计算的有:
请求路径:/ws/geocoder/v1
请求参数:location=28.7033487,115.8660847&key=5Q5BZ-5EVWJ-SN5F3-K6QBZ-B3FAO-*****
a. 首先对参数进行排序:按参数名升序(本例结果为key在前,location在后):

key=5Q5BZ-5EVWJ-SN5F3-K6QBZ-B3FAO-*****&location=28.7033487,115.8660847

b. 签名计算(sig):
请求路径+”?”+请求参数+SK进行拼接,并计算拼接后字符串md5值(字符必须为小写),即为签名(sig):
要求:请求参数必须是未进行任何编码(如urlencode)的原始数据

md5("/ws/geocoder/v1?key=5Q5BZ-5EVWJ-SN5F3-*****&location=28.7033487,115.8660847SWvT26ypwq5Nwb5RvS8cLi6NSoH8HlJX")

本例计算得到结果为:90da272bfa19122547298e2b0bcc0e50
c. 生成最终请求:将计算得到的签名sig,放到请求中(参数名即为:sig):

https://apis.map.qq.com/ws/geocoder/v1?key=5Q5BZ-5EVWJ-SN5F3-K6QBZ-B3FAO-*****&location=28.7033487,115.8660847&sig=90da272bfa19122547298e2b0bcc0e50

注意:计算 sig 要使用原始参数值,不要进行任何编码,但最终发送时的参数,是需要对接口传入的参数值做url编码的

url编码方式:

以地点检索接口位例:
 
请求:...域名省略.../place/v1/search?boundary=region(北京)&keyword=美食

错误方式:"...域名省略.../place/v1/search?"+urlencode("boundary=region(北京)&keyword=美食")
 
正确方式:"...域名省略.../place/v1/search?boundary="+urlencode("region(北京)")+"&keyword="+urlencode("美食")

注:示例中的urlencode()代表url编码函数,不同开发语言的存在不同,以您实际为准

3. 腾讯地图java验签工具类

常量信息

  • KEY和SK自己去后台查看(我没有封装为配置,有需要的可以自行封装)
  • 需要新增接口就在这个常量中新增,然后在工具类中引用就行
package com.applets.manager.core.constant;

/**
 * @author zr 2024/4/26
 */
public class TencentMapConstant {
    /** KEY */
    public static final String KEY = "xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-QGBVW";
    /** SecretKey (SK) */
    public static final String SK = "xxxxxxxxxxxxxxxxxxxxxxxx";
    /** 域名 */
    public static final String HOST = "https://apis.map.qq.com";
    /** 逆地址解析api */
    public static final String GEOCODER_API = "/ws/geocoder/v1";
//    /** 距离矩阵 */
//    public static final String DISTANCE_API = "/ws/distance/v1/matrix";
//    /** IP定位 */
//    public static final String IP_LOCATION_API = "/ws/location/v1/ip";
}

工具类

  • 附带get接口验签和post接口验签
package com.applets.manager.core.util;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSONObject;
import com.applets.manager.core.constant.TencentMapConstant;
import lombok.extern.slf4j.Slf4j;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

/**
 * @author zr 2024/4/26
 */
@Slf4j
public class TencentMapUtil {
    /**
     * 签名计算(sig)_GET:
     * @param path  请求路径
     * @param params    请求参数
     * @return
     */
    public static String generateSignatureGet(String path, Map<String, String> params) {
        try {
            //将参数按照 key 进行字典序排序
            TreeMap<String, String> sortedParams = new TreeMap<>(params);
            //构建原始签名字符串
            StringBuilder rawSignatureBuilder = new StringBuilder();
            rawSignatureBuilder.append(path).append("?");
            int entryIndex = 0;
            for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
                rawSignatureBuilder.append(entry.getKey()).append("=").append(entry.getValue());
                if (entryIndex < sortedParams.size() - 1) {
                    rawSignatureBuilder.append("&");
                }
                entryIndex++;
            }
            rawSignatureBuilder.append(TencentMapConstant.SK);
            String rawSignature = rawSignatureBuilder.toString();

            // 计算 MD5 签名
            byte[] signatureBytes = MessageDigest.getInstance("MD5").digest(rawSignature.getBytes("UTF-8"));
            String signature = byteArrayToHexString(signatureBytes);

            return URLEncoder.encode(signature, "UTF-8"); // 进行 URL 编码
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            log.error("签名计算失败: {}", e.getMessage(), e);
            return null;
        }
    }
    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (byte b : bytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }

    /**
     * 对参数进行编码
     * @param params
     * @return
     */
    public static String encodeParams(Map<String, String> params) {
        TreeMap<String, String> sortedParams = new TreeMap<>(params);
        StringBuilder rawSignatureBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            rawSignatureBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        return rawSignatureBuilder.toString();
    }

    /**
     * 签名计算(sig)_POST:
     * @param jsonObject
     */
    public static String generateSignaturePost(String path, JSONObject jsonObject) {
        //一级属性名排序字符升序排序
        Set<String> propertyNames = jsonObject.keySet();
        List<String> sortedPropertyNames = new ArrayList<>(propertyNames);
        Collections.sort(sortedPropertyNames);
        JSONObject sortedJsonObject = new JSONObject();
        for (String propertyName : sortedPropertyNames) {
            //  Value转成JSON string
            sortedJsonObject.put(propertyName, JSONUtil.toJsonStr(jsonObject.get(propertyName)));
        }
        //替换原始的 JSONObject
        jsonObject.clear();
        jsonObject.putAll(sortedJsonObject);

        //拼接成rawSignatureBuilder型的字符串
        StringBuilder rawSignatureBuilder = new StringBuilder();
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            String propertyName = entry.getKey();
            String propertyValue = (String) entry.getValue();
            // 拼接成 rawSignatureBuilder 型的字符串
            rawSignatureBuilder.append(propertyName)
                    .append("=")
                    .append(propertyValue)
                    .append("&");
        }
        //去除末尾的"&"
        if (rawSignatureBuilder.length() > 0) {
            rawSignatureBuilder.deleteCharAt(rawSignatureBuilder.length() - 1);
        }

        //签名计算(sig) 请求路径+”?”+请求参数+SK
        rawSignatureBuilder.insert(0, path + "?").append(TencentMapConstant.SK);
        String rawSignature = rawSignatureBuilder.toString();

        // 计算 MD5 签名
        try {
            byte[] signatureBytes = MessageDigest.getInstance("MD5").digest(rawSignature.getBytes("UTF-8"));
            String signature = byteArrayToHexString(signatureBytes);

            return URLEncoder.encode(signature, "UTF-8"); // 进行 URL 编码
        } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
            log.error("签名计算失败: {}", e.getMessage(), e);
        }
        return null;
    }
}

4. 测试

package com.applets.manager.core;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.applets.manager.core.constant.TencentMapConstant;
import com.applets.manager.core.util.TencentMapUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
import java.util.*;

/**
 * @author zr 2024/4/26
 */
@RunWith(SpringRunner.class)
//@SpringBootTest(classes = StartApplication.class)
@Slf4j
public class TencentTest {

    @Test
    public void getTest() {
        /** get请求示例 */
        Map<String, String> params = MapUtil.builder(new HashMap<String, String>())
                .put("location", "28.7033487,115.8660847")
                .put("key", TencentMapConstant.KEY)
                .build();
        // 生成最终请求
        String signature = TencentMapUtil.generateSignatureGet(TencentMapConstant.GEOCODER_API, params);
        String finalRequest = TencentMapConstant.HOST + TencentMapConstant.GEOCODER_API + "?" + TencentMapUtil.encodeParams(params) + "sig=" + signature;
        System.out.println(finalRequest);
        RestTemplate restTemplate = new RestTemplate();
        TencentMapResult forObject = restTemplate.getForObject(finalRequest, TencentMapResult.class);
        log.info("地址:{}", JSON.toJSONString(forObject));

    }

    @Test
    public void postTest() {
//        /** post请求示例 */
//        JSONObject requestData = new JSONObject();
//        requestData.set("mode", "driving");
//        requestData.set("key", TencentMapConstant.KEY);
//        requestData.set("from", "32.139063,118.724270");
//        requestData.set("to", "32.150763,118.734398;32.157158,118.696632");
//
//        String signature = TencentMapUtil.generateSignaturePost(TencentMapConstant.DISTANCE_API, requestData);
//        String finalRequest = TencentMapConstant.HOST + TencentMapConstant.DISTANCE_API + "?mode=driving" + "&sig=" + signature;
//        System.out.println(finalRequest);
//        RestTemplate restTemplate = new RestTemplate();
//        System.out.println(restTemplate.postForObject(finalRequest, requestData, TencentMapResult.class));
    }

    @Data
    public static class TencentMapResult<T> {
        /**
         * 状态码,0为正常,其它为异常
         */
        private Integer status;

        /**
         * 状态说明
         */
        private String message;

        /**
         * 本次请求的唯一标识
         */
        private String request_id;

        /**
         * 查询结果总数量
         */
        private Integer count;

        /**
         * 返回数据 > 数组
         */
        private T data;

        /**
         * 返回数据 > 对象
         */
        private T result;

    }
}

测试结果
image.png

因为我这里只开通了一个get接口的使用权限,所以post的就不测了,测试方法在TencentTest.postTest(),需要将TencentMapConstant.DISTANCE_API换成自己的即可

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值