本文主要是介绍springboot项目中整合高德地图的实践,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教...
一:高德开放平台的使用
高德开放平台 | 高德地图API
注册高德地图账号
认证填写个人信息:
认证方式选择“个人认证开发者”即可,然后完善信息
认证成功之后,再次进入控制台,创建关于地图的应用
创建Key(yml文件需要使用):
以上步骤便可以完成高德地图的申请和key的创建。
开始Springboot的创建(就不从0到一的创建了)
二:创建数据库(我是用的是MySQL)
建表语句:
CREATE TABLE `location_record` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `ip` VARCHAR(50) DEFAULT NULL COMMENT '客户端IP地址', `longitude` DOUBLE(10,6) NOT NULL COMMENT '经度坐标,精确到小数点后6位', `latitude` DOUBLE(1China编程0,6) NOT NULL COMMENT '纬度坐标,精确到小数点后6位', `address` VARCHAR(255) DEFAULT NULL COMMENT '详细地址信息', `formatted_address` VARCHAR(255) DEFAULT NULL COMMENT '格式化后的完整地址', `city` VARCHAR(100) DEFAULT NULL COMMENT '所在城市名称', `province` VARCHAR(100) DEFAULT NULL COMMENT '所在省份名称', `district` VARCHAR(100) DEFAULT NULL COMMENT '新增:所在区县名称', `street` VARCHAR(100) DEFAULT NULL COMMENT '新增:街道信息', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间', PRIMARY KEY (`id`), KEY `idx_location` (`longitude`,`latitude`), KEY `idx_create_time` (`create_time`) ) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='地理位置信息记录表';
表中样式:
三:Springboot所需的依赖(根据你的需求再去增加删除依赖)
dependencies> <!-- FastjsON --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency> <!-- OkHttp --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version> 1.2.23</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-Java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>myBATis-plus-boot-starter</artifactId> <version>3.5.6</version> </dependency> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.6.13</version> </dependency> <!-- Apache HttpClient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <!-- Spring Boot Configuration Processor --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <version>2.6.13</version> <optional>true</optional> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.26</version> </dependency> </dependencies>
四:yml文件的配置
server: port: 8096 spring: amap: key: "你的高德地图key值" version: 2.0 geocode-url: https://restapi.amap.com/v3/geocode/geo ip-url: https://restapi.amap.com/v3/ip regeo-url: https://restapi.amap.com/v3/geocode/regeo datasource: # 数据源配置 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/你所建表对应的数据库?serverTimezone=Asia/Shanghai&useSSL=false username: root password: root type: com.alibaba.druid.pool.DruidDataSource # mybatis-plus配置,也可以不加,只是为了显示SQL,Springboot都自动配置好了 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
五:所需要的类
- AmapConfig:
package com.qfedu.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; @Data @Configuration @ConfigurationProperties(prefix = "spring.amap") public class AmapConfig { private String key; // 对应配置中的 key private String securityJsCode; // 对应 security-js-code private String version = "2.0"; // 默认版本号 private String geocodeUrl; // 对应 geocode.url private String ipUrl; // 对应 ip.url private String regeoUrl; // 对应 regeo.url }
- WebMvcConfig(因为我的项目中配置了拦截器,所以需要这样一个类)
package com.qfedu.config; import com.qfedu.common.core.interceptor.LoginInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public LoginInterceptor loginInterceptor() { return new LoginInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(loginInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/api/amap/**", "/api/location/**" // 新增排除location相关路径 ); } }
- HttpUtils:
package com.qfedu.utils; import com.sun.deploy.net.URLEncoder; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.nio.charset.StandardCharsets; public class HttpUtil { public static String doGet(String url) throws IOException { if (url == null || url.trim().isEmpty()) { throw new IllegalArgumentException("URL不能为空"); } CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); // 设置请求头 httpGet.setHeader("Accept", "application/json"); httpGet.setHeader("Content-Type", "application/json"); try (CloseableHttpResponse response = httpClient.execute(httpGet)) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 200) { throw new IOException("HTTP请求失败,状态码: " + statusCode + ", URL: " + url); } HttpEntity entity = response.getEntity(); if (entity == null) { throw new IOException("响应体为空"); } return EntityUtils.toString(entity, StandardCharsets.UTF_8); } finally { httpClient.close(); } } public static String encodeUrlParam(String param) { try { return URLEncoder.encode(param, StandardCharsets.UTF_8.name()); } catch (Exception e) { return param; } } }
- 返回值R类:(我的R类没有使用泛型)
package com.qfedu.common.core.common; public class R { /** * code,指状态码, * 随意定,20000 是正确,40000 错误 * 50000 请求超时 * 60000 没有权限 * msg,指信息描述 * data,返回的数据 */ private int code; private String msg; private Object data; public static R ok() { R r = new R(); r.setCode(200); r.setMsg(编程China编程"成功"); return r; } public static R ok(Object data) { R r = new R(); r.setCode(200); r.setMsg("成功"); r.setData(data); return r; } public static R fail() { R r = new R(); r.setCode(500); r.setMsg("失败"); return r; } public static R fail(String msg) { R r = new R(); r.setCode(500); r.setMsg(msg); return r; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
- 免登录自定义注解:
package com.qfedu.common.core.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 以梦为马,以汗为泉,不忘初心,不负韶华 * * @author ${上官箫宇} * @version 1.0 * @data 2025/6/3 16:08 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)//注解运行时生效 public @interface NoLogin { }
- 拦截器:
package com.qfedu.common.core.interceptor; import com.qfedu.common.core.annotation.NoLogin; import com.qfedu.common.core.constants.CommonConstants; import com.qfedu.common.core.utils.JwtUtils; import com.qfedu.common.core.utils.UserUtils; import io.jsonwebtoken.Claims; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * ---Do First--- * * @author:wellseasun * @date:2025/5/22 下午 8:30 * @desc: */ public class LoginInterceptor implements HandlerInterceptor { // preHandle:执行时机:访问接口前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // handler表示处理某个请求的处理器对象, // 如果是类级别的拦截器,则handler为类对象,如果是方法级别的拦截器,则handler为方法对象 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); boolean annotationPresent = method.isAnnotationPresent(NoLogin.class); if (annotationPresent) { // 如果使用了注解,直接放行 return true; } else { // 没有使用注解,需要从请求头中获取名为LOGIN_TOKEN的token String token = request.getHeader(CommonConstants.LOGIN_TOKEN); if (token == null || token.isEmpty()) { throw new RuntimeException("请重新登录"); } try { JwtUtils jwtUtils = new JwtUtils(); Claims claims = jwtUtils.parseJWT(token); UserUtils.setUid((Integer) claims.get("uid")); } catch (Exception e) { throw e; } } } return true; } }
没错,下面就是层级关系
- mapper:
package com.qfedu.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.qfedu.common.core.entity.LocationRecord; public interface LocationRecordMapper extends BaseMapper<LocationRecord> { }
- service:
package com.qfedu.service; import com.alibaba.fastjson.JSONObject; import com.qfedu.common.core.entity.LocationRecord; public interface AmapService { /** * IP定位 * @param ip IP地址 * @return 定位结果 */ JSONObject ipLocation(String ip); /** * 逆地理编码 * @param longitude 经度 * @param latitude 纬度 * @return 地址信息 */ JSONObject regeoLocation(Double longitude, Double latitude); /** * 地理编码 * @param address 地址 * @return 经纬度信息 */ JSONObject geoLocation(String address); /** * 保存定位记录 * @param record 定位记录 * @return 是否成功 */ boolean saveLocationRecord(LocationRecord record); }
- service实现类:
package com.qfedu.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qfedu.common.core.entity.LocationRecord; import com.qfedu.config.AmapConfig; import com.qfedu.mapper.LocationRecordMapper; import com.qfedu.service.AmapService; import com.qfedu.utils.HttpUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.PostConstruct; /** * 高德地图服务实现类 * 提供IP定位、地理编码、逆地理编码等核心功能 */ @Service public class AmapServiceImpl extends ServiceImpl<LocationRecordMapper, LocationRecord> implements AmapService { private static final Logger logger = LoggerFactory.getLogger(AmapServiceImpl.class); @Autowired private AmapConfig amapConfig; // 高德配置参数(key/url等) /** * 初始化时打印配置信息(调试用) */ @PostConstruct public void init() { logger.info("高德地图配置加载: key={}, ipUrl={}, geoUrl={}, regeoUrl={}", amapConfig.getKey(), amapConfig.getIpUrl(), amapConfig.getGeocodeUrl(), amapConfig.getRegeoUrl()); } // ==================== 核心服务方法 ==================== /** * IP定位服务 * @param ip 需要查询的IP地址 China编程 * @return 包含定位结果的JSON对象(status=1成功,0失败) */ @Override public JSONObject ipLocation(String ip) { // 参数校验 if (!StringUtils.hasText(ip)) { return createErrorResponse("IP地址不能为空"); } try { // 配置校验 validateConfig(amapConfig.getKey(), amapConfig.getIpUrl()); // 构建请求URL(示例:https://restapi.amap.com/v3/ip?key=xxx&ip=8.8.8.8) String url = String.format("%s?key=%s&ip=%s", amapConfig.getIpUrl().trim(), amapConfig.getKey().trim(), HttpUtil.encodeUrlParam(ip)); validateUrl(url); // URL格式校验 logger.info("请求高德IP定位API: {}", url); // 发送HTTP请求并解析响应 String response = HttpUtil.doGet(url); logger.debug("高德IP定位API响应: {}", response); return parseResponse(response); } catch (Exception e) { logger.error("IP定位失败, IP: " + ip, e); return createErrorResponse("IP定位失败: " + e.getMessage()); } } /** * 逆地理编码服务(坐标→地址) * @param longitude 经度 * @param latitude 纬度 * @return 包含地址信息的JSON响应 */ @Override public JSONObject regeoLocation(Double longitude, Double latitude) { if (longitude == null || latitude == null) { return createErrorResponse("经纬度不能为空"); } try { validateConfig(amapConfig.getKey(), null); // 仅校验Key // 构建请求URL(示例:https://restapi.amap.com/v3/geocode/regeo?key=xxx&location=116.4,39.9) String location = longitude + "," + latitude; String url = String.format("%s?key=%s&location=%s", amapConfig.getRegeoUrl(), amapConfig.getKey(), HttpUtil.encodeUrlParam(location)); logger.debug("请求高德逆地理编码API: {}", url); String response = HttpUtil.doGet(url); logger.debug("高德逆地理编码API响应: {}", response); return parseResponse(response); } catch (Exception e) { logger.error("逆地理编码失败, 位置: " + longitude + "," + latitude, e); return createErrorResponse("逆地理编码失败: " + getErrorMessage(e)); } } /** * 地理编码服务(地址→坐标) * @param address 结构化地址(如"北京市海淀区中关村大街1号") * @return 包含经纬度的JSON响应 */ @Override public JSONObject geoLocation(String pythonaddress) { if (!StringUtils.hasText(address)) { return createErrorResponse("地址不能为空"); } try { validateConfig(amapConfig.getKey(), null); // 仅校验Key // 构建请求URL(示例:https://restapi.amap.com/v3/geocode/geo?key=xxx&address=北京) String url = String.format("%s?key=%s&address=%s", amapConfig.getGeocodeUrl(), amapConfig.getKey(), HttpUtil.encodeUrlParam(address)); logger.debug("请求高德地理编码API: {}", url); String response = HttpUtil.doGet(url); logger.debug("高德地理编码API响应: {}", response); return parseResponse(response); } catch (Exception e) { logger.error("地理编码失败, 地址: " + address, e); return createErrorResponse("地理编码失败: " + getErrorMessage(e)); } } /** * 保存定位记录到数据库 * @param record 定位记录实体 * @return 是否保存成功 */ @Override public boolean saveLocationRecord(LocationRecord record) { try { return this.save(record); // 调用MyBatis-Plus的save方法 } catch (Exception e) { logger.error("保存定位记录失败", e); return false; } } // ==================== 内部工具方法 ==================== /** * 校验高德配置参数 * @param key 高德API Key * @param url 需要校验的API地址(可选) * @throws IllegalStateException 当配置不合法时抛出 */ private void validateConfig(String key, String url) { if (amapConfig == null || !StringUtils.hasText(key)) { throw new IllegalStateException("高德地图配置未正确初始化"); } if (url != null && !StringUtils.hasText(url)) { throw new IllegalStateException("高德API地址未配置"); } } /** * 校验URL合法性 * @param url 待校验的URL * @throws IllegalArgumentException 当URL非法时抛出 */ private void validateUrl(String url) { if (!url.startsWith("http")) { throw new IllegalArgumentException("无效的API URL: " + url); } } /** * 解析高德API响应 * @param response 原始JSON字符串 * @return 解析后的JSONObject */ private JSONObject parseResponse(String response) { if (!StringUtils.hasText(response)) { return createErrorResponse("空响应"); } try { JSONObject result = JSON.parseobject(response); return result != null ? result : createErrorResponse("响应解析失败"); } catch (Exception e) { logger.error("解析高德API响应失败", e); return createErrorResponse("响应解析失败: " + e.getMessage()); } } /** * 创建错误响应 * @param message 错误信息 * @return 标准化错误JSON(status=0) */ private JSONObject createErrorResponse(String message) { JSONObject result = new JSONObject(); result.put("status", "0"); // 高德标准错误码 result.put("info", message); return result; } /** * 提取异常信息(避免null) */ private String getErrorMessage(Exception e) { return e.getMessage() != null ? e.getMessage() : "未知错误"; } }
- controller:
package com.qfedu.controller;
import com.alibaba.fastjson.JSONObject;
import com.qfedu.common.core.annotation.NoLogin;
import com.qfedu.common.core.common.R;
import com.qfedu.common.core.entity.LocationRecord;
import com.qfedu.service.AmapService;
import org.springframework.beans.factory.annotation.Autowired;
imVhoZxNmCdnport org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
/**
* 高德地图定位服务控制器
* 提供IP定位、逆地理编码、地理编码三大核心功能
*/
@RestController
@RequestMapping("/api/location")
public class LocationController {
@Autowired
private AmapService amapService; // 高德地图服务接口
/**
* IP定位接口
* @param ip 需要定位的IP地址(如8.8.8.8)
* @return 标准化响应R<JSONObject>,包含定位结果或错误信息
*/
@GetMapping("/ip")
@NoLogin
public R locateByIp(@RequestParam String ip) {
// 调用高德IP定位服务
JSONObject result = amapService.ipLocation(ip);
// 校验高德API返回状态码(1=成功)
if (result != null && "1".equals(result.getString("status"))) {
saveLocationRecord(ip, result); // 持久化定位记录
return R.ok(result); // 返回成功响应
}
// 失败时返回错误信息(优先使用高德返回的info字段)
return R.fail(result != null ? result.getString("info") : "IP定位服务不可用");
}
/**
* 逆地理编码接口(坐标→地址)
* @param longitude 经度(如116.404)
* @param latitude 纬度(如39.915)
* @return 包含地址信息的标准化响应
*/
@GetMapping("/regeo")
@NoLogin
public R regeo(
@RequestParam Double longitude,
@RequestParam Double latitude) {
JSONObject result = amapService.regeoLocation(longitude, latitude);
if (result != null && "1".equals(result.getString("status"))) {
saveLocationRecord(null, longitude, latitude, result); // IP传null
return R.ok(result);
}
return R.fail(result != null ? result.getString("info") : "逆地理编码服务不可用");
}
/**
* 地理编码接口(地址→坐标)
* @param address 结构化地址(如"北京市海淀区中关村大街1号")
* @return 包含经纬度的标准化响应
*/
@GetMapping("/geo")
@NoLogin
public R geo(@RequestParam String address) {
JSONObject result = amapService.geoLocation(address);
if (result != null && "1".equals(result.getString("status"))) {
return R.ok(result); // 地理编码不保存记录
}
return R.fail(result != null ? result.getString("info") : "地理编码服务不可用");
}
// 内部工具方法
/**
* 从IP定位结果提取经纬度并保存记录
* @param ip IP地址
* @param result 高德API返回的完整结果
*/
private void saveLocationRecord(String ip, JSONObject result) {
JSONObject locationObj = result.getJSONObject("location");
if (locationObj != null) {
saveLocationRecord(
ip,
locationObj.getDouble("lng"), // 经度字段
locationObj.getDouble("lat"), // 纬度字段
result
);
}
}
/**
* 保存定位记录到数据库(核心方法)
* @param ip 可能为null(当来源是逆地理编码时)
* @param longitude 经度(必填)
* @param latitude 纬度(必填)
* @param result 高德API原始结果(用于提取地址信息)
*/
private void saveLocationRecord(
String ip,
Double longitude,
Double latitude,
JSONObject result) {
if (result == null) return;
// 1. 构建定位记录实体
LocationRecord record = new LocationRecord();
record.setIp(ip); // IP可能为null
record.setLongitude(longitude);
record.setLatitude(latitude);
// 2. 提取格式化地址(如"北京市海淀区中关村大街1号")
String formattedAddress = result.getString("formatted_address");
record.setAddress(formattedAddress);
record.setFormattedAddress(formattedAddress);
// 3. 提取结构化地址组件(省、市、区等)
JSONObject addressComponent = result.getJSONObject("addressComponent");
if (addressComponent != null) {
record.setProvince(addressComponent.getString("province"));
record.setCity(addressComponent.getString("city"));
// 可扩展:district(区)、street(街道)等字段
}
// 4. 设置时间戳并保存
record.setCreateTime(new Date());
amapService.saveLocationRecord(record); // 调用MyBatis-Plus持久化
}
}
下面就是启动类:(首先不要忘记启动类的扫描注解,还有就是本次用到的注解
@EnableConfigurationProperties({AmapConfig.class})// 启用AmapConfig配置类)
package com.qfedu; import com.qfedu.config.AmapConfig; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; @SpringBootApplication @MapperScan("com.qfedu.mapper") @EnableConfigurationProperties({AmapConfig.class})// 启用AmapConfig配置类 public class MicroServeAmapApplication { public static void main(String[] args) { SpringApplication.run(MicroServeAmapApplication.class, args); } }
写一个简单的页面展示吧
位置你们知道吧,我就不说详细了
展示台湾省地图吧(中国一点都不能少)
下面就是展示的代码,我只不过吧经纬度写成死值了,
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>高德地图展示测试:台湾省台北市地图 - 台北101</title> <!-- 引入高德地图JS API --> <script src="https://webapi.amap.com/maps?v=2.0&key=你的高德地图key"></script> <style> #container { width: 100%; height: 600px; } </style> </head> <body> <!-- 地图容器 --> <div id="container"></div> <script> // 初始化地图,中心点设为台北市 var map = new AMap.Map('container', { zoom: 14, // 缩放级别 center: [121.5654, 25.0330], // 台北市坐标 viewMode: '2D' // 2D地图 }); // 添加标记点(台北101大楼) var marker = new AMap.Marker({ position: [121.5654, 25.0330], // 台北101坐标 map: map }); // 信息窗口内容 var infoWindow = new AMap.InfoWindow({ content: '<div>台北101大楼</div>', offset: new AMap.Pixel(0, -30) }); infoWindow.open(map, marker.getPosition()); </script> </body> </html>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持China编程(www.chinasem.cn)。
这篇关于springboot项目中整合高德地图的实践的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!