把客户信息补充到数据库
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
package com.rj.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
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.entity.CustomerManagement;
|
||||
import com.rj.service.ICustomerManagementService;
|
||||
import com.rj.service.INearbyMerchantSearchService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -14,11 +17,13 @@ import org.springframework.web.client.RestTemplate;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 逻辑参考 {@link com.rj.gaode.AllShopCollectorV5}:地理编码 + place/around 全行业周边搜索。
|
||||
* 逻辑参考 {@link com.rj.gaode AllShopCollectorV5}:地理编码 + place/around 全行业周边搜索。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@@ -28,17 +33,30 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
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;
|
||||
/** 与 customer_management 正面/侧面/生活照三字段一致 */
|
||||
private static final int MAX_PHOTO_URLS = 3;
|
||||
|
||||
/** 高德采集写入客户时的默认门店/销售/意向(可按业务调整) */
|
||||
private static final String DEFAULT_DEALERSHIP_ID = "1957424710587330562";
|
||||
private static final String DEFAULT_DEALERSHIP_NAME = "北京移多旺科技有限公司";
|
||||
private static final String DEFAULT_SALES_ID = "2";
|
||||
private static final String DEFAULT_SALES_NAME = "15810181776";
|
||||
private static final String DEFAULT_SALES_PHONE = "15810181776";
|
||||
private static final String DEFAULT_INTENDED_MODEL = "线索";
|
||||
|
||||
private final RestTemplate restTemplate;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final AmapProperties amapProperties;
|
||||
private final ICustomerManagementService customerManagementService;
|
||||
|
||||
public NearbyMerchantSearchServiceImpl(RestTemplate restTemplate,
|
||||
ObjectMapper objectMapper,
|
||||
AmapProperties amapProperties) {
|
||||
AmapProperties amapProperties,
|
||||
ICustomerManagementService customerManagementService) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.objectMapper = objectMapper;
|
||||
this.amapProperties = amapProperties;
|
||||
this.customerManagementService = customerManagementService;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,12 +94,21 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
JsonNode pois = root.path("pois");
|
||||
if (pois.isArray()) {
|
||||
for (JsonNode p : pois) {
|
||||
String tel = text(p, "tel").trim();
|
||||
if (!StringUtils.hasText(tel)) {
|
||||
continue;
|
||||
}
|
||||
all.add(new NearbyMerchantItem(
|
||||
text(p, "name"),
|
||||
text(p, "address"),
|
||||
text(p, "tel"),
|
||||
tel,
|
||||
text(p, "type"),
|
||||
text(p, "location")
|
||||
text(p, "location"),
|
||||
text(p, "id"),
|
||||
text(p, "distance"),
|
||||
text(p, "typecode"),
|
||||
text(p, "business_area"),
|
||||
extractPhotoUrls(p)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -94,7 +121,15 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
}
|
||||
}
|
||||
}
|
||||
return NearbyMerchantSearchResult.ok(location, all, totalCount);
|
||||
String cTrim = city.trim();
|
||||
String aTrim = address.trim();
|
||||
String report = buildDetailedReport(cTrim, aTrim, radiusMeters, location, totalCount, all);
|
||||
log.info("附近商家检索明细(与 AllShopCollectorV5 格式对齐)\n{}", report);
|
||||
int customerWriteCount = persistMerchantsAsCustomers(all, cTrim, aTrim);
|
||||
if (customerWriteCount > 0) {
|
||||
log.info("高德采集结果已同步 customer_management,成功 {} 条(按 contact 新增或更新)", customerWriteCount);
|
||||
}
|
||||
return NearbyMerchantSearchResult.ok(location, all, totalCount, report, customerWriteCount);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return NearbyMerchantSearchResult.badRequest(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
@@ -104,7 +139,7 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
}
|
||||
|
||||
/**
|
||||
* 地理编码。URL 拼法与 {@link com.rj.gaode#} 一致:
|
||||
* 地理编码。URL 拼法与 {@link com.rj.gaode AllShopCollectorV5} 一致:
|
||||
* 使用 {@link URLEncoder#encode(String, java.nio.charset.Charset)} 处理中文查询参数。
|
||||
* Spring {@code UriComponentsBuilder} 的编码规则与 {@code URLEncoder} 不一致,易导致高德返回 ENGINE_RESPONSE_DATA_ERROR(如 infocode=30001)。
|
||||
* 首次失败后不传 {@code city},仅用合并后的完整地址再请求一次。
|
||||
@@ -185,6 +220,7 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
+ "&radius=" + radius
|
||||
+ "&offset=" + offset
|
||||
+ "&page=" + page
|
||||
+ "&extensions=all"
|
||||
+ "&output=json";
|
||||
String body = restTemplate.getForObject(URI.create(url), String.class);
|
||||
if (body == null) {
|
||||
@@ -199,4 +235,158 @@ public class NearbyMerchantSearchServiceImpl implements INearbyMerchantSearchSer
|
||||
}
|
||||
return node.get(field).asText("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 高德 POI {@code photos} 为数组:元素可为对象 {@code { "url": "..." }} 或个别环境下为 URL 字符串。
|
||||
*/
|
||||
private static List<String> extractPhotoUrls(JsonNode poi) {
|
||||
List<String> urls = new ArrayList<>();
|
||||
JsonNode photos = poi.path("photos");
|
||||
if (!photos.isArray()) {
|
||||
return urls;
|
||||
}
|
||||
for (JsonNode ph : photos) {
|
||||
if (ph == null || ph.isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (ph.isTextual()) {
|
||||
String u = ph.asText().trim();
|
||||
if (StringUtils.hasText(u)) {
|
||||
urls.add(u);
|
||||
if (urls.size() >= MAX_PHOTO_URLS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
String u = ph.path("url").asText("").trim();
|
||||
if (StringUtils.hasText(u)) {
|
||||
urls.add(u);
|
||||
}
|
||||
if (urls.size() >= MAX_PHOTO_URLS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 {@code contact} 与 {@code customer_management} 对齐:已存在则更新,不存在则插入。
|
||||
* 店名/地址/电话/三张照片/门店与销售默认值等见 {@link #applyMerchantDataToCustomer};更新时保留主键、创建时间及录音/联系次数。
|
||||
*/
|
||||
private int persistMerchantsAsCustomers(List<NearbyMerchantItem> items, String searchCity, String searchAddress) {
|
||||
int affected = 0;
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (NearbyMerchantItem item : items) {
|
||||
String contact = item.getTel();
|
||||
if (!StringUtils.hasText(contact)) {
|
||||
continue;
|
||||
}
|
||||
String contactTrim = contact.trim();
|
||||
try {
|
||||
LambdaQueryWrapper<CustomerManagement> w = new LambdaQueryWrapper<CustomerManagement>()
|
||||
.eq(CustomerManagement::getContact, contactTrim)
|
||||
.last("LIMIT 1");
|
||||
CustomerManagement existing = customerManagementService.getOne(w, false);
|
||||
if (existing == null) {
|
||||
CustomerManagement row = new CustomerManagement();
|
||||
row.setId(UUID.randomUUID().toString());
|
||||
applyMerchantDataToCustomer(row, item, contactTrim, searchCity, searchAddress);
|
||||
row.setRecordingCount(0);
|
||||
row.setContactCount(0);
|
||||
row.setCreateTime(now);
|
||||
row.setUpdateTime(now);
|
||||
if (customerManagementService.save(row)) {
|
||||
affected++;
|
||||
}
|
||||
} else {
|
||||
applyMerchantDataToCustomer(existing, item, contactTrim, searchCity, searchAddress);
|
||||
existing.setUpdateTime(now);
|
||||
if (customerManagementService.updateById(existing)) {
|
||||
affected++;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("写入 customer_management 失败, contact={}: {}", contactTrim, e.getMessage());
|
||||
}
|
||||
}
|
||||
return affected;
|
||||
}
|
||||
|
||||
private static void applyMerchantDataToCustomer(CustomerManagement c, NearbyMerchantItem item, String contactTrim,
|
||||
String searchCity, String searchAddress) {
|
||||
String name = item.getName() != null ? item.getName().trim() : "";
|
||||
c.setCustomerName(name.isEmpty() ? "高德POI" : name);
|
||||
c.setDetailedAddress(blankToNull(item.getAddress()));
|
||||
c.setContact(contactTrim);
|
||||
c.setRemark(remarkFromSearchParams(searchCity, searchAddress));
|
||||
List<String> urls = item.getPhotoUrls();
|
||||
if (urls != null && !urls.isEmpty()) {
|
||||
int n = Math.min(urls.size(), MAX_PHOTO_URLS);
|
||||
c.setFrontPhotoUrl(n > 0 ? urls.get(0) : null);
|
||||
c.setSidePhotoUrl(n > 1 ? urls.get(1) : null);
|
||||
c.setLifePhotoUrl(n > 2 ? urls.get(2) : null);
|
||||
} else {
|
||||
c.setFrontPhotoUrl(null);
|
||||
c.setSidePhotoUrl(null);
|
||||
c.setLifePhotoUrl(null);
|
||||
}
|
||||
c.setCustomerSource("AMAP_GaoDe");
|
||||
c.setDealershipId(DEFAULT_DEALERSHIP_ID);
|
||||
c.setDealershipName(DEFAULT_DEALERSHIP_NAME);
|
||||
c.setSalesId(DEFAULT_SALES_ID);
|
||||
c.setSalesName(DEFAULT_SALES_NAME);
|
||||
c.setSalesPhone(DEFAULT_SALES_PHONE);
|
||||
c.setIntendedModel(DEFAULT_INTENDED_MODEL);
|
||||
}
|
||||
|
||||
/** 接口检索参数 CITY、ADDRESS 两段拼接写入 {@code remark} */
|
||||
private static String remarkFromSearchParams(String searchCity, String searchAddress) {
|
||||
String c = searchCity != null ? searchCity.trim() : "";
|
||||
String a = searchAddress != null ? searchAddress.trim() : "";
|
||||
if (!StringUtils.hasText(c) && !StringUtils.hasText(a)) {
|
||||
return null;
|
||||
}
|
||||
if (!StringUtils.hasText(c)) {
|
||||
return a;
|
||||
}
|
||||
if (!StringUtils.hasText(a)) {
|
||||
return c;
|
||||
}
|
||||
return c + " " + a;
|
||||
}
|
||||
|
||||
private static String blankToNull(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
String t = s.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
|
||||
/**
|
||||
* 参考 {@link com.rj.gaode AllShopCollectorV5 parseAndPrint}:每家店为「店名/地址/电话/行业/坐标… + ---------------------------」。
|
||||
*/
|
||||
private static String buildDetailedReport(String city, String address, int radiusMeters, String centerLocation,
|
||||
String totalCount, List<NearbyMerchantItem> merchants) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("===== 附近商家检索(输出格式参考 AllShopCollectorV5)=====\n");
|
||||
sb.append("说明:仅保留联系电话(tel)非空的 POI;高德 count 为接口原始命中总数。\n");
|
||||
sb.append("城市(city):").append(city).append('\n');
|
||||
sb.append("地址(address):").append(address).append('\n');
|
||||
sb.append("搜索半径(米):").append(radiusMeters).append('\n');
|
||||
sb.append("中心经纬度(location):").append(centerLocation).append('\n');
|
||||
sb.append("高德返回总条数(count):").append(totalCount != null ? totalCount : "无").append('\n');
|
||||
sb.append("本响应保留条数(有电话):").append(merchants.size()).append('\n');
|
||||
sb.append("===== 各商铺信息 =====\n");
|
||||
int n = 1;
|
||||
for (NearbyMerchantItem m : merchants) {
|
||||
sb.append("\n【第 ").append(n++).append(" 家】\n");
|
||||
sb.append(m.toDetailBlock()).append('\n');
|
||||
}
|
||||
if (merchants.isEmpty()) {
|
||||
sb.append("\n(无 POI 结果)\n");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user