什么是腾讯地图
腾讯地图(tencent map)是腾讯公司推出的一款数字地图服务,提供丰富的地图展示、定位、搜索、导航等功能。作为国内领先的地图服务提供商,腾讯地图拥有以下特点:
- 海量数据覆盖
- 覆盖全国近400个城市、3000多个区县的地图数据
- 实时更新的poi(兴趣点)数据,包含餐饮、酒店、商场等各类场所
- 精准的路网信息和实时路况数据
- 强大的功能特性
- 位置服务:提供逆/地理编码,实现坐标与地址的相互转换
- 路径规划:支持驾车、步行、骑行、公交等多种出行方式的路线规划
- 周边搜索:基于位置的周边信息查询
- 距离矩阵:计算多个地点之间的时间和距离
- ip定位:通过ip地址获取大致位置
- 天气查询:结合位置信息的实时天气数据
- 技术优势
- 高精度定位能力,支持gps、wi-fi、基站等多种定位方式
- 毫秒级响应速度,保障业务实时性
- 99.9%的服务可用性sla保障
- 丰富的api接口,支持http/https协议
- 应用场景
- 电商物流:配送路线规划、配送范围计算
- 出行服务:网 约车、共享单车位置服务
- 社交应用:位置分享、附近的人
- 生活服务:周边美食、酒店查询
- 企业管理:门店分布、员工签到
springboot集成腾讯地图sdk详细步骤
1. 准备工作
1.1 注册腾讯地图开发者
- 访问腾讯位置服务官网
- 使用qq/微信登录开发者账号
- 完成开发者认证
1.2 创建应用获取key
- 进入控制台 -> 应用管理 -> 我的应用
- 点击"创建应用",填写应用名称
- 选择应用类型(如:webservice)
- 启用所需服务(如:地点搜索、路线规划等)
- 获取key(用于api调用认证)
2. 创建springboot项目
2.1 使用spring initializr创建项目
使用ide创建项目,选择以下依赖:
- spring web
- lombok
- spring configuration processor
2.2 项目结构
src/main/java/com/example/mapdemo/
├── mapdemoapplication.java
├── config/
│ └── tencentmapconfig.java
├── controller/
│ └── mapcontroller.java
├── service/
│ ├── tencentmapservice.java
│ └── impl/
│ └── tencentmapserviceimpl.java
├── dto/
│ ├── request/
│ │ └── locationrequest.java
│ └── response/
│ └── mapresponse.java
└── utils/
└── httpclientutil.java3. 核心代码实现
3.1 maven依赖配置(pom.xml)
<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
xsi:schemalocation="http://maven.apache.org/pom/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelversion>4.0.0</modelversion>
<parent>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-parent</artifactid>
<version>2.7.14</version>
</parent>
<groupid>com.example</groupid>
<artifactid>tencent-map-demo</artifactid>
<version>1.0.0</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- spring boot web -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-web</artifactid>
</dependency>
<!-- lombok -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<optional>true</optional>
</dependency>
<!-- configuration processor -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-configuration-processor</artifactid>
<optional>true</optional>
</dependency>
<!-- httpclient -->
<dependency>
<groupid>org.apache.httpcomponents</groupid>
<artifactid>httpclient</artifactid>
<version>4.5.14</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>fastjson</artifactid>
<version>2.0.32</version>
</dependency>
<!-- commons lang3 -->
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-lang3</artifactid>
</dependency>
</dependencies>
</project>3.2 配置文件(application.yml)
server:
port: 8080
tencent:
map:
key: 你的腾讯地图key
secret-key: 你的密钥(可选,用于数字签名)
base-url: https://apis.map.qq.com
connect-timeout: 5000
read-timeout: 5000
logging:
level:
com.example.mapdemo: debug3.3 配置类
package com.example.mapdemo.config;
import lombok.data;
import org.springframework.boot.context.properties.configurationproperties;
import org.springframework.context.annotation.configuration;
@data
@configuration
@configurationproperties(prefix = "tencent.map")
public class tencentmapconfig {
private string key;
private string secretkey;
private string baseurl = "https://apis.map.qq.com";
private int connecttimeout = 5000;
private int readtimeout = 5000;
}3.4 数据模型类
locationrequest.java - 请求参数
package com.example.mapdemo.dto.request;
import lombok.data;
import javax.validation.constraints.notblank;
@data
public class locationrequest {
@notblank(message = "地址不能为空")
private string address;
private string city; // 城市名称,可选
private double latitude; // 纬度
private double longitude; // 经度
private integer radius = 1000; // 搜索半径,默认1000米
private string keyword; // 搜索关键词
}
mapresponse.java - 响应结果
package com.example.mapdemo.dto.response;
import lombok.builder;
import lombok.data;
import java.util.list;
import java.util.map;
@data
@builder
public class mapresponse<t> {
private integer status; // 状态码,0为成功
private string message; // 状态信息
private t data; // 返回数据
private long requesttime; // 请求时间戳
public static <t> mapresponse<t> success(t data) {
return mapresponse.<t>builder()
.status(0)
.message("success")
.data(data)
.requesttime(system.currenttimemillis())
.build();
}
public static <t> mapresponse<t> error(integer status, string message) {
return mapresponse.<t>builder()
.status(status)
.message(message)
.requesttime(system.currenttimemillis())
.build();
}
}
3.5 http工具类
package com.example.mapdemo.utils;
import lombok.extern.slf4j.slf4j;
import org.apache.http.httpentity;
import org.apache.http.client.config.requestconfig;
import org.apache.http.client.methods.closeablehttpresponse;
import org.apache.http.client.methods.httpget;
import org.apache.http.client.methods.httppost;
import org.apache.http.client.utils.uribuilder;
import org.apache.http.entity.stringentity;
import org.apache.http.impl.client.closeablehttpclient;
import org.apache.http.impl.client.httpclients;
import org.apache.http.util.entityutils;
import org.springframework.stereotype.component;
import java.io.ioexception;
import java.net.uri;
import java.nio.charset.standardcharsets;
import java.util.map;
@slf4j
@component
public class httpclientutil {
private final closeablehttpclient httpclient;
private final requestconfig requestconfig;
public httpclientutil() {
this.httpclient = httpclients.createdefault();
this.requestconfig = requestconfig.custom()
.setconnecttimeout(5000)
.setsockettimeout(5000)
.setconnectionrequesttimeout(5000)
.build();
}
/**
* get请求
*/
public string doget(string url, map<string, string> params) {
try {
uribuilder uribuilder = new uribuilder(url);
if (params != null && !params.isempty()) {
params.foreach(uribuilder::addparameter);
}
uri uri = uribuilder.build();
httpget httpget = new httpget(uri);
httpget.setconfig(requestconfig);
httpget.setheader("content-type", "application/json;charset=utf8");
try (closeablehttpresponse response = httpclient.execute(httpget)) {
httpentity entity = response.getentity();
if (entity != null) {
string result = entityutils.tostring(entity, standardcharsets.utf_8);
log.debug("get请求响应: {}", result);
return result;
}
}
} catch (exception e) {
log.error("get请求异常", e);
}
return null;
}
/**
* post请求(json格式)
*/
public string dopostjson(string url, string json) {
try {
httppost httppost = new httppost(url);
httppost.setconfig(requestconfig);
httppost.setheader("content-type", "application/json;charset=utf8");
stringentity stringentity = new stringentity(json, standardcharsets.utf_8);
httppost.setentity(stringentity);
try (closeablehttpresponse response = httpclient.execute(httppost)) {
httpentity entity = response.getentity();
if (entity != null) {
string result = entityutils.tostring(entity, standardcharsets.utf_8);
log.debug("post请求响应: {}", result);
return result;
}
}
} catch (exception e) {
log.error("post请求异常", e);
}
return null;
}
}
3.6 服务接口
package com.example.mapdemo.service;
import com.example.mapdemo.dto.request.locationrequest;
import com.example.mapdemo.dto.response.mapresponse;
import java.util.map;
public interface tencentmapservice {
/**
* 地理编码(地址转坐标)
*/
mapresponse<?> geocoder(string address, string city);
/**
* 逆地理编码(坐标转地址)
*/
mapresponse<?> reversegeocoder(double latitude, double longitude);
/**
* 地点搜索
*/
mapresponse<?> searchpoi(string keyword, double latitude, double longitude, integer radius);
/**
* 驾车路线规划
*/
mapresponse<?> drivingroute(string origin, string destination);
/**
* 距离矩阵计算
*/
mapresponse<?> distancematrix(string[] origins, string[] destinations);
/**
* ip定位
*/
mapresponse<?> iplocation(string ip);
/**
* 天气查询
*/
mapresponse<?> weather(double latitude, double longitude);
}
3.7 服务实现类
package com.example.mapdemo.service.impl;
import com.alibaba.fastjson.json;
import com.alibaba.fastjson.jsonobject;
import com.example.mapdemo.config.tencentmapconfig;
import com.example.mapdemo.dto.response.mapresponse;
import com.example.mapdemo.service.tencentmapservice;
import com.example.mapdemo.utils.httpclientutil;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.stereotype.service;
import org.springframework.util.stringutils;
import java.util.hashmap;
import java.util.map;
@slf4j
@service
@requiredargsconstructor
public class tencentmapserviceimpl implements tencentmapservice {
private final tencentmapconfig mapconfig;
private final httpclientutil httpclientutil;
/**
* 地理编码 - 地址转坐标
* api文档:https://lbs.qq.com/service/webservice/webserviceguide/webservicegeocoder
*/
@override
public mapresponse<?> geocoder(string address, string city) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("address", address);
if (stringutils.hastext(city)) {
params.put("region", city);
}
string url = mapconfig.getbaseurl() + "/ws/geocoder/v1/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
jsonobject location = jsonresult.getjsonobject("result").getjsonobject("location");
return mapresponse.success(location);
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("地理编码失败", e);
return mapresponse.error(-1, "地理编码失败:" + e.getmessage());
}
}
/**
* 逆地理编码 - 坐标转地址
*/
@override
public mapresponse<?> reversegeocoder(double latitude, double longitude) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("location", latitude + "," + longitude);
params.put("get_poi", "1"); // 是否返回周边poi
string url = mapconfig.getbaseurl() + "/ws/geocoder/v1/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("result"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("逆地理编码失败", e);
return mapresponse.error(-1, "逆地理编码失败:" + e.getmessage());
}
}
/**
* 地点搜索
*/
@override
public mapresponse<?> searchpoi(string keyword, double latitude, double longitude, integer radius) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("keyword", keyword);
params.put("boundary", "nearby(" + latitude + "," + longitude + "," + radius + ")");
params.put("page_size", "20");
params.put("page_index", "1");
string url = mapconfig.getbaseurl() + "/ws/place/v1/search/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("data"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("地点搜索失败", e);
return mapresponse.error(-1, "地点搜索失败:" + e.getmessage());
}
}
/**
* 驾车路线规划
*/
@override
public mapresponse<?> drivingroute(string origin, string destination) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("from", origin);
params.put("to", destination);
params.put("output", "json");
string url = mapconfig.getbaseurl() + "/ws/direction/v1/driving/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("result"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("路线规划失败", e);
return mapresponse.error(-1, "路线规划失败:" + e.getmessage());
}
}
/**
* 距离矩阵计算
*/
@override
public mapresponse<?> distancematrix(string[] origins, string[] destinations) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("from", string.join(";", origins));
params.put("to", string.join(";", destinations));
params.put("mode", "driving"); // 驾车模式
string url = mapconfig.getbaseurl() + "/ws/distance/v1/matrix/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("result"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("距离矩阵计算失败", e);
return mapresponse.error(-1, "距离矩阵计算失败:" + e.getmessage());
}
}
/**
* ip定位
*/
@override
public mapresponse<?> iplocation(string ip) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("ip", ip);
params.put("output", "json");
string url = mapconfig.getbaseurl() + "/ws/location/v1/ip/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("result"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("ip定位失败", e);
return mapresponse.error(-1, "ip定位失败:" + e.getmessage());
}
}
/**
* 天气查询
*/
@override
public mapresponse<?> weather(double latitude, double longitude) {
try {
map<string, string> params = new hashmap<>();
params.put("key", mapconfig.getkey());
params.put("location", latitude + "," + longitude);
params.put("output", "json");
string url = mapconfig.getbaseurl() + "/ws/weather/v1/";
string result = httpclientutil.doget(url, params);
jsonobject jsonresult = json.parseobject(result);
if (jsonresult.getinteger("status") == 0) {
return mapresponse.success(jsonresult.getjsonobject("result"));
} else {
return mapresponse.error(jsonresult.getinteger("status"),
jsonresult.getstring("message"));
}
} catch (exception e) {
log.error("天气查询失败", e);
return mapresponse.error(-1, "天气查询失败:" + e.getmessage());
}
}
}
3.8 控制器类
package com.example.mapdemo.controller;
import com.example.mapdemo.dto.request.locationrequest;
import com.example.mapdemo.dto.response.mapresponse;
import com.example.mapdemo.service.tencentmapservice;
import lombok.requiredargsconstructor;
import lombok.extern.slf4j.slf4j;
import org.springframework.web.bind.annotation.*;
import javax.validation.valid;
@slf4j
@restcontroller
@requestmapping("/api/map")
@requiredargsconstructor
public class mapcontroller {
private final tencentmapservice mapservice;
/**
* 地理编码(地址转坐标)
*/
@getmapping("/geocoder")
public mapresponse<?> geocoder(
@requestparam string address,
@requestparam(required = false) string city) {
log.info("地理编码请求 - 地址: {}, 城市: {}", address, city);
return mapservice.geocoder(address, city);
}
/**
* 逆地理编码(坐标转地址)
*/
@getmapping("/reverse-geocoder")
public mapresponse<?> reversegeocoder(
@requestparam double latitude,
@requestparam double longitude) {
log.info("逆地理编码请求 - 坐标: ({}, {})", latitude, longitude);
return mapservice.reversegeocoder(latitude, longitude);
}
/**
* 地点搜索
*/
@getmapping("/search")
public mapresponse<?> search(
@requestparam string keyword,
@requestparam double latitude,
@requestparam double longitude,
@requestparam(defaultvalue = "1000") integer radius) {
log.info("地点搜索请求 - 关键词: {}, 坐标: ({}, {}), 半径: {}",
keyword, latitude, longitude, radius);
return mapservice.searchpoi(keyword, latitude, longitude, radius);
}
/**
* 路线规划
*/
@getmapping("/route")
public mapresponse<?> route(
@requestparam string origin,
@requestparam string destination) {
log.info("路线规划请求 - 起点: {}, 终点: {}", origin, destination);
return mapservice.drivingroute(origin, destination);
}
/**
* ip定位
*/
@getmapping("/ip-location")
public mapresponse<?> iplocation(@requestparam string ip) {
log.info("ip定位请求 - ip: {}", ip);
return mapservice.iplocation(ip);
}
/**
* 天气查询
*/
@getmapping("/weather")
public mapresponse<?> weather(
@requestparam double latitude,
@requestparam double longitude) {
log.info("天气查询请求 - 坐标: ({}, {})", latitude, longitude);
return mapservice.weather(latitude, longitude);
}
/**
* 距离矩阵计算
*/
@postmapping("/distance-matrix")
public mapresponse<?> distancematrix(@valid @requestbody locationrequest request) {
// 这里简化处理,实际应根据请求构建参数
string[] origins = {request.getlatitude() + "," + request.getlongitude()};
string[] destinations = {"39.984154,116.307490", "39.995120,116.327450"}; // 示例坐标
return mapservice.distancematrix(origins, destinations);
}
}
4. 启动类
package com.example.mapdemo;
import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.springbootapplication;
import org.springframework.boot.context.properties.enableconfigurationproperties;
@springbootapplication
@enableconfigurationproperties
public class mapdemoapplication {
public static void main(string[] args) {
springapplication.run(mapdemoapplication.class, args);
}
}
测试与使用
1. 启动应用
运行 mapdemoapplication.java 的 main 方法
2. api测试
地理编码测试
curl "http://localhost:8080/api/map/geocoder?address=北京市海淀区&city=北京"
地点搜索测试
curl "http://localhost:8080/api/map/search?keyword=餐厅&latitude=39.984154&longitude=116.307490&radius=2000"
详细总结
1. 集成要点总结
1.1 准备工作的重要性
- key管理:腾讯地图api的key是访问服务的凭证,需要妥善保管,建议使用配置文件管理
- 权限配置:在腾讯地图控制台正确配置应用权限,确保所需服务已开通
- 配额限制:了解各api的免费配额和计费规则,避免超出限制导致服务中断
1.2 架构设计特点
- 分层设计:controller-service-repository三层架构,职责清晰
- 配置分离:使用@configurationproperties将配置独立管理,便于维护
- 工具类封装:httpclientutil封装http请求,提高代码复用性
- 统一响应:mapresponse统一api返回格式,便于前端处理
1.3 关键技术实现
- http客户端:使用apache httpclient处理api请求,支持连接池和超时配置
- json处理:fastjson实现请求参数和响应结果的序列化/反序列化
- 参数验证:使用spring validation进行请求参数校验
- 异常处理:全局异常捕获,确保服务稳定性
2. 性能优化
2.1 缓存策略
// 可以考虑使用redis缓存高频查询结果
@cacheable(value = "geocoder", key = "#address + '_' + #city")
public mapresponse<?> geocoder(string address, string city) {
// 实现代码
}
2.2 连接池优化
// 优化httpclient配置
poolinghttpclientconnectionmanager connectionmanager =
new poolinghttpclientconnectionmanager();
connectionmanager.setmaxtotal(200); // 最大连接数
connectionmanager.setdefaultmaxperroute(20); // 每个路由最大连接数
2.3 异步处理
// 使用completablefuture实现异步调用
@async
public completablefuture<mapresponse<?>> asyncgeocoder(string address) {
return completablefuture.completedfuture(geocoder(address, null));
}
3. 安全性考虑
3.1 key保护
- 禁止在前端代码中暴露key
- 使用环境变量或配置中心管理敏感信息
- 定期更换key,降低泄露风险
3.2 请求签名
// 添加签名验证(如腾讯地图支持)
public string generatesignature(map<string, string> params) {
// 按照腾讯地图签名规则生成签名
// 1. 参数排序
// 2. 拼接字符串
// 3. md5加密
}
3.3 访问控制
// 添加接口限流
@ratelimiter(limit = 10, timeout = 1)
public mapresponse<?> geocoder(string address) {
// 实现代码
}
4. 监控与运维
4.1 日志记录
@slf4j
@component
public class mapapiinterceptor {
@around("execution(* com.example.mapdemo.service.*.*(..))")
public object logapicall(proceedingjoinpoint pjp) throws throwable {
long starttime = system.currenttimemillis();
string methodname = pjp.getsignature().getname();
try {
object result = pjp.proceed();
long duration = system.currenttimemillis() - starttime;
log.info("api调用 - {} - 耗时: {}ms", methodname, duration);
return result;
} catch (exception e) {
log.error("api调用异常 - {}", methodname, e);
throw e;
}
}
}
4.2 健康检查
@endpoint(id = "map")
@component
public class maphealthendpoint {
private final tencentmapservice mapservice;
@readoperation
public map<string, object> health() {
map<string, object> health = new hashmap<>();
try {
// 简单测试api可用性
mapservice.geocoder("北京市", null);
health.put("status", "up");
} catch (exception e) {
health.put("status", "down");
health.put("error", e.getmessage());
}
return health;
}
}
5. 常见问题与解决方案
5.1 返回状态码处理
| 状态码 | 含义 | 解决方案 |
|---|---|---|
| 0 | 成功 | - |
| 110 | 请求来源非法 | 检查key是否正确 |
| 311 | 参数缺失 | 检查必填参数 |
| 320 | 请求超过配额 | 升级服务或优化调用 |
| 403 | 请求被拒绝 | 检查ip白名单设置 |
5.2 性能问题
- qps限制:实现请求队列和限流机制
- 超时设置:根据业务需求调整连接超时和读取超时时间
- 数据缓存:对不经常变化的数据增加缓存
6. 扩展建议
6.1 功能扩展
- 接入腾讯地图web js api,实现前端地图展示
- 开发地图数据可视化功能
- 实现路径规划的多种模式(避开高速、少收费等)
7. 最佳实践总结
通过以上步骤,实现了springboot与腾讯地图sdk的集成,实现了以下核心功能:
- 完整的功能覆盖:实现了地理编码、逆地理编码、地点搜索等主流地图服务
- 良好的架构设计:采用分层架构,代码结构清晰,易于维护
- 完善的错误处理:统一的响应格式和异常处理机制
- 可扩展性:预留了缓存、限流等扩展点,便于后续优化
以上就是springboot集成腾讯地图sdk的详细步骤的详细内容,更多关于springboot集成腾讯地图sdk的资料请关注代码网其它相关文章!
发表评论