Просмотр исходного кода

同步聚好麦平台的成交状态+新增聚好麦平台的商店参数配置

cgp 2 дней назад
Родитель
Сommit
3b4a8ba008

+ 204 - 0
fs-admin/src/main/java/com/fs/his/task/JhmTask.java

@@ -0,0 +1,204 @@
+package com.fs.his.task;
+
+import com.fs.jhmApi.client.JHMThirdApiClient;
+import com.fs.jhmApi.config.JhmStoreConfig;
+import com.fs.jhmApi.model.request.JHMOrderInfoRequest;
+import com.fs.jhmApi.model.response.JHMBaseResponse;
+import com.fs.jhmApi.model.response.JHMOrderDetail;
+import com.fs.jhmApi.model.response.JHMOrderInfoData;
+import com.fs.qw.domain.FsCompanyCustomer;
+import com.fs.qw.mapper.FsCompanyCustomerMapper;
+import com.fs.system.service.ISysConfigService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.time.Instant;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Slf4j
+@Component("JhmTask")
+public class JhmTask {
+
+    @Autowired
+    private FsCompanyCustomerMapper fsCompanyCustomerMapper;
+
+    @Autowired
+    private ISysConfigService sysConfigService;
+
+    private static final String CONFIG_KEY = "jhmStore.config";
+
+    public void syncOrderToCustomer() {
+        List<JhmStoreConfig.StoreConfig> storeConfigs = loadStoreConfigs();
+        if (CollectionUtils.isEmpty(storeConfigs)) {
+            log.error("未找到聚好麦店铺配置,任务终止");
+            return;
+        }
+
+        for (JhmStoreConfig.StoreConfig config : storeConfigs) {
+            try {
+                syncOrdersForStore(config);
+            } catch (Exception e) {
+                log.error("同步店铺[{}]订单失败", config.getStoreName(), e);
+            }
+        }
+    }
+
+    private List<JhmStoreConfig.StoreConfig> loadStoreConfigs() {
+        JhmStoreConfig storeConfig = sysConfigService.getConfig(CONFIG_KEY, JhmStoreConfig.class);
+        if (storeConfig == null || CollectionUtils.isEmpty(storeConfig.getStores())) {
+            return Collections.emptyList();
+        }
+        return storeConfig.getStores();
+    }
+
+    private void syncOrdersForStore(JhmStoreConfig.StoreConfig config) {
+        log.info("开始同步店铺[{}]订单", config.getStoreName());
+
+        JHMThirdApiClient apiClient = new JHMThirdApiClient(config.getAppKey(), config.getAppSecret());
+        String fullUrl = config.getBusinessUrl();
+
+        List<JHMOrderDetail> validOrders = fetchAllOrders(apiClient, fullUrl);
+        if (CollectionUtils.isEmpty(validOrders)) {
+            log.info("店铺[{}]无有效订单", config.getStoreName());
+            return;
+        }
+
+        List<JHMOrderDetail> dedupOrders = deduplicateByPhone(validOrders);
+        log.info("店铺[{}]去重后订单数: {}", config.getStoreName(), dedupOrders.size());
+
+        List<String> phones = dedupOrders.stream()
+                .map(o -> o.getBuyer().getReceivePhone())
+                .distinct()
+                .collect(Collectors.toList());
+
+        List<FsCompanyCustomer> existingCustomers = fsCompanyCustomerMapper.selectByPhones(phones);
+        Map<String, FsCompanyCustomer> phoneCustomerMap = existingCustomers.stream()
+                .collect(Collectors.toMap(FsCompanyCustomer::getPhone, c -> c, (a, b) -> b));
+
+        List<FsCompanyCustomer> toUpdateList = new ArrayList<>();
+        for (JHMOrderDetail order : dedupOrders) {
+            String phone = order.getBuyer().getReceivePhone();
+            FsCompanyCustomer customer = phoneCustomerMap.get(phone);
+            if (customer == null) {
+                log.debug("手机号[{}]无对应客户,跳过", phone);
+                continue;
+            }
+
+            Integer kdzlMakeStatus = (order.getStatus() != null && order.getStatus() == 4) ? 1 : 0;
+            String address = order.getBuyer().getAddress();
+            String customerName = order.getBuyer().getConsignee();
+            Date orderTime = order.getCreatedAt() != null ?
+                    Date.from(Instant.ofEpochSecond(order.getCreatedAt())) : null;
+
+            boolean needUpdate = false;
+            if (!Objects.equals(customer.getKdzlMakeStatus(), kdzlMakeStatus)) {
+                customer.setKdzlMakeStatus(kdzlMakeStatus);
+                needUpdate = true;
+            }
+            if (!Objects.equals(customer.getAddress(), address)) {
+                customer.setAddress(address);
+                needUpdate = true;
+            }
+            if (!Objects.equals(customer.getCustomerName(), customerName)) {
+                customer.setCustomerName(customerName);
+                needUpdate = true;
+            }
+            if (orderTime != null && !Objects.equals(customer.getCreateTime(), orderTime)) {
+                customer.setCreateTime(orderTime);
+                needUpdate = true;
+            }
+
+            if (needUpdate) {
+                toUpdateList.add(customer);
+            }
+        }
+
+        if (toUpdateList.isEmpty()) {
+            log.info("店铺[{}]无客户信息需要更新", config.getStoreName());
+            return;
+        }
+        // 批量更新
+        int rows = fsCompanyCustomerMapper.batchUpdateForJhmTask(toUpdateList);
+        log.info("店铺[{}]更新完成,成功: {}", config.getStoreName(), rows);
+    }
+
+    private List<JHMOrderDetail> fetchAllOrders(JHMThirdApiClient apiClient, String fullUrl) {
+        List<JHMOrderDetail> allOrders = new ArrayList<>();
+        int page = 1;
+        int pageSize = 100;
+        long sevenDaysAgo = System.currentTimeMillis() / 1000 - 7 * 24 * 3600;
+        long now = System.currentTimeMillis() / 1000;
+
+        ParameterizedTypeReference<JHMBaseResponse<JHMOrderInfoData>> responseType =
+                new ParameterizedTypeReference<JHMBaseResponse<JHMOrderInfoData>>() {};
+
+        while (true) {
+            JHMOrderInfoRequest request = new JHMOrderInfoRequest();
+            request.setFilterType(1);
+            request.setStartTime(sevenDaysAgo);
+            request.setEndTime(now);
+            request.setPage(page);
+            request.setPageSize(pageSize);
+
+            JHMBaseResponse<JHMOrderInfoData> response;
+            try {
+                response = apiClient.doPost(fullUrl, request, responseType);
+            } catch (Exception e) {
+                log.error("请求聚好麦订单接口失败,page={}", page, e);
+                break;
+            }
+            JHMOrderInfoData data = handleResponse(response, fullUrl);
+            if (data == null || CollectionUtils.isEmpty(data.getList())) {
+                break;
+            }
+
+            List<JHMOrderDetail> validInPage = data.getList().stream()
+                    .filter(order -> order.getStatus() != null && (order.getStatus() == 2 || order.getStatus() == 4))
+                    .collect(Collectors.toList());
+            allOrders.addAll(validInPage);
+
+            if (allOrders.size() >= data.getTotal() || data.getList().size() < pageSize) {
+                break;
+            }
+            page++;
+        }
+        log.info("共拉取到 {} 条有效订单", allOrders.size());
+        return allOrders;
+    }
+
+    private List<JHMOrderDetail> deduplicateByPhone(List<JHMOrderDetail> orders) {
+        Map<String, JHMOrderDetail> map = new HashMap<>();
+        for (JHMOrderDetail order : orders) {
+            String phone = order.getBuyer().getReceivePhone();
+            JHMOrderDetail existing = map.get(phone);
+            if (existing == null ||
+                    (order.getCreatedAt() != null && order.getCreatedAt() > existing.getCreatedAt())) {
+                map.put(phone, order);
+            }
+        }
+        return new ArrayList<>(map.values());
+    }
+
+    private <T> T handleResponse(JHMBaseResponse<T> response, String url) {
+        if (response == null) {
+            throw new RuntimeException("聚好麦第三方接口返回空响应,url: " + url);
+        }
+        if (!Boolean.TRUE.equals(response.getSuccess())) {
+            log.error("聚好麦第三方接口 success=false, code: {}, traceId: {}, url: {}",
+                    response.getCode(), response.getTraceId(), url);
+            throw new RuntimeException(String.format("聚好麦第三方接口回显失败,code=%s, traceId=%s",
+                    response.getCode(), response.getTraceId()));
+        }
+        if (response.getCode() == null || !response.getCode().equals(10000)) {
+            log.error("聚好麦第三方业务错误, code: {}, traceId: {}, url: {}",
+                    response.getCode(), response.getTraceId(), url);
+            throw new RuntimeException(String.format("聚好麦第三方业务错误,code=%s, traceId=%s",
+                    response.getCode(), response.getTraceId()));
+        }
+        return response.getData();
+    }
+}

+ 16 - 53
fs-service/src/main/java/com/fs/jhmApi/client/JHMThirdApiClient.java

@@ -1,12 +1,9 @@
 package com.fs.jhmApi.client;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fs.jhmApi.config.JHMThirdApiConfig;
-import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.core.ParameterizedTypeReference;
 import org.springframework.http.*;
-import org.springframework.stereotype.Component;
 import org.springframework.web.client.RestTemplate;
 import org.springframework.web.util.UriComponentsBuilder;
 
@@ -17,96 +14,64 @@ import java.util.Map;
 import java.util.TreeMap;
 import java.util.UUID;
 
-/**
- * 第三方开放平台 HTTP 客户端
- * 功能:自动添加系统参数(app_key/nonce/timestamp/sign)并执行 POST 请求
- * 注意:所有接口均为 POST,系统参数在 Query,业务参数在 Body(JSON)
- */
 @Slf4j
-@Component
-@RequiredArgsConstructor
 public class JHMThirdApiClient {
 
-    private final JHMThirdApiConfig config;
+    private final String appKey;
+    private final String appSecret;
     private final ObjectMapper objectMapper;
+    private static final RestTemplate REST_TEMPLATE = new RestTemplate();
 
-    private static final RestTemplate restTemplate;
-
-    static {
-        restTemplate = new RestTemplate();
+    public JHMThirdApiClient(String appKey, String appSecret) {
+        this.appKey = appKey;
+        this.appSecret = appSecret;
+        this.objectMapper = new ObjectMapper(); // 内部创建,线程安全
     }
 
-    /**
-     * 通用 POST 请求,返回泛型响应
-     *
-     * @param path         接口路径(如 "/open-api/order/info")
-     * @param body         业务请求体对象(自动序列化为 JSON)
-     * @param responseType 响应类型引用(用于保留泛型,如 new ParameterizedTypeReference<BaseResponse<OrderInfoData>>() {})
-     * @param <T>          响应类型
-     * @return 解析后的响应对象
-     */
-    public <T> T doPost(String path, Object body, ParameterizedTypeReference<T> responseType) {
+    public <T> T doPost(String url, Object body, ParameterizedTypeReference<T> responseType) {
         try {
-            // 1. 序列化 Body 为 JSON
             String rawBodyJson = objectMapper.writeValueAsString(body);
-            // 去除所有空白字符(签名和发送均使用压缩后的 JSON)
             String compactBody = rawBodyJson.replaceAll("\\s+", "");
 
-            // 2. 生成系统参数
             String nonce = UUID.randomUUID().toString().replace("-", "").substring(0, 12);
-            long timestamp = System.currentTimeMillis() / 1000; // 10位秒级时间戳
+            long timestamp = System.currentTimeMillis() / 1000;
 
-            // 使用 TreeMap 自动按键字典序排序
             Map<String, String> queryParams = new TreeMap<>();
-            queryParams.put("app_key", config.getAppKey());
+            queryParams.put("app_key", appKey);
             queryParams.put("nonce", nonce);
             queryParams.put("timestamp", String.valueOf(timestamp));
 
-            // 3. 生成签名
-            String sign = buildSign(config.getAppSecret(), compactBody, queryParams);
+            String sign = buildSign(appSecret, compactBody, queryParams);
             queryParams.put("sign", sign);
 
-            // 4. 构建 URL(使用 UriComponentsBuilder 逐个添加 Query 参数,避免泛型警告)
-            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(config.getBaseUrl() + path);
+            UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
             for (Map.Entry<String, String> entry : queryParams.entrySet()) {
                 builder.queryParam(entry.getKey(), entry.getValue());
             }
-            String url = builder.build().toUriString();
+            String fullUrl = builder.build().toUriString();
 
-            // 5. 设置 Headers
             HttpHeaders headers = new HttpHeaders();
             headers.setContentType(MediaType.APPLICATION_JSON);
             headers.set(HttpHeaders.ACCEPT_CHARSET, StandardCharsets.UTF_8.name());
 
             HttpEntity<String> httpEntity = new HttpEntity<>(compactBody, headers);
-
-            log.debug("请求 URL: {}, Body: {}", url, compactBody);
-            ResponseEntity<T> responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, responseType);
+            log.debug("请求 URL: {}, Body: {}", fullUrl, compactBody);
+            ResponseEntity<T> responseEntity = REST_TEMPLATE.exchange(fullUrl, HttpMethod.POST, httpEntity, responseType);
             return responseEntity.getBody();
 
         } catch (Exception e) {
-            log.error("调用聚好麦第三方接口异常, path: {}", path, e);
+            log.error("调用聚好麦第三方接口异常, url: {}", url, e);
             throw new RuntimeException("聚好麦第三方接口调用失败: " + e.getMessage(), e);
         }
     }
 
-    /**
-     * 签名算法(严格按文档实现)
-     *
-     * @param appSecret    应用密钥
-     * @param compactBody  去除空白后的 Body JSON
-     * @param systemParams 系统参数(已包含 app_key, nonce, timestamp)
-     * @return 大写 MD5 签名
-     */
     private String buildSign(String appSecret, String compactBody, Map<String, String> systemParams) {
-        // 1. 将系统参数和 body 放入 TreeMap 进行字典序排序
         TreeMap<String, String> sortedMap = new TreeMap<>();
         sortedMap.put("app_key", systemParams.get("app_key"));
         sortedMap.put("nonce", systemParams.get("nonce"));
         sortedMap.put("timestamp", systemParams.get("timestamp"));
         sortedMap.put("body", compactBody);
 
-        // 2. 拼接:appSecret + key1value1key2value2... + appSecret
         StringBuilder sb = new StringBuilder(appSecret);
         for (Map.Entry<String, String> entry : sortedMap.entrySet()) {
             sb.append(entry.getKey()).append(entry.getValue());
@@ -116,7 +81,6 @@ public class JHMThirdApiClient {
         String strToSign = sb.toString();
         log.debug("待签名字符串: {}", strToSign);
 
-        // 3. MD5 加密并转大写
         try {
             MessageDigest md = MessageDigest.getInstance("MD5");
             byte[] digest = md.digest(strToSign.getBytes(StandardCharsets.UTF_8));
@@ -126,7 +90,6 @@ public class JHMThirdApiClient {
         }
     }
 
-    /** 字节数组转十六进制字符串 */
     private String bytesToHex(byte[] bytes) {
         char[] hexArray = "0123456789ABCDEF".toCharArray();
         char[] hexChars = new char[bytes.length * 2];

+ 0 - 23
fs-service/src/main/java/com/fs/jhmApi/config/JHMThirdApiConfig.java

@@ -1,23 +0,0 @@
-package com.fs.jhmApi.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-/**
- * HTTP客户端 + 签名工具
- * */
-@Data
-@Component
-//@ConfigurationProperties(prefix = "third-api")
-public class JHMThirdApiConfig {
-    /** 应用 AppKey(测试:KHAUN24Y8ANJ76QE7VY1MWTQ) */
-    private String appKey;
-    /** 应用 AppSecret(测试:8UI5HIO308X2AKYSX841BABPRBNYSI2N) */
-    private String appSecret;
-    /** 接口基础地址(生产:https://api.yixikeji.cn) */
-    private String baseUrl;
-    /** 连接超时(毫秒) */
-    private int connectTimeout = 5000;
-    /** 读取超时(毫秒) */
-    private int readTimeout = 10000;
-}

+ 25 - 0
fs-service/src/main/java/com/fs/jhmApi/config/JhmStoreConfig.java

@@ -0,0 +1,25 @@
+package com.fs.jhmApi.config;
+
+import lombok.Data;
+import java.util.List;
+
+/**
+ * 聚好麦店铺配置根对象,对应数据库 JSON 结构
+ */
+@Data
+public class JhmStoreConfig {
+    private List<StoreConfig> stores;
+
+    @Data
+    public static class StoreConfig {
+        private String storeName;//店铺名称
+        private String appName;//应用名称
+        private String appKey;
+        private String appSecret;
+        private String businessUrl;//业务接口地址
+        /** 连接超时(毫秒) */
+        private int connectTimeout = 5000;
+        /** 读取超时(毫秒) */
+        private int readTimeout = 10000;
+    }
+}

+ 4 - 4
fs-service/src/main/java/com/fs/jhmApi/model/request/JHMOrderInfoRequest.java

@@ -20,18 +20,18 @@ public class JHMOrderInfoRequest {
     @JsonProperty("filter_type")
     private Integer filterType;
 
-    /** 开始时间(10位时间戳),必需 */
+    /** 开始时间(10位时间戳) */
     @JsonProperty("start_time")
     private Long startTime;
 
-    /** 结束时间(10位时间戳),必需 */
+    /** 结束时间(10位时间戳 */
     @JsonProperty("end_time")
     private Long endTime;
 
-    /** 页码,必需 */
+    /** 页码 */
     private Integer page;
 
-    /** 每页条数,必需 */
+    /** 每页条数(20260702最大支持100条) */
     @JsonProperty("page_size")
     private Integer pageSize;
 }

+ 0 - 40
fs-service/src/main/java/com/fs/jhmApi/model/request/JHMOrderUpdateRequest.java

@@ -1,40 +0,0 @@
-package com.fs.jhmApi.model.request;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Data;
-
-import java.util.List;
-
-/**
- * 更新订单信息请求体(Body 参数)
- * 对应接口:POST /open-api/order/update
- */
-@Data
-public class JHMOrderUpdateRequest {
-
-    /** 订单更新列表,必需 */
-    private List<OrderUpdateItem> list;
-
-    @Data
-    public static class OrderUpdateItem {
-        /** 订单号,必需 */
-        @JsonProperty("order_num")
-        private String orderNum;
-
-        /** 快递公司 ID,必需 */
-        @JsonProperty("express_code")
-        private String expressCode;
-
-        /** 快递公司名称,必需 */
-        @JsonProperty("express_name")
-        private String expressName;
-
-        /** 快递单号,必需 */
-        @JsonProperty("express_number")
-        private String expressNumber;
-
-        /** 快递单号修改时间(10位时间戳),必需 */
-        @JsonProperty("express_update_time")
-        private Long expressUpdateTime;
-    }
-}

+ 224 - 76
fs-service/src/main/java/com/fs/jhmApi/model/response/JHMOrderDetail.java

@@ -5,184 +5,332 @@ import lombok.Data;
 import java.util.List;
 
 /**
- * 订单详情(对应推送示例中的订单对象)
- * 字段与推送数据中的订单主体一致,但无 app_key 和 action
+ * 聚好麦订单详情(对应 /open-api/order/info 响应中的订单对象)
+ * 字段完全参照官方文档定义
  */
 @Data
 public class JHMOrderDetail {
 
+    // ==================== 订单基本信息 ====================
     @JsonProperty("union_id")
-    private String unionId;
+    private String unionId;                 // 用户ID
 
     @JsonProperty("order_num")
-    private String orderNum;
+    private String orderNum;                // 订单号
 
-    /** 订单主状态:-1取消,1待支付,2待发货,3待收货,4已完成 */
+    /** 订单状态:-1已取消,1待支付,2待发货,3待收货,4已完成,5售后中,6支付失败 */
     private Integer status;
 
-    /** 售后状态:1待处理,2待退货,3待收货,6退款完成,7关闭,8退款中,9极速退款,12失败,13用户取消 */
+    /** 售后状态:1待处理,2待买家退货,3待商家收货,4换货待商家发货,5待买家收货,6退款完成,7售后关闭,8退款中,9极速退款成功,10售后完成,11待买家处理,12退款失败 */
     @JsonProperty("after_status")
     private Integer afterStatus;
 
     @JsonProperty("after_status_remark")
-    private String afterStatusRemark;
+    private String afterStatusRemark;       // 售后状态备注
 
     @JsonProperty("cancel_type")
-    private Integer cancelType;
+    private Integer cancelType;             // 取消类型:1库存不足,2地址不全,3区域库存不足,4买家不想买,5协商取消,6其他,7用户取消
 
     @JsonProperty("total_money")
-    private Integer totalMoney;
+    private Integer totalMoney;             // 订单总金额(分)
 
-    private Integer money;
-    private Integer discount;
+    private Integer money;                  // 订单支付金额(分)
+
+    private Integer discount;               // 优惠金额(分)
 
     @JsonProperty("freight_money")
-    private Integer freightMoney;
+    private Integer freightMoney;           // 运费(分)
+
+    @JsonProperty("channel_type")
+    private Integer channelType;            // 渠道类型:1,2,3=广点通,4=磁力,5=bilibili,6=巨量,7=百度
 
     @JsonProperty("order_source")
-    private Integer orderSource;
+    private Integer orderSource;            // 订单来源:1自然流量,2广告引流,3回传流量
 
     @JsonProperty("pay_mode")
-    private Integer payMode;
+    private Integer payMode;                // 支付方式:1余额,2微信支付,3支付宝
 
     @JsonProperty("pay_sn")
-    private String paySn;
+    private String paySn;                   // 支付单号
 
     @JsonProperty("pay_time")
-    private Long payTime;
+    private Long payTime;                   // 支付时间(时间戳)
 
     @JsonProperty("pay_money")
-    private Integer payMoney;
+    private Integer payMoney;               // 支付金额(分)
 
     @JsonProperty("pay_state_desc")
-    private String payStateDesc;
+    private String payStateDesc;            // 支付状态描述
 
     @JsonProperty("transaction_id")
-    private String transactionId;
+    private String transactionId;           // 微信支付订单号
 
     @JsonProperty("complete_time")
-    private Long completeTime;
+    private Long completeTime;              // 订单完成时间
 
     @JsonProperty("receipt_time")
-    private Long receiptTime;
+    private Long receiptTime;               // 快递签收时间
 
     @JsonProperty("deliver_time")
-    private Long deliverTime;
+    private Long deliverTime;               // 发货时间
+
+    @JsonProperty("is_callback")
+    private Integer isCallback;             // 是否回传(1是,0否)
 
     @JsonProperty("ad_account_id")
-    private Long adAccountId;
+    private Integer adAccountId;            // 广告账号ID
+
+    @JsonProperty("ad_plan_id")
+    private String adPlanId;                // 广告计划ID
 
     @JsonProperty("shop_remark")
-    private String shopRemark;
+    private String shopRemark;              // 商家备注
 
     @JsonProperty("user_remark")
-    private String userRemark;
+    private String userRemark;              // 用户备注
 
     @JsonProperty("updated_at")
-    private Long updatedAt;
+    private Long updatedAt;                 // 订单更新时间
 
     @JsonProperty("created_at")
-    private Long createdAt;
+    private Long createdAt;                 // 订单创建时间
+
+    @JsonProperty("parent_order_num")
+    private String parentOrderNum;          // 父级订单号
+
+    @JsonProperty("order_type")
+    private Integer orderType;              // 订单类型:1普通,2全球购,3香港直邮,4微信小店
 
-    /** 商品信息 */
-    private Goods goods;
+    @JsonProperty("activity_type")
+    private Integer activityType;           // 活动类型
 
-    /** 买家/收货信息 */
-    private Buyer buyer;
+    @JsonProperty("app_name")
+    private String appName;                 // 应用名称
+
+    // ==================== 嵌套对象 ====================
+    private Goods goods;                    // 商品信息
+
+    private Buyer buyer;                    // 买家/收货信息
 
-    /** 售后单列表(可能有多个) */
     @JsonProperty("after_sales")
-    private List<AfterSale> afterSales;
+    private List<AfterSale> afterSales;     // 售后单列表
+
+    @JsonProperty("ad_info")
+    private AdInfo adInfo;                  // 广告信息
 
-    // ---------- 内部类 ----------
+    // ==================== 内部类定义 ====================
+
+    /**
+     * 商品信息
+     */
     @Data
     public static class Goods {
         @JsonProperty("product_id")
-        private String productId;
+        private String productId;           // 商品ID
+
         @JsonProperty("product_code")
-        private String productCode; // 商家商品编码
+        private String productCode;         // 商品编码(商家自定义)
+
         @JsonProperty("sku_id")
-        private Integer skuId;      // 0表示无规格
+        private Integer skuId;              // SKU ID(0表示无规格)
+
         @JsonProperty("sku_code")
-        private String skuCode;     // SKU编码
-        private Integer price;
-        private Integer num;
-        private String image;
-        private String name;
+        private String skuCode;             // SKU编码
+
+        private Integer price;              // 单价(分)
+
+        private Integer num;                // 购买数量
+
+        private String image;               // 商品封面图
+
+        private String name;                // 商品名称
+
         @JsonProperty("sku_name")
-        private String skuName;
+        private String skuName;             // 规格名称
+
         @JsonProperty("created_at")
         private Long createdAt;
+
         @JsonProperty("updated_at")
         private Long updatedAt;
+
         @JsonProperty("after_num")
-        private Integer afterNum;
+        private Integer afterNum;           // 已售后数量
+
+        @JsonProperty("active_type")
+        private Integer activeType;         // 商品类型:0普通,1顺手买
     }
 
+    /**
+     * 买家/收货信息
+     */
     @Data
     public static class Buyer {
-        private String express;
+        private String express;             // 快递公司名称
+
         @JsonProperty("express_number")
-        private String expressNumber;
-        private String consignee;
+        private String expressNumber;       // 快递单号
+
+        private String consignee;           // 收货人姓名
+
         @JsonProperty("receive_phone")
-        private String receivePhone;
-        private String address;
-        private String province;
-        private String city;
-        private String area;
+        private String receivePhone;        // 收货人手机号
+
+        private String address;             // 完整地址
+
+        private String province;            // 省份
+
+        private String city;                // 城市
+
+        private String area;                // 区/县
+
         @JsonProperty("address_detail")
-        private String addressDetail;
+        private String addressDetail;       // 详细地址
+
         @JsonProperty("area_id")
-        private Integer areaId;
+        private Integer areaId;             // 区域ID
+
         @JsonProperty("area_ids")
-        private String areaIds;
+        private String areaIds;             // 地址层级ID(逗号分隔)
+
         @JsonProperty("edit_address_code")
-        private Integer editAddressCode;
+        private Integer editAddressCode;    // 地址修改标记:0未修改,2用户修改,3商家修改,5都修改
+
         @JsonProperty("is_update")
-        private Integer isUpdate;
+        private Integer isUpdate;           // 是否修改过物流:0否,1是
+
         @JsonProperty("express_update_time")
-        private Long expressUpdateTime;
+        private Long expressUpdateTime;     // 快递单号修改时间
+
         @JsonProperty("created_at")
         private Long createdAt;
+
         @JsonProperty("updated_at")
         private Long updatedAt;
     }
 
+    /**
+     * 售后信息
+     */
     @Data
     public static class AfterSale {
         @JsonProperty("refund_sn")
-        private String refundSn;
+        private String refundSn;            // 退款单号
+
         @JsonProperty("after_sale_status")
-        private Integer afterSaleStatus;
+        private Integer afterSaleStatus;    // 售后状态(同订单中的 after_status)
+
         @JsonProperty("after_sale_type")
-        private Integer afterSaleType;
-        private String reason;
-        private Integer number;
-        private Integer price;
+        private Integer afterSaleType;      // 业务类型:1仅退款,2退货退款,3换货,4极速退款
+
+        private String reason;              // 售后原因
+
+        private Integer number;             // 退货数量
+
+        private Integer price;              // 退款单价(分)
+
         @JsonProperty("refund_money")
-        private Integer refundMoney;
+        private Integer refundMoney;        // 实际申请退款金额(分)
+
         @JsonProperty("refuse_reason")
-        private String refuseReason;
+        private String refuseReason;        // 拒绝原因
+
         @JsonProperty("refuse_fail")
-        private String refuseFail;
+        private String refuseFail;          // 微信退款失败原因
+
         @JsonProperty("apply_at")
-        private Long applyAt;
+        private Long applyAt;               // 申请时间
+
         @JsonProperty("confirm_at")
-        private Long confirmAt;
+        private Long confirmAt;             // 确认时间
+
         @JsonProperty("audit_at")
-        private Long auditAt;
+        private Long auditAt;               // 审核时间
+
         @JsonProperty("cancel_at")
-        private Long cancelAt;
+        private Long cancelAt;              // 取消时间
+
         @JsonProperty("updated_at")
-        private Long updatedAt;
+        private Long updatedAt;             // 更新时间
+
         @JsonProperty("success_at")
-        private Long successAt;
+        private Long successAt;             // 退款成功时间
+
         @JsonProperty("refuse_at")
-        private Long refuseAt;
+        private Long refuseAt;              // 拒绝时间
+
         @JsonProperty("after_sale")
-        private Integer afterSale;
+        private Integer afterSale;          // 是否极速退款:1开启,2关闭
+
+        @JsonProperty("express_name")
+        private String expressName;         // 退货物流公司
+
+        @JsonProperty("express_number")
+        private String expressNumber;       // 退货物流单号
+
+        private String consignee;           // 申请人姓名
+
+        @JsonProperty("user_send_time")
+        private Long userSendTime;          // 用户发货时间
+
+        @JsonProperty("cancel_source")
+        private Integer cancelSource;       // 取消来源:1商家后台取消
+
         @JsonProperty("after_status_remark")
-        private String afterStatusRemark;
+        private String afterStatusRemark;   // 售后状态说明
+
+        @JsonProperty("change_product")
+        private ChangeProduct changeProduct; // 换货商品信息(当售后类型为换货时)
+    }
+
+    /**
+     * 换货商品信息(仅在换货售后中返回)
+     */
+    @Data
+    public static class ChangeProduct {
+        @JsonProperty("product_id")
+        private String productId;           // 换货商品ID
+
+        @JsonProperty("product_code")
+        private String productCode;         // 换货商品编码
+
+        @JsonProperty("sku_id")
+        private Integer skuId;              // 换货SKU ID
+
+        @JsonProperty("sku_code")
+        private String skuCode;             // 换货SKU编码
+
+        private String name;                // 换货商品名称
+
+        @JsonProperty("sku_name")
+        private String skuName;             // 换货规格名称
+
+        private Integer price;              // 换货单价(分)
+
+        private Integer num;                // 换货数量
+    }
+
+    /**
+     * 广告信息
+     */
+    @Data
+    public static class AdInfo {
+        @JsonProperty("ad_account_id")
+        private String adAccountId;         // 广告主ID
+
+        @JsonProperty("campaign_id")
+        private String campaignId;          // 计划ID
+
+        @JsonProperty("ad_group_id")
+        private String adGroupId;           // 项目ID(广告组)
+
+        @JsonProperty("creative_id")
+        private String creativeId;          // 创意ID
+
+        @JsonProperty("click_id")
+        private String clickId;             // 点击ID
+
+        @JsonProperty("req_id")
+        private String reqId;               // 请求ID
     }
 }

+ 0 - 27
fs-service/src/main/java/com/fs/jhmApi/service/IJHMThirdOrderService.java

@@ -1,27 +0,0 @@
-package com.fs.jhmApi.service;
-
-
-import com.fs.jhmApi.model.request.JHMOrderInfoRequest;
-import com.fs.jhmApi.model.request.JHMOrderUpdateRequest;
-import com.fs.jhmApi.model.response.JHMOrderInfoData;
-import com.fs.jhmApi.model.response.JHMOrderUpdateResponse;
-
-/**
- * 第三方订单服务接口
- */
-public interface IJHMThirdOrderService {
-
-    /**
-     * 获取订单信息
-     * @param request 请求参数
-     * @return 订单列表数据
-     */
-    JHMOrderInfoData getOrderInfo(JHMOrderInfoRequest request);
-
-    /**
-     * 更新订单信息(发货)
-     * @param request 请求参数
-     * @return 更新结果明细
-     */
-    JHMOrderUpdateResponse updateOrder(JHMOrderUpdateRequest request);
-}

+ 0 - 67
fs-service/src/main/java/com/fs/jhmApi/service/impl/JHMThirdOrderServiceImpl.java

@@ -1,67 +0,0 @@
-package com.fs.jhmApi.service.impl;
-
-
-import com.fs.jhmApi.client.JHMThirdApiClient;
-import com.fs.jhmApi.model.request.JHMOrderInfoRequest;
-import com.fs.jhmApi.model.request.JHMOrderUpdateRequest;
-import com.fs.jhmApi.model.response.JHMBaseResponse;
-import com.fs.jhmApi.model.response.JHMOrderInfoData;
-import com.fs.jhmApi.model.response.JHMOrderUpdateResponse;
-import com.fs.jhmApi.service.IJHMThirdOrderService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.stereotype.Service;
-
-/**
- * 第三方订单服务实现
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class JHMThirdOrderServiceImpl implements IJHMThirdOrderService {
-
-    private final JHMThirdApiClient apiClient;
-
-    /** 接口路径常量 */
-    private static final String PATH_ORDER_INFO = "/open-api/order/info";
-    private static final String PATH_ORDER_UPDATE = "/open-api/order/update";
-
-    @Override
-    public JHMOrderInfoData getOrderInfo(JHMOrderInfoRequest request) {
-        ParameterizedTypeReference<JHMBaseResponse<JHMOrderInfoData>> responseType =
-                new ParameterizedTypeReference<JHMBaseResponse<JHMOrderInfoData>>() {};
-        JHMBaseResponse<JHMOrderInfoData> response = apiClient.doPost(PATH_ORDER_INFO, request, responseType);
-        return handleResponse(response, PATH_ORDER_INFO);
-    }
-
-    @Override
-    public JHMOrderUpdateResponse updateOrder(JHMOrderUpdateRequest request) {
-        ParameterizedTypeReference<JHMBaseResponse<JHMOrderUpdateResponse>> responseType =
-                new ParameterizedTypeReference<JHMBaseResponse<JHMOrderUpdateResponse>>() {};
-        JHMBaseResponse<JHMOrderUpdateResponse> response = apiClient.doPost(PATH_ORDER_UPDATE, request, responseType);
-        return handleResponse(response, PATH_ORDER_UPDATE);
-    }
-
-    /**
-     * 统一响应处理:校验 code 和 success,失败抛出异常
-     */
-    private <T> T handleResponse(JHMBaseResponse<T> response, String path) {
-        if (response == null) {
-            throw new RuntimeException("聚好麦第三方接口返回空响应,path: " + path);
-        }
-        if (!Boolean.TRUE.equals(response.getSuccess())) {
-            log.error("聚好麦第三方接口 success=false, code: {}, traceId: {}, path: {}",
-                    response.getCode(), response.getTraceId(), path);
-            throw new RuntimeException(String.format("聚好麦第三方接口失败,code=%s, traceId=%s",
-                    response.getCode(), response.getTraceId()));
-        }
-        if (response.getCode() == null || !response.getCode().equals(10000)) {
-            log.error("第三方业务错误, code: {}, traceId: {}, path: {}",
-                    response.getCode(), response.getTraceId(), path);
-            throw new RuntimeException(String.format("第三方业务错误,code=%s, traceId=%s",
-                    response.getCode(), response.getTraceId()));
-        }
-        return response.getData();
-    }
-}

+ 6 - 0
fs-service/src/main/java/com/fs/qw/mapper/FsCompanyCustomerMapper.java

@@ -103,4 +103,10 @@ public interface FsCompanyCustomerMapper {
      * 根据手机号批量更新客户信息表通话状态
      * */
     int updateBatchFsCompanyCustomerCallStatusByPhoneList(@Param("phoneAndCallStatusVOList") List<UserPhoneAndCallStatusVO> phoneAndCallStatusVOList);
+
+    /** 根据手机号列表批量查询客户 */
+    List<FsCompanyCustomer> selectByPhones(@Param("phones") List<String> phones);
+
+    /** 批量更新客户(只更新指定字段) */
+    int batchUpdateForJhmTask(@Param("list")List<FsCompanyCustomer> list);
 }

+ 64 - 1
fs-service/src/main/resources/mapper/qw/FsCompanyCustomerMapper.xml

@@ -60,7 +60,14 @@
             and customer_name like concat('%', #{customerName}, '%')
         </if>
         <if test="phone != null and phone != ''">
-            and phone like concat('%', #{phone}, '%')
+            <choose>
+                <when test="phone.length() == 4">
+                    and right(phone, 4) = #{phone}
+                </when>
+                <otherwise>
+                    and phone like concat('%', #{phone}, '%')
+                </otherwise>
+            </choose>
         </if>
         <if test="companyUserName != null and companyUserName != ''">
             and company_user_name like concat('%', #{companyUserName}, '%')
@@ -215,6 +222,14 @@
         SELECT id FROM fs_company_customer WHERE phone = #{phone} and del_flag = '0' LIMIT 1
     </select>
 
+    <select id="selectByPhones" resultType="com.fs.qw.domain.FsCompanyCustomer">
+        <include refid="selectFsCompanyCustomerVo"/>
+        WHERE phone IN
+        <foreach collection="phones" item="phone" open="(" separator="," close=")">
+            #{phone}
+        </foreach>
+    </select>
+
     <insert id="insertFsCompanyCustomer" parameterType="com.fs.qw.domain.FsCompanyCustomer" useGeneratedKeys="true" keyProperty="id">
         insert into fs_company_customer
         <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -458,4 +473,52 @@
         </foreach>
     </update>
 
+    <update id="batchUpdateForJhmTask">
+        UPDATE fs_company_customer
+        <trim prefix="SET" suffixOverrides=",">
+            <!-- 地址 -->
+            <trim prefix="address = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN id = #{item.id}
+                    AND #{item.address} IS NOT NULL
+                    AND #{item.address} != ''
+                    THEN #{item.address}
+                </foreach>
+                ELSE address
+            </trim>
+            <!-- 客户姓名 -->
+            <trim prefix="customer_name = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN id = #{item.id}
+                    AND #{item.customerName} IS NOT NULL
+                    AND #{item.customerName} != ''
+                    THEN #{item.customerName}
+                </foreach>
+                ELSE customer_name
+            </trim>
+            <!-- 成交状态:0未成交,1已成交 -->
+            <trim prefix="kdzl_make_status = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN id = #{item.id}
+                    AND #{item.kdzlMakeStatus} IS NOT NULL
+                    THEN #{item.kdzlMakeStatus}
+                </foreach>
+                ELSE kdzl_make_status
+            </trim>
+            <!-- 创建时间/订单时间 -->
+            <trim prefix="create_time = CASE" suffix="END,">
+                <foreach collection="list" item="item">
+                    WHEN id = #{item.id}
+                    AND #{item.createTime} IS NOT NULL
+                    THEN #{item.createTime}
+                </foreach>
+                ELSE create_time
+            </trim>
+        </trim>
+        WHERE id IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            #{item.id}
+        </foreach>
+    </update>
+
 </mapper>