补充客户信息
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package com.rj.service;
|
||||
|
||||
import com.rj.dto.NearbyMerchantSearchResult;
|
||||
|
||||
/**
|
||||
* 基于高德地图:地址转坐标 + 周边全行业 POI(商家)检索。
|
||||
*/
|
||||
public interface INearbyMerchantSearchService {
|
||||
|
||||
/**
|
||||
* 根据城市、详细地址得到中心点,再按半径分页拉取周边商家。
|
||||
*
|
||||
* @param city 城市(传给地理编码 city 参数,如「河北省廊坊市三河市燕郊镇」)
|
||||
* @param address 地址/地标(传给地理编码 address 参数)
|
||||
* @param radiusMeters 周边搜索半径(米)
|
||||
* @param maxPage 最多请求页数(从 1 递增)
|
||||
* @param maxRecordsPerPage 每页条数,高德允许 1~50,超出会被裁剪
|
||||
*/
|
||||
NearbyMerchantSearchResult searchNearbyMerchants(String city, String address,
|
||||
int radiusMeters, int maxPage, int maxRecordsPerPage);
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.rj.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.rj.config.AmapProperties;
|
||||
import com.rj.dto.NearbyMerchantItem;
|
||||
import com.rj.dto.NearbyMerchantSearchResult;
|
||||
import com.rj.service.INearbyMerchantSearchService;
|
||||
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.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 逻辑参考 {@link com.rj.gaode.AllShopCollectorV5}:地理编码 + place/around 全行业周边搜索。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchService {
|
||||
|
||||
private static final String GEOCODE_URL = "https://restapi.amap.com/v3/geocode/geo";
|
||||
private static final String AROUND_URL = "https://restapi.amap.com/v3/place/around";
|
||||
private static final int AMAP_OFFSET_MAX = 50;
|
||||
private static final int AMAP_PAGE_DELAY_MS = 800;
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AmapProperties amapProperties;
|
||||
|
||||
public NearbyMerchantSearchServiceImpl(RestTemplate restTemplate,
|
||||
ObjectMapper objectMapper,
|
||||
AmapProperties amapProperties) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
this.amapProperties = amapProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NearbyMerchantSearchResult searchNearbyMerchants(String city, String address,
|
||||
int radiusMeters, int maxPage, int maxRecordsPerPage) {
|
||||
if (!StringUtils.hasText(amapProperties.getWebServiceKey())) {
|
||||
return NearbyMerchantSearchResult.badRequest("未配置高德 Web 服务 Key,请在配置中设置 amap.web-service-key");
|
||||
}
|
||||
if (!StringUtils.hasText(city) || !StringUtils.hasText(address)) {
|
||||
return NearbyMerchantSearchResult.badRequest("CITY 与 ADDRESS 不能为空");
|
||||
}
|
||||
if (radiusMeters <= 0) {
|
||||
return NearbyMerchantSearchResult.badRequest("RADIUS 须为正整数(米)");
|
||||
}
|
||||
if (maxPage < 1) {
|
||||
return NearbyMerchantSearchResult.badRequest("MAX_PAGE 须大于等于 1");
|
||||
}
|
||||
int offset = Math.min(AMAP_OFFSET_MAX, Math.max(1, maxRecordsPerPage));
|
||||
int pages = Math.min(maxPage, 100);
|
||||
|
||||
try {
|
||||
String location = geocodeToLocation(city.trim(), address.trim());
|
||||
List<NearbyMerchantItem> all = new ArrayList<>();
|
||||
String totalCount = null;
|
||||
for (int page = 1; page <= pages; page++) {
|
||||
String json = fetchAroundPage(location, radiusMeters, offset, page);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
if (!"1".equals(root.path("status").asText())) {
|
||||
String info = root.path("info").asText("高德接口返回失败");
|
||||
return NearbyMerchantSearchResult.badRequest("周边搜索失败:" + info);
|
||||
}
|
||||
if (totalCount == null) {
|
||||
totalCount = root.path("count").asText(null);
|
||||
}
|
||||
JsonNode pois = root.path("pois");
|
||||
if (pois.isArray()) {
|
||||
for (JsonNode p : pois) {
|
||||
all.add(new NearbyMerchantItem(
|
||||
text(p, "name"),
|
||||
text(p, "address"),
|
||||
text(p, "tel"),
|
||||
text(p, "type"),
|
||||
text(p, "location")
|
||||
));
|
||||
}
|
||||
}
|
||||
if (page < pages) {
|
||||
try {
|
||||
Thread.sleep(AMAP_PAGE_DELAY_MS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return NearbyMerchantSearchResult.serverError("查询被中断");
|
||||
}
|
||||
}
|
||||
}
|
||||
return NearbyMerchantSearchResult.ok(location, all, totalCount);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return NearbyMerchantSearchResult.badRequest(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.warn("附近商家检索异常: {}", e.getMessage());
|
||||
return NearbyMerchantSearchResult.serverError("附近商家检索异常:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
JsonNode root = objectMapper.readTree(json);
|
||||
if (!"1".equals(root.path("status").asText())) {
|
||||
String info = root.path("info").asText("地理编码失败");
|
||||
throw new IllegalArgumentException("地理编码失败:" + info);
|
||||
}
|
||||
JsonNode geocodes = root.path("geocodes");
|
||||
if (!geocodes.isArray() || geocodes.isEmpty()) {
|
||||
throw new IllegalArgumentException("未解析到该地址的经纬度,请检查 CITY、ADDRESS");
|
||||
}
|
||||
String loc = geocodes.get(0).path("location").asText("");
|
||||
if (!StringUtils.hasText(loc)) {
|
||||
throw new IllegalArgumentException("地理编码结果中无 location 字段");
|
||||
}
|
||||
return loc;
|
||||
}
|
||||
|
||||
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);
|
||||
if (body == null) {
|
||||
throw new IllegalStateException("高德周边搜索返回空响应");
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
private static String text(JsonNode node, String field) {
|
||||
if (node == null || !node.has(field)) {
|
||||
return "";
|
||||
}
|
||||
return node.get(field).asText("");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user