From 1f197f80a11101c460bba491ec96058de49c9d64 Mon Sep 17 00:00:00 2001 From: zhugongping Date: Wed, 12 Aug 2026 14:53:02 +0800 Subject: [PATCH] =?UTF-8?q?20260812:1=E3=80=81=E5=A4=A9=E5=9C=B0=E5=9B=BE?= =?UTF-8?q?=E5=9C=B0=E7=90=86=E7=BC=96=E7=A0=81=EF=BC=9B=E9=80=86=E5=9C=B0?= =?UTF-8?q?=E7=90=86=E7=BC=96=E7=A0=81=EF=BC=9B2=E3=80=81=E5=9C=B0?= =?UTF-8?q?=E5=9B=BE=E9=80=89=E7=82=B9=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/map/MapLocationController.java | 72 +++++++ .../src/main/resources/application.yml | 3 +- .../common/constant/FastBeeConstant.java | 3 + .../fastbee/http/map/ILocationService.java | 8 +- .../http/map/LocationServiceFactory.java | 5 + .../http/map/TiandituLocationService.java | 192 +++++++++++++++++ yack-web/src/api/iot/project.js | 18 ++ yack-web/src/views/iot/project/index.vue | 204 ++++++++++++++++-- 8 files changed, 481 insertions(+), 24 deletions(-) create mode 100644 fastbee-admin/src/main/java/com/fastbee/web/controller/map/MapLocationController.java create mode 100644 fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/TiandituLocationService.java diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/map/MapLocationController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/map/MapLocationController.java new file mode 100644 index 00000000..70a79f6e --- /dev/null +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/map/MapLocationController.java @@ -0,0 +1,72 @@ +package com.fastbee.web.controller.map; + +import com.fastbee.common.core.domain.AjaxResult; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.http.map.ILocationService; +import com.fastbee.http.map.LocationServiceFactory; +import com.fastbee.http.model.AddressInfo; +import com.fastbee.http.model.Coordinate; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import 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 javax.annotation.Resource; +import java.math.BigDecimal; + +/** + * 地图定位接口(供项目管理-地图选点使用) + * + * @author Sumuxi + * @date 2026/8/12 + */ +@Api(tags = "地图定位") +@RestController +@RequestMapping("/map/location") +public class MapLocationController { + + @Resource + private LocationServiceFactory locationServiceFactory; + + /** + * 根据中文地址获取经纬度(正地理编码) + */ + @ApiOperation("根据中文地址获取经纬度") + @PreAuthorize("@ss.hasPermi('iot:project:edit')") + @GetMapping("/geocode") + public AjaxResult geocode(@RequestParam("address") String address, + @RequestParam(value = "provider", defaultValue = "tianditu") String provider) { + if (StringUtils.isEmpty(address)) { + return AjaxResult.error("地址不能为空"); + } + ILocationService service = locationServiceFactory.getLocationService(provider); + Coordinate coordinate = service.getCoordinateByAddress(address); + if (coordinate == null || coordinate.getLongitude() == null || coordinate.getLatitude() == null) { + return AjaxResult.error("未找到该地址的坐标,请尝试更详细的地址"); + } + return AjaxResult.success(coordinate); + } + + /** + * 根据经纬度获取中文地址(逆地理编码) + */ + @ApiOperation("根据经纬度获取中文地址") + @PreAuthorize("@ss.hasPermi('iot:project:edit')") + @GetMapping("/reverseGeocode") + public AjaxResult reverseGeocode(@RequestParam("lng") Double lng, @RequestParam("lat") Double lat, + @RequestParam(value = "provider", defaultValue = "tianditu") String provider) { + if (lng == null || lat == null) { + return AjaxResult.error("经纬度不能为空"); + } + ILocationService service = locationServiceFactory.getLocationService(provider); + AddressInfo addressInfo = service.getAddressByCoordinate( + new Coordinate(BigDecimal.valueOf(lng), BigDecimal.valueOf(lat))); + if (addressInfo == null) { + return AjaxResult.error("未获取到该坐标对应的地址"); + } + return AjaxResult.success(addressInfo); + } +} diff --git a/fastbee-admin/src/main/resources/application.yml b/fastbee-admin/src/main/resources/application.yml index 6357b46d..191e5485 100644 --- a/fastbee-admin/src/main/resources/application.yml +++ b/fastbee-admin/src/main/resources/application.yml @@ -230,4 +230,5 @@ map: provider: gaode # 提供者 baidu_ak: 0SD4KrYeMXVu8Ox2pxQGDOw3NmDYiKIw gaode_ak: 3af85be39e2f28334097781349336157 - gaode_secret: d0561ad20142e74f8c59edc5deafdbe4 \ No newline at end of file + gaode_secret: d0561ad20142e74f8c59edc5deafdbe4 + tianditu_ak: b920e6076a06aa279c974f5f76cf746e diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java index d60c0b3a..2478040b 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java +++ b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java @@ -389,6 +389,9 @@ public interface FastBeeConstant { // 高德(https://lbs.amap.com/api/webservice/guide/api/georegeo) String GAODE_BASE = "https://restapi.amap.com"; + // 天地图(http://lbs.tianditu.gov.cn/server/geocodinginterface.html) + String TIANDITU_BASE = "https://api.tianditu.gov.cn"; + // 腾讯 TENCENT(预留) } diff --git a/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/ILocationService.java b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/ILocationService.java index 7fffc3ba..29bd0850 100644 --- a/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/ILocationService.java +++ b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/ILocationService.java @@ -33,7 +33,9 @@ public interface ILocationService { * @param coordinate 不同(BD09,GCJ02,WGS84)实现类坐标 * @return 世界坐标系 */ - Coordinate convertToWgs84(Coordinate coordinate); + default Coordinate convertToWgs84(Coordinate coordinate) { + return coordinate; + } /** @@ -41,5 +43,7 @@ public interface ILocationService { * @param wgs84Coordinate WGS84 坐标 * @return 转换后的目标坐标系坐标 */ - Coordinate convertPlatformCoordinateFromWgs84(Coordinate wgs84Coordinate); + default Coordinate convertPlatformCoordinateFromWgs84(Coordinate wgs84Coordinate) { + return wgs84Coordinate; + } } diff --git a/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/LocationServiceFactory.java b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/LocationServiceFactory.java index 0ad76b7b..752bfa5e 100644 --- a/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/LocationServiceFactory.java +++ b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/LocationServiceFactory.java @@ -20,6 +20,9 @@ public class LocationServiceFactory { @Resource private ILocationService gaodeLocationService; + @Resource + private ILocationService tiandituLocationService; + // 从配置文件读取当前使用的服务商 @Value("${map.provider:baidu}") private String provider; @@ -40,6 +43,8 @@ public class LocationServiceFactory { } if ("gaode".equalsIgnoreCase(provider)) { return gaodeLocationService; + } else if ("tianditu".equalsIgnoreCase(provider)) { + return tiandituLocationService; } /*else if ("tencent".equalsIgnoreCase(provider)) { return tencentLocationService; }*/ diff --git a/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/TiandituLocationService.java b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/TiandituLocationService.java new file mode 100644 index 00000000..b921d6be --- /dev/null +++ b/fastbee-plugs/fastbee-http/src/main/java/com/fastbee/http/map/TiandituLocationService.java @@ -0,0 +1,192 @@ +package com.fastbee.http.map; + +import com.alibaba.fastjson2.JSONObject; +import com.fastbee.common.constant.FastBeeConstant; +import com.fastbee.http.model.AddressInfo; +import com.fastbee.http.model.Coordinate; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 天地图地图API服务 + *

+ * 文档:http://lbs.tianditu.gov.cn/server/geocodinginterface.html + * 坐标系:天地图使用 CGCS2000,与 WGS84 在实用精度下可视为一致。 + * + * @author Sumuxi + * @date 2026/8/12 + */ +@Slf4j +@Service("tiandituLocationService") +public class TiandituLocationService implements ILocationService { + + @Value("${map.tianditu_ak:b920e6076a06aa279c974f5f76cf746e}") + private String tiandituAK; + + /** 地理编码/逆地理编码共用的接口路径 */ + private static final String TIANDITU_PATH_GEOCODER = "/geocoder"; + + /** + * 正地理编码:中文地址 → 经纬度。 + * 请求:http://api.tianditu.gov.cn/geocoder?ds={"keyWord":"北京市海淀区莲花池西路28号"}&tk=您的密钥 + */ + @Override + public Coordinate getCoordinateByAddress(String address) { + if (StringUtils.isBlank(address)) { + log.warn("天地图地理编码:地址为空"); + return null; + } + if (StringUtils.isBlank(tiandituAK)) { + log.warn("天地图地理编码:未配置 map.tianditu_ak"); + return null; + } + + JSONObject ds = new JSONObject(); + ds.put("keyWord", address.trim()); + String encodedDs = urlEncode(ds.toJSONString()); + if (encodedDs == null) { + return null; + } + + String response = sendGet(FastBeeConstant.MAP.TIANDITU_BASE + TIANDITU_PATH_GEOCODER + + "?ds=" + encodedDs + "&tk=" + tiandituAK); + if (StringUtils.isBlank(response)) { + return null; + } + + JSONObject root = JSONObject.parseObject(response); + if (root == null || !"0".equals(String.valueOf(root.get("status")))) { + log.warn("天地图地理编码失败 status={} msg={}", root != null ? root.get("status") : null, + root != null ? root.getString("msg") : null); + return null; + } + JSONObject location = root.getJSONObject("location"); + if (location == null) { + log.warn("天地图地理编码:响应缺少 location"); + return null; + } + try { + BigDecimal lng = new BigDecimal(location.getString("lon")); + BigDecimal lat = new BigDecimal(location.getString("lat")); + log.info("天地图地理编码:地址 {} -> 经度 {},纬度 {}", address, lng, lat); + return new Coordinate(lng, lat); + } catch (NumberFormatException | NullPointerException e) { + log.warn("天地图地理编码:无法解析坐标 location={}", location.toJSONString()); + return null; + } + } + + /** + * 逆地理编码:经纬度 → 地址。 + * 请求:http://api.tianditu.gov.cn/geocoder?postStr={"lon":116.37304,"lat":39.92594,"ver":1}&type=geocode&tk=您的密钥 + */ + @Override + public AddressInfo getAddressByCoordinate(Coordinate coordinate) { + if (coordinate == null || coordinate.getLongitude() == null || coordinate.getLatitude() == null) { + log.warn("天地图逆地理编码:坐标为空"); + return null; + } + if (StringUtils.isBlank(tiandituAK)) { + log.warn("天地图逆地理编码:未配置 map.tianditu_ak"); + return null; + } + + JSONObject postStr = new JSONObject(); + postStr.put("lon", coordinate.getLongitude().doubleValue()); + postStr.put("lat", coordinate.getLatitude().doubleValue()); + postStr.put("ver", 1); + String encodedPostStr = urlEncode(postStr.toJSONString()); + if (encodedPostStr == null) { + return null; + } + + String response = sendGet(FastBeeConstant.MAP.TIANDITU_BASE + TIANDITU_PATH_GEOCODER + + "?postStr=" + encodedPostStr + "&type=geocode&tk=" + tiandituAK); + if (StringUtils.isBlank(response)) { + return null; + } + + JSONObject root = JSONObject.parseObject(response); + if (root == null || !"0".equals(String.valueOf(root.get("status")))) { + log.warn("天地图逆地理编码失败 status={} msg={}", root != null ? root.get("status") : null, + root != null ? root.getString("msg") : null); + return null; + } + JSONObject result = root.getJSONObject("result"); + if (result == null) { + return null; + } + AddressInfo info = new AddressInfo(); + info.setFormattedAddress(result.getString("formatted_address")); + JSONObject ac = result.getJSONObject("addressComponent"); + if (ac != null) { + info.setCountry(ac.getString("nation")); + info.setProvince(ac.getString("province")); + info.setCity(ac.getString("city")); + info.setDistrict(ac.getString("county")); + info.setTown(ac.getString("town")); + info.setStreet(ac.getString("road")); + info.setBuilding(ac.getString("poi")); + } + return info; + } + + private String urlEncode(String value) { + try { + return URLEncoder.encode(value, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + log.error("天地图:参数 UTF-8 编码失败", e); + return null; + } + } + + /** + * 天地图接口对 User-Agent 敏感:浏览器 UA(含 {@code HttpUtils} 固定的 IE6 UA)会被 WAF + * 识别为浏览器访问,从而拒绝服务端 Key(HTTP 403 权限类型错误)。 + * 因此这里使用非浏览器 UA 直连,且请求必须携带 URL 编码后的完整查询串。 + */ + private String sendGet(String url) { + HttpURLConnection conn = null; + try { + conn = (HttpURLConnection) new URL(url).openConnection(); + conn.setRequestMethod("GET"); + conn.setConnectTimeout(30000); + conn.setReadTimeout(30000); + conn.setRequestProperty("Accept", "*/*"); + conn.setRequestProperty("User-Agent", "fastbee-server/3.8.5"); + int code = conn.getResponseCode(); + if (code != HttpURLConnection.HTTP_OK) { + log.warn("天地图请求失败 code={}", code); + return null; + } + try (BufferedReader in = new BufferedReader( + new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { + StringBuilder result = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + result.append(line); + } + return result.toString(); + } + } catch (IOException e) { + log.error("天地图请求异常", e); + return null; + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } +} diff --git a/yack-web/src/api/iot/project.js b/yack-web/src/api/iot/project.js index 3abfc6da..9e70eae9 100644 --- a/yack-web/src/api/iot/project.js +++ b/yack-web/src/api/iot/project.js @@ -61,6 +61,24 @@ export function delProject(projectId) { }) } +// 根据中文地址查询坐标(天地图地理编码,走后端服务端密钥) +export function getCoordinateByAddress(address) { + return request({ + url: '/map/location/geocode', + method: 'get', + params: { address: address, provider: 'tianditu' } + }) +} + +// 根据经纬度查询中文地址(天地图逆地理编码,走后端服务端密钥) +export function getAddressByCoordinate(lng, lat) { + return request({ + url: '/map/location/reverseGeocode', + method: 'get', + params: { lng: lng, lat: lat, provider: 'tianditu' } + }) +} + // 根据负责人ID查询项目列表 export function listProjectByUser(userId) { return request({ diff --git a/yack-web/src/views/iot/project/index.vue b/yack-web/src/views/iot/project/index.vue index 42296ef9..669395b9 100644 --- a/yack-web/src/views/iot/project/index.vue +++ b/yack-web/src/views/iot/project/index.vue @@ -49,7 +49,7 @@ {{ getStatusText(scope.row.projectStatus) }} - + + + +