diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/ErpGoofishOrderMapper.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/ErpGoofishOrderMapper.java index 2943126..7617b84 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/ErpGoofishOrderMapper.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/mapper/ErpGoofishOrderMapper.java @@ -19,5 +19,10 @@ public interface ErpGoofishOrderMapper { List selectPendingShip(@Param("statuses") List statuses, @Param("limit") int limit); + /** + * 按闲鱼买家订单号(与 jd_order.third_party_order_no 对齐时)检索,用于京东运单就绪后补绑发货。 + */ + List selectByGoofishOrderNo(@Param("orderNo") String orderNo); + int resetShipForRetry(@Param("id") Long id); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java index a82f366..7565f60 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/goofish/GoofishOrderPipeline.java @@ -36,6 +36,8 @@ public class GoofishOrderPipeline { private static final Logger log = LoggerFactory.getLogger(GoofishOrderPipeline.class); private static final String REDIS_WAYBILL_KEY_PREFIX = "logistics:waybill:order:"; + /** F-闲鱼等京东单:运单可按第三方单号=闲鱼单号写入 Redis,见 LogisticsServiceImpl 镜像写入 */ + public static final String REDIS_WAYBILL_GOOFISH_ORDER_PREFIX = "logistics:waybill:goofish:"; @Resource private ErpGoofishOrderMapper erpGoofishOrderMapper; @@ -175,7 +177,7 @@ public class GoofishOrderPipeline { if (StringUtils.isEmpty(orderNo)) { return; } - JDOrder jd = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo); + JDOrder jd = jdOrderService.selectJDOrderByThirdPartyOrderNo(orderNo.trim()); if (jd == null || jd.getId() == null) { return; } @@ -415,38 +417,90 @@ public class GoofishOrderPipeline { } public void syncWaybillFromRedis(ErpGoofishOrder row) { - if (row == null || row.getId() == null || row.getJdOrderId() == null) { + if (row == null || row.getId() == null) { return; } - String key = REDIS_WAYBILL_KEY_PREFIX + row.getJdOrderId(); - String wb = stringRedisTemplate.opsForValue().get(key); - if (StringUtils.isEmpty(wb)) { + String wb = null; + if (row.getJdOrderId() != null) { + String key = REDIS_WAYBILL_KEY_PREFIX + row.getJdOrderId(); + wb = stringRedisTemplate.opsForValue().get(key); + } + if (StringUtils.isEmpty(wb) && StringUtils.isNotEmpty(row.getOrderNo())) { + String gk = REDIS_WAYBILL_GOOFISH_ORDER_PREFIX + row.getOrderNo().trim(); + wb = stringRedisTemplate.opsForValue().get(gk); + } + if (wb == null || wb.trim().isEmpty()) { return; } - if (wb.equals(row.getLocalWaybillNo())) { + String nw = wb.trim(); + String prevLocal = row.getLocalWaybillNo(); + if (nw.equals(prevLocal != null ? prevLocal.trim() : "")) { return; } - String prev = row.getLocalWaybillNo(); ErpGoofishOrder patch = new ErpGoofishOrder(); patch.setId(row.getId()); - patch.setLocalWaybillNo(wb.trim()); + patch.setLocalWaybillNo(nw); patch.setUpdateTime(DateUtils.getNowDate()); erpGoofishOrderMapper.update(patch); - row.setLocalWaybillNo(wb.trim()); + row.setLocalWaybillNo(nw); if (goofishOrderChangeLogger != null) { goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(), GoofishOrderChangeLogger.TYPE_LOGISTICS, GoofishOrderChangeLogger.SOURCE_REDIS_WAYBILL, - "本地运单 " + (prev == null || prev.isEmpty() ? "无" : prev) + "→" + wb.trim()); + "本地运单 " + (prevLocal == null || prevLocal.isEmpty() ? "无" : prevLocal) + "→" + nw); } } + /** + * 与前端列表「运单号」展示对齐:除 local/detail 列外,从 detail_json 的 data 根取 waybill_no(列未同步时列表仍可能有单号)。 + */ + private String extractWaybillFromDetailJson(String detailJson) { + if (StringUtils.isEmpty(detailJson)) { + return null; + } + try { + JSONObject root = JSON.parseObject(detailJson); + if (root == null) { + return null; + } + JSONObject data = root.getJSONObject("data"); + if (data == null) { + data = root; + } + String wb = firstNonEmpty(data, "waybill_no", "waybillNo", "mail_no", "mailNo", "express_no", "expressNo"); + return StringUtils.isEmpty(wb) ? null : wb.trim(); + } catch (Exception e) { + log.debug("从 detail_json 解析运单号失败: {}", e.getMessage()); + return null; + } + } + + private String resolveWaybillForShip(ErpGoofishOrder row) { + String waybill = row.getLocalWaybillNo(); + if (StringUtils.isEmpty(waybill)) { + waybill = row.getDetailWaybillNo(); + } + if (StringUtils.isEmpty(waybill)) { + waybill = extractWaybillFromDetailJson(row.getDetailJson()); + } + return StringUtils.isEmpty(waybill) ? null : waybill.trim(); + } + public void tryAutoShip(ErpGoofishOrder row) { + tryAutoShip(row, false); + } + + /** + * @param manualRetry true 时表示管理端「重试发货」:放宽「订单状态须在 auto-ship-order-statuses」校验(与前端列表展示不一致时常因该条件直接 return) + */ + public void tryAutoShip(ErpGoofishOrder row, boolean manualRetry) { if (row == null || row.getId() == null) { return; } List awaiting = resolveAutoShipOrderStatuses(); - if (row.getOrderStatus() == null || !awaiting.contains(row.getOrderStatus())) { - return; + if (!manualRetry) { + if (row.getOrderStatus() == null || !awaiting.contains(row.getOrderStatus())) { + return; + } } if (row.getRefundStatus() != null && row.getRefundStatus() != 0) { return; @@ -454,10 +508,7 @@ public class GoofishOrderPipeline { if (row.getShipStatus() != null && row.getShipStatus() == 1) { return; } - String waybill = row.getLocalWaybillNo(); - if (StringUtils.isEmpty(waybill)) { - waybill = row.getDetailWaybillNo(); - } + String waybill = resolveWaybillForShip(row); if (StringUtils.isEmpty(waybill)) { return; } @@ -1047,7 +1098,15 @@ public class GoofishOrderPipeline { if (full == null) { continue; } + tryLinkJdOrder(full); syncWaybillFromRedis(full); + if ((full.getRefundStatus() == null || full.getRefundStatus() == 0) + && statuses.contains(full.getOrderStatus()) + && StringUtils.isEmpty(full.getLocalWaybillNo()) + && StringUtils.isEmpty(full.getDetailWaybillNo())) { + refreshDetail(full); + syncWaybillFromRedis(full); + } tryAutoShip(full); n++; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java index fd05b41..d3a3493 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/ErpGoofishOrderServiceImpl.java @@ -7,10 +7,12 @@ import com.ruoyi.jarvis.domain.ErpGoofishOrder; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLog; import com.ruoyi.jarvis.domain.ErpGoofishOrderEventLogQuery; import com.ruoyi.jarvis.domain.ErpOpenConfig; +import com.ruoyi.jarvis.domain.JDOrder; import com.ruoyi.jarvis.dto.GoofishNotifyMessage; import com.ruoyi.jarvis.mapper.ErpGoofishOrderEventLogMapper; import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper; import com.ruoyi.jarvis.service.IErpGoofishOrderService; +import com.ruoyi.jarvis.service.IJDOrderService; import com.ruoyi.jarvis.service.IErpOpenConfigService; import com.ruoyi.jarvis.service.goofish.GoofishNotifyAsyncFacade; import com.ruoyi.jarvis.service.goofish.GoofishOrderChangeLogger; @@ -20,13 +22,20 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.ruoyi.common.utils.DateUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import javax.annotation.Resource; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; @Service public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { + private static final Logger log = LoggerFactory.getLogger(ErpGoofishOrderServiceImpl.class); + @Autowired private ObjectProvider rocketMQTemplate; @@ -51,6 +60,9 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { @Resource private GoofishOrderChangeLogger goofishOrderChangeLogger; + @Resource + private IJDOrderService jdOrderService; + @Override public void publishOrProcessNotify(String appid, Long timestamp, JSONObject body) { RocketMQTemplate mq = rocketMQTemplate.getIfAvailable(); @@ -135,8 +147,10 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { erpGoofishOrderMapper.resetShipForRetry(id); ErpGoofishOrder row = erpGoofishOrderMapper.selectById(id); if (row != null) { + goofishOrderPipeline.refreshDetail(row); + goofishOrderPipeline.tryLinkJdOrder(row); goofishOrderPipeline.syncWaybillFromRedis(row); - goofishOrderPipeline.tryAutoShip(row); + goofishOrderPipeline.tryAutoShip(row, true); } } @@ -155,20 +169,72 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { if (jdOrderId == null) { return; } - ErpGoofishOrder query = new ErpGoofishOrder(); - query.setJdOrderId(jdOrderId); - List list = erpGoofishOrderMapper.selectList(query); - if (list == null || list.isEmpty()) { + JDOrder jd = jdOrderService.selectJDOrderById(jdOrderId); + LinkedHashSet processedGoofishPk = new LinkedHashSet<>(); + + ErpGoofishOrder queryByJd = new ErpGoofishOrder(); + queryByJd.setJdOrderId(jdOrderId); + List linked = erpGoofishOrderMapper.selectList(queryByJd); + if (linked != null) { + for (ErpGoofishOrder ref : linked) { + processGoofishWaybillSyncAfterJdReady(ref.getId(), processedGoofishPk); + } + } + + if (jd != null && org.springframework.util.StringUtils.hasText(jd.getThirdPartyOrderNo()) + && isFxianyuDistributionMark(jd.getDistributionMark())) { + List byNo = + erpGoofishOrderMapper.selectByGoofishOrderNo(jd.getThirdPartyOrderNo().trim()); + if (byNo != null) { + for (ErpGoofishOrder ref : byNo) { + ErpGoofishOrder full = erpGoofishOrderMapper.selectById(ref.getId()); + if (full == null) { + continue; + } + if (full.getJdOrderId() != null && !full.getJdOrderId().equals(jdOrderId)) { + log.warn( + "闲鱼单 {} 已关联本地 jd_order_id={},与本次京东单行主键 {} 不一致,跳过补绑", + full.getOrderNo(), full.getJdOrderId(), jdOrderId); + continue; + } + if (full.getJdOrderId() == null) { + attachGoofishToJdOrder(full.getId(), jdOrderId); + } + processGoofishWaybillSyncAfterJdReady(ref.getId(), processedGoofishPk); + } + } + } + } + + /** 单次:读库、同步 Redis 运单(含镜像 key)、尽力自动发货 */ + private void processGoofishWaybillSyncAfterJdReady(Long erpGoofishPk, + LinkedHashSet processedGoofishPk) { + if (erpGoofishPk == null || processedGoofishPk.contains(erpGoofishPk)) { return; } - for (ErpGoofishOrder ref : list) { - ErpGoofishOrder full = erpGoofishOrderMapper.selectById(ref.getId()); - if (full == null) { - continue; - } - goofishOrderPipeline.syncWaybillFromRedis(full); - goofishOrderPipeline.tryAutoShip(full); + ErpGoofishOrder full = erpGoofishOrderMapper.selectById(erpGoofishPk); + if (full == null) { + return; } + goofishOrderPipeline.syncWaybillFromRedis(full); + goofishOrderPipeline.tryAutoShip(full); + processedGoofishPk.add(erpGoofishPk); + } + + private void attachGoofishToJdOrder(long erpGoofishPk, long jdOrderDbId) { + ErpGoofishOrder p = new ErpGoofishOrder(); + p.setId(erpGoofishPk); + p.setJdOrderId(jdOrderDbId); + p.setUpdateTime(DateUtils.getNowDate()); + erpGoofishOrderMapper.update(p); + } + + private static boolean isFxianyuDistributionMark(String distributionMark) { + if (!org.springframework.util.StringUtils.hasText(distributionMark)) { + return false; + } + String m = distributionMark.trim(); + return m.startsWith("F") || m.contains("\u95f2\u9c7c"); } @Override @@ -187,10 +253,9 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { if (jdOrderId == null || goofishOrderChangeLogger == null) { return; } - ErpGoofishOrder query = new ErpGoofishOrder(); - query.setJdOrderId(jdOrderId); - List list = erpGoofishOrderMapper.selectList(query); - if (list == null || list.isEmpty()) { + JDOrder jd = jdOrderService.selectJDOrderById(jdOrderId); + LinkedHashSet ids = resolveGoofishRowIdsForJdLogisticsEvent(jdOrderId, jd); + if (ids.isEmpty()) { return; } String wb = waybillNo != null ? waybillNo.trim() : ""; @@ -199,8 +264,9 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { if (msg.length() > 1000) { msg = msg.substring(0, 999) + "…"; } - for (ErpGoofishOrder row : list) { - if (row.getId() == null) { + for (Long oid : ids) { + ErpGoofishOrder row = erpGoofishOrderMapper.selectById(oid); + if (row == null || row.getId() == null) { continue; } goofishOrderChangeLogger.append(row.getId(), row.getAppKey(), row.getOrderNo(), @@ -208,6 +274,33 @@ public class ErpGoofishOrderServiceImpl implements IErpGoofishOrderService { } } + private LinkedHashSet resolveGoofishRowIdsForJdLogisticsEvent(Long jdOrderId, JDOrder jd) { + LinkedHashSet ids = new LinkedHashSet<>(); + ErpGoofishOrder q = new ErpGoofishOrder(); + q.setJdOrderId(jdOrderId); + List linked = erpGoofishOrderMapper.selectList(q); + if (linked != null) { + for (ErpGoofishOrder r : linked) { + if (r.getId() != null) { + ids.add(r.getId()); + } + } + } + if (jd != null && org.springframework.util.StringUtils.hasText(jd.getThirdPartyOrderNo()) + && isFxianyuDistributionMark(jd.getDistributionMark())) { + List byNo = + erpGoofishOrderMapper.selectByGoofishOrderNo(jd.getThirdPartyOrderNo().trim()); + if (byNo != null) { + for (ErpGoofishOrder r : byNo) { + if (r.getId() != null) { + ids.add(r.getId()); + } + } + } + } + return ids; + } + @Override public List listEventLogsByOrderId(Long orderId) { if (orderId == null) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java index 6b92485..2a1f20c 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/service/impl/LogisticsServiceImpl.java @@ -9,6 +9,7 @@ import com.ruoyi.jarvis.mapper.WeComShareLinkLogisticsJobMapper; import com.ruoyi.jarvis.service.IErpGoofishOrderService; import com.ruoyi.jarvis.service.ILogisticsService; import com.ruoyi.jarvis.service.IJDOrderService; +import com.ruoyi.jarvis.service.goofish.GoofishOrderPipeline; import com.ruoyi.system.service.ISysConfigService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -283,6 +284,7 @@ public class LogisticsServiceImpl implements ILogisticsService { */ private void safeNotifyGoofishShip(Long jdOrderId, String waybillNo, String traceSummary) { try { + mirrorFxianyuGoofishSecondaryWaybillRedis(jdOrderId, waybillNo); erpGoofishOrderService.traceJdLogisticsPushForGoofish(jdOrderId, waybillNo, traceSummary); erpGoofishOrderService.notifyJdWaybillReady(jdOrderId); } catch (Exception e) { @@ -290,6 +292,38 @@ public class LogisticsServiceImpl implements ILogisticsService { } } + /** + * F-闲鱼等:将运单镜像到 logistics:waybill:goofish:${第三方单号},即使 erp_goofish_order.jd_order_id 尚未关联也能被同步。 + */ + private void mirrorFxianyuGoofishSecondaryWaybillRedis(Long jdOrderDbId, String waybillNo) { + if (jdOrderDbId == null || !StringUtils.hasText(waybillNo)) { + return; + } + JDOrder jd = jdOrderService.selectJDOrderById(jdOrderDbId); + if (jd == null) { + return; + } + if (!isFxianyuJdDistributionMark(jd.getDistributionMark())) { + return; + } + String tp = jd.getThirdPartyOrderNo(); + if (!StringUtils.hasText(tp)) { + return; + } + stringRedisTemplate.opsForValue().set( + GoofishOrderPipeline.REDIS_WAYBILL_GOOFISH_ORDER_PREFIX + tp.trim(), + waybillNo.trim(), + 30, TimeUnit.DAYS); + } + + private static boolean isFxianyuJdDistributionMark(String distributionMark) { + if (!StringUtils.hasText(distributionMark)) { + return false; + } + String m = distributionMark.trim(); + return m.startsWith("F") || m.contains("\u95f2\u9c7c"); + } + @Override public boolean fetchLogisticsAndPush(JDOrder order) { if (order == null || order.getId() == null) { diff --git a/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/GoofishScheduledTasks.java b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/GoofishScheduledTasks.java index 9ba4d9b..b676e4b 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/GoofishScheduledTasks.java +++ b/ruoyi-system/src/main/java/com/ruoyi/jarvis/task/GoofishScheduledTasks.java @@ -46,6 +46,14 @@ public class GoofishScheduledTasks { } if (n > 0) { log.info("闲管家定时拉单本轮处理约 {} 条子订单项", n); + try { + int synced = erpGoofishOrderService.syncWaybillAndTryShipBatch(goofishProperties.getAutoShipBatchSize()); + if (synced > 0) { + log.info("拉单后轮询:运单同步/自动发货扫描处理 {} 条", synced); + } + } catch (Exception syncEx) { + log.warn("拉单后轮询运单同步/发货扫描异常 {}", syncEx.getMessage()); + } } } diff --git a/ruoyi-system/src/main/resources/mapper/jarvis/ErpGoofishOrderMapper.xml b/ruoyi-system/src/main/resources/mapper/jarvis/ErpGoofishOrderMapper.xml index ef2648f..3a13fbf 100644 --- a/ruoyi-system/src/main/resources/mapper/jarvis/ErpGoofishOrderMapper.xml +++ b/ruoyi-system/src/main/resources/mapper/jarvis/ErpGoofishOrderMapper.xml @@ -100,8 +100,8 @@ + + insert into erp_goofish_order (app_key, seller_id, user_name, order_no, order_type, order_status, refund_status, modify_time, product_id, item_id,