20260329:1、规范地图,系统已经融入高德、百度正逆地址编码;2、Modbus设备经纬度用LBS指令读取

This commit is contained in:
George 2026-03-29 09:31:27 +08:00
parent 775fa83e25
commit bcc78fe240
18 changed files with 478 additions and 48 deletions

View File

@ -2,16 +2,18 @@ package com.fastbee.web.controller.backend;
import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.utils.StringUtils; 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.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
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 javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -28,6 +30,9 @@ public class BackendController {
@Resource @Resource
private RedisTemplate<String, Object> redisTemplate; private RedisTemplate<String, Object> redisTemplate;
@Resource
private LocationServiceFactory locationServiceFactory;
@ApiOperation("设置缓存值(调试/运维)") @ApiOperation("设置缓存值(调试/运维)")
@PreAuthorize("@ss.hasPermi('monitor:cache:remove')") @PreAuthorize("@ss.hasPermi('monitor:cache:remove')")
@PostMapping("/set") @PostMapping("/set")
@ -64,4 +69,40 @@ public class BackendController {
redisTemplate.delete(key); redisTemplate.delete(key);
return AjaxResult.success("ok"); return AjaxResult.success("ok");
} }
// getAddressByCoordinate
@ApiOperation("根据坐标获取地址(逆地理编码)")
@PreAuthorize("@ss.hasPermi('monitor:cache:remove')")
@GetMapping("/getAddressByCoordinate")
public AjaxResult getAddressByCoordinate(@RequestParam("coordinate") String coordinate, @RequestParam("provider") String provider) {
if (StringUtils.isEmpty(coordinate)) {
return AjaxResult.error("coordinate不能为空");
}
String[] split = coordinate.split(",");
// 工厂模式获取定位服务
ILocationService service = locationServiceFactory.getLocationService(provider);
Coordinate c = new Coordinate(BigDecimal.valueOf(Double.parseDouble(split[0])), BigDecimal.valueOf(Double.parseDouble(split[1])));
AddressInfo addressByCoordinate = service.getAddressByCoordinate(c);
// TODO:用地图服务根据坐标获取地址
return AjaxResult.success(addressByCoordinate);
}
// getCoordinateByAddress
@ApiOperation("根据地址获取坐标(正地理编码)")
@PreAuthorize("@ss.hasPermi('monitor:cache:remove')")
@GetMapping("/getCoordinateByAddress")
public AjaxResult getCoordinateByAddress(@RequestParam("address") String address, @RequestParam("provider") String provider) {
if (StringUtils.isEmpty(address)) {
return AjaxResult.error("address不能为空");
}
// TODO:用地图服务根据地址获取坐标
ILocationService service = locationServiceFactory.getLocationService(provider);
Coordinate coordinateByAddress = service.getCoordinateByAddress(address);
return AjaxResult.success(coordinateByAddress);
}
} }

View File

@ -154,7 +154,3 @@ logging:
swagger: swagger:
enabled: true # 是否开启swagger enabled: true # 是否开启swagger
pathMapping: /dev-api # 请求前缀 pathMapping: /dev-api # 请求前缀
# 地图配置
map:
provider: baidu

View File

@ -153,7 +153,3 @@ logging:
swagger: swagger:
enabled: true # 是否开启swagger enabled: true # 是否开启swagger
pathMapping: /prod-api # 请求前缀 pathMapping: /prod-api # 请求前缀
# 地图配置
map:
provider: baidu

View File

@ -103,7 +103,3 @@ logging:
swagger: swagger:
enabled: true # 是否开启swagger enabled: true # 是否开启swagger
pathMapping: /test-api # 请求前缀 pathMapping: /test-api # 请求前缀
# 地图配置
map:
provider: baidu

View File

@ -217,5 +217,11 @@ liteflow:
scriptTypeField: script_type scriptTypeField: script_type
scriptLanguageField: script_language scriptLanguageField: script_language
scriptEnableField: enable scriptEnableField: enable
other:
# 地图配置
map:
provider: gaode # 提供者
baidu_ak: 0SD4KrYeMXVu8Ox2pxQGDOw3NmDYiKIw baidu_ak: 0SD4KrYeMXVu8Ox2pxQGDOw3NmDYiKIw
gaode_ak: 3af85be39e2f28334097781349336157
gaode_secret: d0561ad20142e74f8c59edc5deafdbe4

View File

@ -371,14 +371,17 @@ public interface FastBeeConstant {
String GB28181 = "GB28181"; String GB28181 = "GB28181";
} }
// 地图相关 /**
* 第三方地图 Web 服务:仅存放根地址与各接口路径,完整 URL = BASE + PATH。
*/
interface MAP { interface MAP {
// 百度 BAIDU // 百度(https://lbs.baidu.com/faq/api?title=webapi/guide/webservice-geocoding)
String BAIDU_GEOCODING = "http://api.map.baidu.com/geocoding/v3/"; String BAIDU_BASE = "https://api.map.baidu.com";
// 高德 GAODE // 高德(https://lbs.amap.com/api/webservice/guide/api/georegeo)
String GAODE_BASE = "https://restapi.amap.com";
// 腾讯 TENCENT // 腾讯 TENCENT(预留)
} }
} }

View File

@ -136,10 +136,10 @@ public class ATCommandParserUtils {
if (lbsValue != null && lbsValue.contains(",")) { if (lbsValue != null && lbsValue.contains(",")) {
String[] parts = lbsValue.split(","); String[] parts = lbsValue.split(",");
try { try {
// 注意:实际顺序可能是 [纬度,经度] 或 [经度,纬度],根据文档通常为经度,纬度 // 返回 [经度,纬度]
double latitude = Double.parseDouble(parts[0]); double latitude = Double.parseDouble(parts[0]);
double longitude = Double.parseDouble(parts[1]); double longitude = Double.parseDouble(parts[1]);
return new double[]{latitude, longitude}; return new double[]{longitude, latitude};
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
return null; return null;
} }
@ -292,7 +292,7 @@ public class ATCommandParserUtils {
System.out.println("LBS解析结果: " + parseLBSString(lbsHex)); System.out.println("LBS解析结果: " + parseLBSString(lbsHex));
double[] lbs = parseLBS(lbsHex); double[] lbs = parseLBS(lbsHex);
if (lbs != null) { if (lbs != null) {
System.out.println("纬度: " + lbs[0] + ", 经度: " + lbs[1]); System.out.println("经度: " + lbs[0] + ", 纬度: " + lbs[1]);
} }
// 测试通用解析 // 测试通用解析

View File

@ -33,6 +33,7 @@ import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
@ -312,7 +313,7 @@ public class DeviceDebugLogAspect {
return; return;
} }
// 跟新IMEI、ICCID // 更新IMEI、ICCID、longitude、latitude
if (StringUtils.isEmpty(device.getImei()) || StringUtils.isEmpty(device.getIccid())) { if (StringUtils.isEmpty(device.getImei()) || StringUtils.isEmpty(device.getIccid())) {
Map<String, String> atCommandMap = ATCommandParserUtils.parseATResponseFromHex(context.sourceData); Map<String, String> atCommandMap = ATCommandParserUtils.parseATResponseFromHex(context.sourceData);
Device updateDevice = new Device(); Device updateDevice = new Device();
@ -322,6 +323,16 @@ public class DeviceDebugLogAspect {
if (StringUtils.isEmpty(device.getIccid())) { if (StringUtils.isEmpty(device.getIccid())) {
updateDevice.setIccid(atCommandMap.get("ICCID")); updateDevice.setIccid(atCommandMap.get("ICCID"));
} }
if (device.getLongitude() == null && device.getLatitude() == null) {
String lbsValue = atCommandMap.get("LBS");
if (lbsValue != null) {
String[] lbsArray = lbsValue.split(",");
if (lbsArray.length == 2) {
updateDevice.setLongitude(new BigDecimal(lbsArray[1]));
updateDevice.setLatitude(new BigDecimal(lbsArray[0]));
}
}
}
updateDevice.setDeviceId(device.getDeviceId()); updateDevice.setDeviceId(device.getDeviceId());
updateDevice.setUpdateBy("DTU"); updateDevice.setUpdateBy("DTU");
updateDevice.setUpdateTime(DateUtils.getNowDate()); updateDevice.setUpdateTime(DateUtils.getNowDate());

View File

@ -118,6 +118,11 @@ public class PowerOnTriggerTaskExecutor {
deviceMessage.setMessage("41542b49434349445c725c6e"); deviceMessage.setMessage("41542b49434349445c725c6e");
deviceMessageService.messagePost(deviceMessage); deviceMessageService.messagePost(deviceMessage);
} }
if (device.getLongitude() == null || device.getLatitude() == null) {
deviceMessage.setMessage("41542b4c42535c725c6e"); // AT+LBS\r\n
deviceMessageService.messagePost(deviceMessage);
}
} }
/** /**

View File

@ -29,7 +29,7 @@ public class ThingsModelPushJob {
/** /**
* 每分钟执行一次,检查并触发到期的推送任务 * 每分钟执行一次,检查并触发到期的推送任务
*/ */
@Scheduled(fixedDelay = 60_000) @Scheduled(fixedDelay = 300_000)
public void execute() { public void execute() {
List<ThingsModelPushConfig> configs = pushConfigService.selectAllEnabled(); List<ThingsModelPushConfig> configs = pushConfigService.selectAllEnabled();
if (CollectionUtils.isEmpty(configs)) { if (CollectionUtils.isEmpty(configs)) {

View File

@ -1,8 +1,4 @@
package com.fastbee.http.ip;/** package com.fastbee.http.ip;
* @description: IP地址相关客户端调用
* @author Sumuxi
* @date 2026/3/8
*/
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.constant.Constants; import com.fastbee.common.constant.Constants;

View File

@ -5,6 +5,7 @@ import com.fastbee.common.constant.Constants;
import com.fastbee.common.constant.ErrorCodeConstants; import com.fastbee.common.constant.ErrorCodeConstants;
import com.fastbee.common.constant.FastBeeConstant; import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.utils.http.HttpUtils; import com.fastbee.common.utils.http.HttpUtils;
import com.fastbee.http.model.AddressInfo;
import com.fastbee.http.model.Coordinate; import com.fastbee.http.model.Coordinate;
import com.fastbee.http.utils.CoordinateTransformUtils; import com.fastbee.http.utils.CoordinateTransformUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -16,6 +17,7 @@ import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import static com.fastbee.common.exception.ServiceExceptionUtil.exception; import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
@ -30,9 +32,15 @@ import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
@Service("baiduLocationService") @Service("baiduLocationService")
public class BaiduILocationService implements ILocationService { public class BaiduILocationService implements ILocationService {
@Value("${other.baidu_ak}") @Value("${map.baidu_ak}")
private String baiduAK; private String baiduAK;
// 地理编码
private static final String BAIDU_PATH_GEOCODING = "/geocoding/v3/";
// 逆地理编码
private static final String BAIDU_PATH_REVERSE_GEOCODING = "/reverse_geocoding/v3/";
/** /**
* 根据结构化地址获取地理编码(即经纬度) * 根据结构化地址获取地理编码(即经纬度)
* 地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能。 * 地理编码服务提供将结构化地址数据(如:北京市海淀区上地十街十号)转换为对应坐标点(经纬度)功能。
@ -43,7 +51,7 @@ public class BaiduILocationService implements ILocationService {
public Coordinate getCoordinateByAddress(String address) { public Coordinate getCoordinateByAddress(String address) {
log.info("结构化地址:{}", address); log.info("结构化地址:{}", address);
String baseUrl = FastBeeConstant.MAP.BAIDU_GEOCODING; String baseUrl = FastBeeConstant.MAP.BAIDU_BASE + BAIDU_PATH_GEOCODING;
address = StringUtils.isEmpty(address) || Objects.equals("内网IP", address) ? "湖南省长沙市岳麓区" : address; address = StringUtils.isEmpty(address) || Objects.equals("内网IP", address) ? "湖南省长沙市岳麓区" : address;
String encodedAddress; String encodedAddress;
try { try {
@ -61,6 +69,55 @@ public class BaiduILocationService implements ILocationService {
return null; return null;
} }
/**
* 逆地理编码:坐标系为 BD09,与百度地理编码返回及本类 {@link #convertPlatformCoordinateFromWgs84} 一致。
* 文档:https://lbs.baidu.com/faq/api?title=webapi/guide/webservice-geocoding
*/
@Override
public AddressInfo getAddressByCoordinate(Coordinate coordinate) {
if (coordinate == null || coordinate.getLongitude() == null || coordinate.getLatitude() == null) {
log.warn("百度逆地理编码:坐标为空");
return null;
}
if (StringUtils.isBlank(baiduAK)) {
log.warn("百度逆地理编码:未配置 map.baidu_ak");
return null;
}
double lat = coordinate.getLatitude().doubleValue();
double lng = coordinate.getLongitude().doubleValue();
// location:纬度,经度;coordtype 与坐标系一致
String location = String.format(Locale.US, "%.6f,%.6f", lat, lng);
String param = "ak=" + baiduAK + "&output=json&coordtype=bd09ll&location=" + location;
String response = HttpUtils.sendGet(FastBeeConstant.MAP.BAIDU_BASE + BAIDU_PATH_REVERSE_GEOCODING, param, Constants.UTF8);
if (StringUtils.isEmpty(response)) {
return null;
}
JSONObject root = JSONObject.parseObject(response);
if (root == null || root.getIntValue("status") != 0) {
log.warn("百度逆地理编码失败 status={} message={}", root != null ? root.get("status") : null,
root != null ? root.getString("message") : 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("country"));
info.setProvince(ac.getString("province"));
info.setCity(ac.getString("city"));
info.setDistrict(ac.getString("district"));
info.setTown(ac.getString("town"));
info.setStreet(ac.getString("street"));
info.setStreetNumber(ac.getString("street_number"));
}
info.setBuilding(result.getString("business"));
return info;
}
@Override @Override
public Coordinate convertToWgs84(Coordinate baiduCoordinate) { public Coordinate convertToWgs84(Coordinate baiduCoordinate) {
@ -71,4 +128,13 @@ public class BaiduILocationService implements ILocationService {
return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1])); return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1]));
} }
@Override
public Coordinate convertPlatformCoordinateFromWgs84(Coordinate wgs84Coordinate) {
//世界坐标系(WGS84)的坐标转换为百度坐标系(BD09)
double[] doubles = CoordinateTransformUtils.wgs84ToBd09(wgs84Coordinate.getLongitude().doubleValue(),
wgs84Coordinate.getLatitude().doubleValue());
return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1]));
}
} }

View File

@ -1,10 +1,27 @@
package com.fastbee.http.map; package com.fastbee.http.map;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.fastbee.common.constant.Constants;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.utils.http.HttpUtils;
import com.fastbee.common.utils.sign.Md5Utils;
import com.fastbee.http.model.AddressInfo;
import com.fastbee.http.model.Coordinate; import com.fastbee.http.model.Coordinate;
import com.fastbee.http.utils.CoordinateTransformUtils; import com.fastbee.http.utils.CoordinateTransformUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
/** /**
* @description: 高德地图API实现类 * @description: 高德地图API实现类
@ -12,15 +29,205 @@ import java.math.BigDecimal;
* @author Sumuxi * @author Sumuxi
* @date 2026/3/8 * @date 2026/3/8
*/ */
@Slf4j
@Service("gaodeLocationService") @Service("gaodeLocationService")
public class GaodeLocationService implements ILocationService { public class GaodeLocationService implements ILocationService {
@Value("${map.gaode_ak:3af85be39e2f28334097781349336157}")
private String gaodeAK;
/**
* 数字签名私钥(控制台开启「数字签名」后必填)。
* 算法参见:https://lbs.amap.com/faq/quota-key/key/41181
*/
@Value("${map.gaode_secret:d0561ad20142e74f8c59edc5deafdbe4}")
private String gaodeSecret;
/** 地理编码:地址 → 经纬度(GCJ-02) */
private static final String GAODE_PATH_GEO = "/v3/geocode/geo";
/** 逆地理编码:经纬度 → 地址 */
private static final String GAODE_PATH_REGEO = "/v3/geocode/regeo";
/**
* 地理编码:结构化地址转经纬度,坐标系为 GCJ-02(火星坐标),与高德 Web 服务一致。
* 文档:https://lbs.amap.com/api/webservice/guide/api/georegeo
* 数字签名:MD5 使用 UTF-8 下原始参数值拼接(与控制台一致),请求中的 address 需单独 URL 编码,参见
* https://lbs.amap.com/faq/quota-key/key/41181
*/
@Override @Override
public Coordinate getCoordinateByAddress(String address) { public Coordinate getCoordinateByAddress(String address) {
return null; if (StringUtils.isBlank(address)) {
log.warn("高德地理编码:地址为空");
return null;
}
if (StringUtils.isBlank(gaodeAK)) {
log.warn("高德地理编码:未配置 map.gaode_ak");
return null;
}
String raw = address.trim();
TreeMap<String, String> forSign = new TreeMap<>();
forSign.put("address", raw);
forSign.put("output", "json");
forSign.put("key", gaodeAK);
String sig = null;
if (StringUtils.isNotBlank(gaodeSecret)) {
sig = Md5Utils.hash(joinKeyValueSorted(forSign) + gaodeSecret);
}
String encodedAddress;
try {
encodedAddress = URLEncoder.encode(raw, StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
log.error("高德地理编码:地址 UTF-8 编码失败", e);
return null;
}
StringBuilder qb = new StringBuilder();
qb.append("address=").append(encodedAddress);
qb.append("&key=").append(gaodeAK);
qb.append("&output=json");
if (sig != null) {
qb.append("&sig=").append(sig);
}
String response = HttpUtils.sendGet(FastBeeConstant.MAP.GAODE_BASE + GAODE_PATH_GEO, qb.toString(), Constants.UTF8);
if (StringUtils.isBlank(response)) {
return null;
}
JSONObject root = JSONObject.parseObject(response);
if (root == null || !"1".equals(String.valueOf(root.get("status")))) {
log.warn("高德地理编码失败 status={} info={}", root != null ? root.get("status") : null,
root != null ? root.getString("info") : null);
return null;
}
JSONArray geocodes = root.getJSONArray("geocodes");
if (geocodes == null || geocodes.isEmpty()) {
return null;
}
JSONObject first = geocodes.getJSONObject(0);
if (first == null) {
return null;
}
String location = first.getString("location");
if (StringUtils.isBlank(location)) {
return null;
}
String[] parts = location.split(",");
if (parts.length != 2) {
return null;
}
try {
BigDecimal lng = new BigDecimal(parts[0].trim());
BigDecimal lat = new BigDecimal(parts[1].trim());
return new Coordinate(lng, lat);
} catch (NumberFormatException e) {
log.warn("高德地理编码:无法解析坐标 location={}", location);
return null;
}
}
/**
* 逆地理编码:坐标系为 GCJ-02(火星坐标),与高德 Web 服务一致。
* 文档:https://lbs.amap.com/api/webservice/guide/api/georegeo
*/
@Override
public AddressInfo getAddressByCoordinate(Coordinate coordinate) {
if (coordinate == null || coordinate.getLongitude() == null || coordinate.getLatitude() == null) {
log.warn("高德逆地理编码:坐标为空");
return null;
}
if (StringUtils.isBlank(gaodeAK)) {
log.warn("高德逆地理编码:未配置 map.gaode_key");
return null;
}
double lng = coordinate.getLongitude().doubleValue();
double lat = coordinate.getLatitude().doubleValue();
// 经度在前、纬度在后,小数点后不超过 6 位
String location = String.format(Locale.US, "%.6f,%.6f", lng, lat);
Map<String, String> regeoParams = new HashMap<>(4);
regeoParams.put("extensions", "base");
regeoParams.put("location", location);
regeoParams.put("output", "json");
String param = buildGaodeWebServiceQuery(regeoParams);
String response = HttpUtils.sendGet(FastBeeConstant.MAP.GAODE_BASE + GAODE_PATH_REGEO, param, Constants.UTF8);
if (StringUtils.isBlank(response)) {
return null;
}
JSONObject root = JSONObject.parseObject(response);
if (root == null || !"1".equals(String.valueOf(root.get("status")))) {
log.warn("高德逆地理编码失败 status={} info={}", root != null ? root.get("status") : null,
root != null ? root.getString("info") : null);
return null;
}
JSONObject regeocode = root.getJSONObject("regeocode");
if (regeocode == null) {
return null;
}
AddressInfo info = new AddressInfo();
info.setFormattedAddress(regeocode.getString("formatted_address"));
JSONObject ac = regeocode.getJSONObject("addressComponent");
if (ac != null) {
info.setCountry(ac.getString("country"));
info.setProvince(ac.getString("province"));
info.setCity(parseGaodeCity(ac));
info.setDistrict(ac.getString("district"));
info.setTown(ac.getString("township"));
JSONObject streetNumber = ac.getJSONObject("streetNumber");
if (streetNumber != null) {
info.setStreet(streetNumber.getString("street"));
info.setStreetNumber(streetNumber.getString("number"));
}
JSONObject building = ac.getJSONObject("building");
if (building != null) {
info.setBuilding(building.getString("name"));
}
}
return info;
}
/**
* 直辖市等场景下 city 可能为空数组,需回退到省名等。
*/
private static String parseGaodeCity(JSONObject ac) {
Object cityVal = ac.get("city");
if (cityVal instanceof String) {
String s = (String) cityVal;
if (StringUtils.isNotBlank(s)) {
return s;
}
} else if (cityVal instanceof JSONArray) {
JSONArray arr = (JSONArray) cityVal;
if (!arr.isEmpty()) {
return arr.getString(0);
}
}
String province = ac.getString("province");
return StringUtils.isNotBlank(province) ? province : null;
}
/**
* 组装高德 Web 服务 GET 查询串:参数名升序拼接后接私钥做 MD5 得到 sig(与官方数字签名说明一致)。
* 未配置 {@code map.gaode_secret} 时不传 sig,适用于未开启数字签名的 Key。
*/
private String buildGaodeWebServiceQuery(Map<String, String> paramsWithoutKey) {
TreeMap<String, String> sorted = new TreeMap<>(paramsWithoutKey);
sorted.put("key", gaodeAK);
if (StringUtils.isNotBlank(gaodeSecret)) {
String signSource = joinKeyValueSorted(sorted) + gaodeSecret;
sorted.put("sig", Md5Utils.hash(signSource));
}
return joinKeyValueSorted(sorted);
}
private static String joinKeyValueSorted(TreeMap<String, String> sorted) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> e : sorted.entrySet()) {
if (sb.length() > 0) {
sb.append('&');
}
sb.append(e.getKey()).append('=').append(e.getValue());
}
return sb.toString();
} }
@Override @Override
@ -33,4 +240,14 @@ public class GaodeLocationService implements ILocationService {
return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1])); return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1]));
} }
@Override
public Coordinate convertPlatformCoordinateFromWgs84(Coordinate wgs84Coordinate) {
// 将世界坐标 转化为 高德(火星)坐标
double[] doubles = CoordinateTransformUtils.wgs84ToGcj02(
wgs84Coordinate.getLongitude().doubleValue(),
wgs84Coordinate.getLatitude().doubleValue()
);
return new Coordinate(BigDecimal.valueOf(doubles[0]), BigDecimal.valueOf(doubles[1]));
}
} }

View File

@ -1,5 +1,6 @@
package com.fastbee.http.map; package com.fastbee.http.map;
import com.fastbee.http.model.AddressInfo;
import com.fastbee.http.model.Coordinate; import com.fastbee.http.model.Coordinate;
/** /**
@ -18,11 +19,13 @@ public interface ILocationService {
Coordinate getCoordinateByAddress(String address); Coordinate getCoordinateByAddress(String address);
/** /**
* 根据经纬度获取详细地址 * 根据经纬度逆地理编码获取结构化地址。
* @param coordinate 经纬度 * 坐标系因实现而异:{@code gaodeLocationService} 使用 GCJ-02;{@code baiduLocationService} 使用 BD09。
* @return 详细地址信息 *
* @param coordinate 经纬度(经度、纬度)
* @return 详细地址信息;参数无效、配置缺失或第三方失败时可能为 {@code null}
*/ */
//AddressInfo getAddressByCoordinate(Coordinate coordinate); AddressInfo getAddressByCoordinate(Coordinate coordinate);
/** /**
@ -31,4 +34,12 @@ public interface ILocationService {
* @return 世界坐标系 * @return 世界坐标系
*/ */
Coordinate convertToWgs84(Coordinate coordinate); Coordinate convertToWgs84(Coordinate coordinate);
/**
* 将世界坐标系(WGS84)的坐标转换为指定平台(高德、百度、腾讯)坐标系。
* @param wgs84Coordinate WGS84 坐标
* @return 转换后的目标坐标系坐标
*/
Coordinate convertPlatformCoordinateFromWgs84(Coordinate wgs84Coordinate);
} }

View File

@ -26,14 +26,23 @@ public class LocationServiceFactory {
/** /**
* 获取当前配置的 LocationService 实例 * 获取当前配置的 LocationService 实例
* @param providerArr 指定地图提供者,优先级高于配置文件
* @return 具体服务实现 * @return 具体服务实现
*/ */
public ILocationService getLocationService() { public ILocationService getLocationService(String... providerArr) {
// if ("gaode".equalsIgnoreCase(provider)) {
// return gaodeLocationService; String provider;
// } else if ("tencent".equalsIgnoreCase(provider)) { // 优先参数指定服务商
// return tencentLocationService; if (providerArr != null && providerArr.length > 0){
// } provider = providerArr[0];
} else {
provider = this.provider;
}
if ("gaode".equalsIgnoreCase(provider)) {
return gaodeLocationService;
} /*else if ("tencent".equalsIgnoreCase(provider)) {
return tencentLocationService;
}*/
// 默认返回百度 // 默认返回百度
return baiduLocationService; return baiduLocationService;
} }

View File

@ -0,0 +1,59 @@
package com.fastbee.http.model;
import lombok.Data;
/**
* @description: 地址信息
* 国家、省份、城市、区县、城镇、乡村、街道、门牌号码、屋邨、大厦
*
* @author Sumuxi
* @date 2026/3/28
*/
@Data
public class AddressInfo {
/**
* 格式化地址
*/
private String formattedAddress;
/**
* 国家
*/
private String country;
/**
* 省份
*/
private String province;
/**
* 城市
*/
private String city;
/**
* 区/县
*/
private String district;
/**
* 乡/镇
*/
private String town;
/**
* 街道
*/
private String street;
/**
* 门牌号
*/
private String streetNumber;
/**
* 建筑/楼栋
*/
private String building;
}

View File

@ -181,7 +181,7 @@ public class Device extends BaseEntity {
/** /**
* 是否自定义位置 * 是否自定义位置
**/ **/
@ApiModelProperty("定位方式(1=ip自动定位,2=设备定位,3=自定义)") @ApiModelProperty("定位方式(1=自动定位[ip地址、modbus LBS],2=设备定位,3=自定义)")
private Integer locationWay; private Integer locationWay;
/** /**

View File

@ -27,6 +27,7 @@ import com.fastbee.common.utils.json.JsonUtils;
import com.fastbee.http.ip.IPAddressService; import com.fastbee.http.ip.IPAddressService;
import com.fastbee.http.map.ILocationService; import com.fastbee.http.map.ILocationService;
import com.fastbee.http.map.LocationServiceFactory; import com.fastbee.http.map.LocationServiceFactory;
import com.fastbee.http.model.AddressInfo;
import com.fastbee.http.model.Coordinate; import com.fastbee.http.model.Coordinate;
import com.fastbee.iot.cache.IDeviceCache; import com.fastbee.iot.cache.IDeviceCache;
import com.fastbee.iot.cache.ITSLCache; import com.fastbee.iot.cache.ITSLCache;
@ -1099,6 +1100,7 @@ public class DeviceServiceImpl implements IDeviceService {
// 随机经纬度和地址 // 随机经纬度和地址
//SysUser user = getLoginUser().getUser(); //SysUser user = getLoginUser().getUser();
device.setNetworkIp(sysUser.getLoginIp()); device.setNetworkIp(sysUser.getLoginIp());
device.setTransport(product.getTransport()); // 便于Modbus设备LBS定位
setLocation(sysUser.getLoginIp(), device); setLocation(sysUser.getLoginIp(), device);
deviceMapper.insertDevice(device); deviceMapper.insertDevice(device);
// 处理modbus产品 // 处理modbus产品
@ -1632,13 +1634,29 @@ public class DeviceServiceImpl implements IDeviceService {
* @param device 设备信息 * @param device 设备信息
*/ */
private void setLocation(String ip, Device device) { private void setLocation(String ip, Device device) {
// 工厂模式获取定位服务
ILocationService service = locationServiceFactory.getLocationService();
// Modbus设备采用LBS定位(从设备获取经纬度)
if (Objects.equals(1, device.getLocationWay()) && FastBeeConstant.PROTOCOL.ModbusRtu.equals(device.getTransport())) {
log.info("Modbus设备采用LBS定位{}", device.getSerialNumber());
// 将经纬度转换为地址编码 networkAddress
if (StringUtils.isEmpty(device.getNetworkAddress()) && device.getLongitude() != null && device.getLatitude() != null) {
Coordinate platformCoordinate = service.convertPlatformCoordinateFromWgs84(new Coordinate(device.getLongitude(), device.getLatitude()));
AddressInfo addressInfo = service.getAddressByCoordinate(platformCoordinate);
String networkAddress = StringUtils.isEmpty(addressInfo.getBuilding()) ? addressInfo.getFormattedAddress() : addressInfo.getBuilding();
device.setNetworkAddress(networkAddress);
log.info("{}- 设置地址:{}", device.getSerialNumber(), networkAddress);
}
return;
}
try { try {
// 根据IP获取地址 // 根据IP获取地址
String address = ipAddressService.getAddressByIP(ip); String address = ipAddressService.getAddressByIP(ip);
device.setNetworkAddress(address); device.setNetworkAddress(address);
log.info("{}- 设置地址:{}", device.getSerialNumber(), address); log.info("{}- 设置地址:{}", device.getSerialNumber(), address);
// 查询经纬度 // 查询经纬度
ILocationService service = locationServiceFactory.getLocationService();
Coordinate coordinate = service.getCoordinateByAddress(address); Coordinate coordinate = service.getCoordinateByAddress(address);
Coordinate wgs84Coordinate = service.convertToWgs84(coordinate); Coordinate wgs84Coordinate = service.convertToWgs84(coordinate);
device.setLongitude(wgs84Coordinate.getLongitude()); device.setLongitude(wgs84Coordinate.getLongitude());