diff --git a/src/main/java/com/rj/service/impl/NearbyMerchantSearchServiceImpl.java b/src/main/java/com/rj/service/impl/NearbyMerchantSearchServiceImpl.java index 5c6f617..84fb1fd 100644 --- a/src/main/java/com/rj/service/impl/NearbyMerchantSearchServiceImpl.java +++ b/src/main/java/com/rj/service/impl/NearbyMerchantSearchServiceImpl.java @@ -10,8 +10,10 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -101,44 +103,90 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer } } + /** + * 地理编码。URL 拼法与 {@link com.rj.gaode#} 一致: + * 使用 {@link URLEncoder#encode(String, java.nio.charset.Charset)} 处理中文查询参数。 + * Spring {@code UriComponentsBuilder} 的编码规则与 {@code URLEncoder} 不一致,易导致高德返回 ENGINE_RESPONSE_DATA_ERROR(如 infocode=30001)。 + * 首次失败后不传 {@code city},仅用合并后的完整地址再请求一次。 + */ private String geocodeToLocation(String city, String address) throws Exception { - String url = UriComponentsBuilder.fromUriString(GEOCODE_URL) - .queryParam("key", amapProperties.getWebServiceKey()) - .queryParam("city", city) - .queryParam("address", address) - .queryParam("output", "json") - .build() - .encode() - .toUriString(); - String json = restTemplate.getForObject(url, String.class); + String json = fetchGeocode(city, address); + String loc = parseGeocodeLocation(json); + if (loc != null) { + return loc; + } + + String merged = mergeCityAddress(city, address); + log.debug("地理编码首次未返回坐标,不传 city、使用合并地址重试"); + json = fetchGeocode(null, merged); + loc = parseGeocodeLocation(json); + if (loc != null) { + return loc; + } + + throw geocodeFailureException(json); + } + + /** 与 AllShopCollectorV5 相同:key、c(可选)、address、output;city 与 address 经 UTF-8 URLEncoder。无 city 时不带 city 参数。 */ + private String fetchGeocode(String city, String address) { + String key = amapProperties.getWebServiceKey(); + StringBuilder sb = new StringBuilder(GEOCODE_URL); + sb.append("?key=").append(key); + if (StringUtils.hasText(city)) { + sb.append("&city=").append(URLEncoder.encode(city, StandardCharsets.UTF_8)); + } + sb.append("&address=").append(URLEncoder.encode(address, StandardCharsets.UTF_8)); + sb.append("&output=json"); + String url = sb.toString(); + String json = restTemplate.getForObject(URI.create(url), String.class); + if (json == null) { + throw new IllegalStateException("高德地理编码返回空响应"); + } + return json; + } + + /** status=1 且存在 location 时返回坐标,否则返回 null。 */ + private String parseGeocodeLocation(String json) throws Exception { JsonNode root = objectMapper.readTree(json); if (!"1".equals(root.path("status").asText())) { - String info = root.path("info").asText("地理编码失败"); - throw new IllegalArgumentException("地理编码失败:" + info); + return null; } JsonNode geocodes = root.path("geocodes"); if (!geocodes.isArray() || geocodes.isEmpty()) { - throw new IllegalArgumentException("未解析到该地址的经纬度,请检查 CITY、ADDRESS"); + return null; } String loc = geocodes.get(0).path("location").asText(""); - if (!StringUtils.hasText(loc)) { - throw new IllegalArgumentException("地理编码结果中无 location 字段"); - } - return loc; + return StringUtils.hasText(loc) ? loc : null; } + private static String mergeCityAddress(String city, String address) { + String c = city.trim(); + String a = address.trim(); + if (a.startsWith(c)) { + return a; + } + return c + a; + } + + private IllegalArgumentException geocodeFailureException(String json) throws Exception { + JsonNode root = objectMapper.readTree(json); + String info = root.path("info").asText("地理编码失败"); + String infocode = root.path("infocode").asText(""); + if (StringUtils.hasText(infocode)) { + return new IllegalArgumentException("地理编码失败:" + info + "(infocode=" + infocode + ")"); + } + return new IllegalArgumentException("地理编码失败:" + info); + } + + /** 与 AllShopCollectorV5#getAllShops 相同的参数顺序与拼接方式(location 为经纬度串,无需 URL 编码)。 */ private String fetchAroundPage(String location, int radius, int offset, int page) { - String url = UriComponentsBuilder.fromUriString(AROUND_URL) - .queryParam("key", amapProperties.getWebServiceKey()) - .queryParam("location", location) - .queryParam("radius", radius) - .queryParam("offset", offset) - .queryParam("page", page) - .queryParam("output", "json") - .build() - .encode() - .toUriString(); - String body = restTemplate.getForObject(url, String.class); + String url = AROUND_URL + "?key=" + amapProperties.getWebServiceKey() + + "&location=" + location + + "&radius=" + radius + + "&offset=" + offset + + "&page=" + page + + "&output=json"; + String body = restTemplate.getForObject(URI.create(url), String.class); if (body == null) { throw new IllegalStateException("高德周边搜索返回空响应"); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c6ccd5d..2949f19 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -271,9 +271,10 @@ dify: top-sales-alldim-token: app-99nVuuKfcRR2vikxgOTiNQm0 classification-token: app-Id1eOuv5yBb1pexpEWVDqSS3 -# 高德地图 Web 服务(地理编码、周边 POI);本地可设环境变量 AMAP_WEB_SERVICE_KEY +# 高德地图 Web 服务(地理编码、周边 POI) +# 未设环境变量时使用与 com.rj.gaode.AllShopCollectorV5 一致的本地默认;生产/正式环境请设置 AMAP_WEB_SERVICE_KEY 覆盖 amap: - web-service-key: ${AMAP_WEB_SERVICE_KEY:} + web-service-key: ${AMAP_WEB_SERVICE_KEY:34eaa47387aa9e8c1a8ca0d4c8abaa1d} # MinIO 对象存储配置 minio: